Шаблон Java Builder, вы понимаете?

Шаблоны проектирования

加油.png

Предисловие: Когда мне нечего делать в последнее время, я хочу посмотреть исходники некоторых часто используемых сторонних библиотек.Я не ожидал узнать, что прямое разгром исходников навредит телу после его прочтения.Вообщем, разработка превосходной библиотеки с открытым исходным кодом будет включать в себя множество шаблонов проектирования, например, разработка Android часто использует okHttp, чтобы открыть исходный код и посмотреть, Нани? Режим Builder можно увидеть повсюду, поэтому в этой статье будет дано краткое описание режима Builder, в основном для удобства анализа исходного кода, связанного с Android, начиная с практических приложений~

В проектировании oop-кодирования у нас есть классическая поговорка под названием «все является объектом», а в реальной разработке — до тех пор, пока мы можем получить экземпляр класса, то есть объект. Вы можете начать что-то делать, вы можете приказать объектам что-то делать, конечно ~ каждый объект имеет разные способности и может делать разные вещи. Объект хранит свойства-члены (переменные-члены и методы-члены) класса. Когда мы приказываем объекту работать за нас, мы фактически вызываем свойства, специфичные для объекта. Мы только что сказали, что возможности каждого объекта различны, и то, что объект может делать, определяется при его первом создании. Давайте сначала поговорим о методе строительства объекта.

1. Сборка через конструктор

Предположим, сцена: мы используем класс для представления автомобиля, и у автомобиля есть некоторые обязательные атрибуты, такие как: кузов, шины, двигатель, руль и т. д. Также есть некоторые необязательные атрибуты, которых предполагается более 10, такие как: некоторые украшения на машине, подушки безопасности и многие другие атрибуты.

Если мы используем конструкторы для создания объектов, наш подход состоит в том, чтобы предоставить первому конструктору 4 обязательных свойства, а затем по очереди перегружать разные конструкторы необязательными свойствами, что возможно, но будут следующие проблемы:

  • Когда свойств становится слишком много, приходится перегружать n множественных конструкторов, а состав различных конструкторов формулируется под конкретные требования, не говоря уже об объеме кода, гибкость сильно снижается.
  • Когда клиент вызывает конструктор, ему нужно передать множество свойств, что может привести к трудностям при вызове.Нам нужно быть знакомыми со свойствами, предоставляемыми каждым конкретным конструктором.В случае многих свойств параметров мы можем быть небрежными , И неправильный порядок.
public class Car {
    /**
     * 必需属性
     */
    private String carBody;//车身
    private String tyre;//轮胎
    private String engine;//发动机
    private String aimingCircle;//方向盘
    /**
     * 可选属性
     */
    private String decoration;//车内装饰品

    /**
     * 必需属性构造器
     *
     * @param carBody
     * @param tyre
     * @param engine
     */
    public Car(String carBody, String tyre, String engine) {
        this.carBody = carBody;
        this.tyre = tyre;
        this.engine = engine;
    }

    /**
     * 假如我们需要再添加车内装饰品,即在原来构造器基础上再重载一个构造器
     *
     * @param carBody
     * @param tyre
     * @param engine
     * @param aimingCircle
     * @param decoration
     */
    public Car(String carBody, String tyre, String engine, String aimingCircle, String decoration) {
        this.carBody = carBody;
        this.tyre = tyre;
        this.engine = engine;
        this.aimingCircle = aimingCircle;
        this.decoration = decoration;
    }
}

Во-вторых, построение режима JavaBeans

Предоставьте конструктор без параметров и предоставьте пользователям некоторые общедоступные методы для самостоятельной установки свойств объекта.По сравнению с первым методом этот метод, кажется, повышает гибкость, и пользователи могут свободно устанавливать свойства в соответствии со своими потребностями. Но у этого подхода есть свои серьезные недостатки: Поскольку процесс построения разделен на несколько вызовов, JavaBean может находиться в несогласованном состоянии во время построения. Класс не может гарантировать согласованность, просто оценивая допустимость аргументов конструктора. Еще одним серьезным недостатком является то, что шаблон JavaBeans не позволяет сделать классы неизменяемыми. , что требует дополнительных операций для обеспечения его потокобезопасности.

public class Car {
    /**
     * 必需属性
     */
    private String carBody;//车身
    private String tyre;//轮胎
    private String engine;//发动机
    private String aimingCircle;//方向盘
    /**
     * 可选属性
     */
    private String decoration;//车内装饰品

    public void setCarBody(String carBody) {
        this.carBody = carBody;
    }

    public void setTyre(String tyre) {
        this.tyre = tyre;
    }

    public void setEngine(String engine) {
        this.engine = engine;
    }

    public void setAimingCircle(String aimingCircle) {
        this.aimingCircle = aimingCircle;
    }

    public void setDecoration(String decoration) {
        this.decoration = decoration;
    }
}

Так есть ли способ решить вышеуказанную проблему? Конечно есть~ Теперь наш главный герой будет играть в ----- режиме Строителя

3. Режим строителя

Наши пользователи, как правило, не выполняют утомительный процесс сборки автомобиля самостоятельно, а доверяют его производителю автомобиля. Производитель автомобиля завершит процесс сборки автомобиля.Строитель здесь является производителем автомобиля.Создание нашего автомобиля будет завершено им.

public final class Car {
    /**
     * 必需属性
     */
    final String carBody;//车身
    final String tyre;//轮胎
    final String engine;//发动机
    final String aimingCircle;//方向盘
    final String safetyBelt;//安全带
    /**
     * 可选属性
     */
    final String decoration;//车内装饰品
    /**
     * car 的构造器 持有 Builder,将builder制造的组件赋值给 car 完成构建
     * @param builder
     */
    public Car(Builder builder) {
        this.carBody = builder.carBody;
        this.tyre = builder.tyre;
        this.engine = builder.engine;
        this.aimingCircle = builder.aimingCircle;
        this.decoration = builder.decoration;
        this.safetyBelt = builder.safetyBelt;
    }
    ...省略一些get方法
    public static final class Builder {
        String carBody;
        String tyre;
        String engine;
        String aimingCircle;
        String decoration;
        String safetyBelt;

        public Builder() {
            this.carBody = "宝马";
            this.tyre = "宝马";
            this.engine = "宝马";
            this.aimingCircle = "宝马";
            this.decoration = "宝马";
        }
         /**
         * 实际属性配置方法
         * @param carBody
         * @return
         */
        public Builder carBody(String carBody) {
            this.carBody = carBody;
            return this;
        }

        public Builder tyre(String tyre) {
            this.tyre = tyre;
            return this;
        }
        public Builder safetyBelt(String safetyBelt) {
          if (safetyBelt == null) throw new NullPointerException("没系安全带,你开个毛车啊");
            this.safetyBelt = safetyBelt;
            return this;
        }
        public Builder engine(String engine) {
            this.engine = engine;
            return this;
        }

        public Builder aimingCircle(String aimingCircle) {
            this.aimingCircle = aimingCircle;
            return this;
        }

        public Builder decoration(String decoration) {
            this.decoration = decoration;
            return this;
        }
        /**
         * 最后创造出实体car
         * @return
         */
        public Car build() {
            return new Car(this);
        }
    }
}

Теперь, когда наш класс написан, давайте выполним код при его вызове:

 Car car = new Car.Builder()
                .build();

Сломайте точку, отладьте и запустите, чтобы увидеть эффект:

car默认构造.png

Как видите, наша машина по умолчанию уже изготовлена, и все детали по умолчанию - "BMW", Диди ~ Слишком поздно объяснять, садись в машину. Если мы не используем значение по умолчанию и нам нужно настроить его самостоятельно, это очень просто. Просто получите объект Builder, поочередно вызовите указанный метод и, наконец, вызовите build, чтобы вернуть автомобиль. Пример кода ниже:

        //配置car的车身为 奔驰
        Car car = new Car.Builder()
                .carBody("奔驰")
                .build();

Все еще отлаживайте, чтобы увидеть, успешно ли настроен автомобиль ~

car 定制.png

Эй, волшебная кастомизация автомобиля прошла успешно, нечего сказать, продолжай ездить~~

Мы также можем добавить некоторые из наших ограничений на свойства конфигурации к ряду методов сборки в классе Builder. Например, мы добавляем атрибут ремня безопасности в автомобиль и добавляем следующий код в соответствующий метод Builder:

 public Builder safetyBelt(String safetyBelt) {
            if (safetyBelt == null) throw new NullPointerException("没系安全带,你开个毛车啊");
            this.safetyBelt = safetyBelt;
            return this;
        }

Затем при вызове:

     //配置car的车身为 奔驰
     Car car = new Car.Builder()
                      .carBody("奔驰")
                      .safetyBelt(null)
                      .build();

Мы добавили нулевое суждение к атрибуту ремня безопасности конфигурации, после настройки нулевого атрибута будет выдано исключение. Хорошо, машина собрана, давайте посмотрим на машину~

Все еще отлаживай и уезжай~

car 属性配置判断.png

родился~~~ не случайно, перевернулся. . .

Наконец, клиент сказал, что впечатление от автомобиля, который вы сделали, не очень хорошее, и вы хотите переделать автомобиль, но можно ли переделывать автомобиль после того, как он покинул завод? Так как же это сделать? Не волнуйтесь, говорить легко, мы можем это сделать, если снова получим объект Builder. Давайте добавим следующую структуру в Builder, а затем сравним структуру Car, чтобы увидеть, что в ней особенного:

       /**
         * 回厂重造
         * @param car
         */
        public Builder(Car car) {
            this.carBody = car.carBody;
            this.safetyBelt = car.safetyBelt;
            this.decoration = car.decoration;
            this.tyre = car.tyre;
            this.aimingCircle = car.aimingCircle;
            this.engine = car.engine;
        }
  /**
     * car 的构造器 持有 Builder,将 builder 制造的组件赋值给 car 完成构建
     *
     * @param builder
     */
    public Car(Builder builder) {
        this.carBody = builder.carBody;
        this.tyre = builder.tyre;
        this.engine = builder.engine;
        this.aimingCircle = builder.aimingCircle;
        this.decoration = builder.decoration;
        this.safetyBelt = builder.safetyBelt;
    }

Да, кажется, симметричные отношения, да. Мы предлагаем соответствующие конструкции. Вызов возвращает соответствующий объект, и возвращаемый эффект может быть достигнут. Добавить метод в автомобиль

 /**
     * 重新拿回builder 去改造car
     * @return
     */
    public Builder newBuilder() {
        return new Builder(this);
    }

Теперь давайте попробуем, смогу ли я вернуться на завод и восстановить его? Чтобы переделать оригинальный автомобиль BMW в автомобиль Mercedes-Benz, вызовите код:

Car newCar = car.newBuilder()
                .carBody("奔驰")
                .safetyBelt("奔驰")
                .tyre("奔驰")
                .aimingCircle("奔驰")
                .decoration("奔驰")
                .engine("奔驰")
                .build();

Хорошо, машина переделана, давайте продолжим отладку и попробуем переделать, если нас что-то не устраивает

car 改造.png
Ха-ха, его переделали, и клиент вполне доволен~~

Проанализируем, как он устроен.

  • Создаем новый статический внутренний класс Builder, то есть производителя автомобиля, ему передаем в производство нашу машину, а в него копируем все атрибуты требуемые машине
  • Определяет пустую конструкцию Builder, которая инициализирует car значениями по умолчанию. Это делается с целью инициализации конструкции, не определяйте свойства в частности, а напрямую используйте значения по умолчанию. Определить структуру Builder, передать Car и выполнить операцию присвоения свойства Car соответствующему свойству Builder в структуре, цель — перестроить билдер для рефакторинга
  • Определите ряд методов для инициализации свойств, эти методы аналогичны методам конструкции режима Javabeans, разница состоит в том, что возвращаемое значение имеет построитель типа, чтобы облегчить цепочные вызовы. Наконец, определите способ возвращения объекта автомобиля объекта, конструктор автомобиля удерживает строитель, и, наконец, назначить компоненты, сделанные построителем для автомобиля, чтобы завершить строительство

На этом наше знакомство с режимом Builder закончилось. Вот лишь вариант режима Builder, то есть режим, который широко используется в android. Преимущества и недостатки кратко изложены ниже:

преимущество:

  • Развязка, четкая логика. Единая конструкция передается классу Builder, а класс Car не должен заботиться о внутренних деталях реализации, а обращает внимание только на результаты.

  • Цепные вызовы являются гибкими и легко расширяемыми. По сравнению с методом-конструктором в методе 1 значительно улучшена гибкость настройки свойств объекта, а поддержка цепных вызовов делает логику намного понятнее, и когда нам нужно расширить, нам нужно только добавить соответствующие расширенные свойства, что очень удобно.

недостаток:

  • Если мы настаиваем на недостатке, нам нужно написать больше кода на ранней стадии, и каждая сборка должна сначала создать соответствующий объект Builder. Но эти накладные расходы практически ничтожны. Написание большего количества кода на ранней стадии необходимо для лучшего расширения в будущем. Разве это не то, что должны учитывать хорошие программисты?

Решение:Не ленивый программист - не хороший программист.Для вышеперечисленных недостатков IDEA серии ide есть соответствующие плагиныInnerBuilderВы можете автоматически сгенерировать код, связанный с билдером, установить свой собственный Google и использовать только клавиши Alt + Insert в классе сущности, будет кнопка сборки для обеспечения генерации кода.

сцены, которые будут использоватьсяКак правило, при наличии более 4 атрибутов класса рекомендуется использовать этот режим. Также, если есть неопределенность в атрибутах класса, это может быть использовано при добавлении новых атрибутов в будущем, что удобно для расширения.

В-четвертых, применение режима Строителя в андроиде

1. Широко используется в okHttp

В начале мы также упомянули, что паттерн Builder можно увидеть повсюду в okHttp. Например, OkHttpClient, Request, Response и другие классы используют этот режим. Ниже с Класс Request кратко объяснен в качестве примера. Для получения подробной информации вы можете скачать исходный код, чтобы просмотреть его. Это в основном не проблема в соответствии с приведенной выше процедурой.

Запрос имеет 6 свойств.Он содержит Builder в соответствии с рутинным методом строительства.В процессе строительства компоненты, сделанные строителем, назначаются Request для завершения строительства, а newBuilder предоставляется для восстановления Builder и возврата на завод для реконструкция:

final HttpUrl url;
  final String method;
  final Headers headers;
  final RequestBody body;
  final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }

  public Builder newBuilder() {
    return new Builder(this);
  }

Builder имеет две конструкции, первая пустая конструкция инициализирует два значения по умолчанию. Второй конструктор содержит запрос на перестроение Builder обратно в фабрику для перестроения.

public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

    Builder(Request request) {
      this.url = request.url;
      this.method = request.method;
      this.body = request.body;
      this.tag = request.tag;
      this.headers = request.headers.newBuilder();
    }

Остальные — это некоторые методы инициализации свойств, а возвращаемое значение — Builder для упрощения цепочки. Вот метод, пожалуйста, проверьте исходный код для получения подробной информации и, наконец, вызовите метод build(), чтобы инициализировать запрос и передать в Builder для завершения сборки.

  public Builder url(HttpUrl url) {
      if (url == null) throw new NullPointerException("url == null");
      this.url = url;
      return this;
    }
...此处省略部分方法
  public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
2. Используйте AlertDialog в исходном коде Android

Режим Builder, используемый в AlertDialog, также является такой рутиной, я считаю, что если вы разберетесь с ним раньше, вам будет легко увидеть исходный код самостоятельно. Из-за недостатка места он здесь не расширяется.

Вывод: я лично считаю, что изучать шаблоны проектирования совершенно необходимо.Иногда нам нужно читать исходный код распространенных фреймворков с открытым исходным кодом, который может не только изучить некоторые дизайнерские идеи, но и облегчить ежедневное использование. Увидел это в блоге"То, что мы больше не делаем колеса, не означает, что нам не нужно знать, как делать колеса и как сделать их лучше!", а шаблон проектирования является краеугольным камнем понимания исходного кода фреймворка, потому что часто превосходные фреймворки включают множество шаблонов проектирования. Я буду продолжать обновлять и изучать новые шаблоны проектирования в будущем, а затем подводить итоги~

Отказ от ответственности: Вышеизложенное является лишь моим скромным мнением.Если есть какие-либо недостатки, я надеюсь указать

Больше оригинальных статей будет размещено на официальном аккаунте как можно скорее, пожалуйста, отсканируйте код, чтобы следоватьЧжан Шаолинь

张少林同学.jpg