Конструкторы против фабричных методов

StackOverflow https://stackoverflow.com/questions/628950

  •  07-07-2019
  •  | 
  •  

Вопрос

Какой способ инициализации является предпочтительным при моделировании классов:

  1. Конструкторы или
  2. Фабричные методы

И каковы будут соображения по поводу использования любого из них?

В определенных ситуациях я предпочитаю иметь фабричный метод, который возвращает значение null, если объект не может быть создан.Это делает код аккуратным.Я могу просто проверить, не является ли возвращаемое значение нулевым, прежде чем предпринимать альтернативные действия, в отличие от выдачи исключения из конструктора.(лично я не люблю исключений)

Скажем, у меня есть конструктор класса, который ожидает значение идентификатора.Конструктор использует это значение для заполнения класса из базы данных.В случае, если запись с указанным идентификатором не существует, конструктор создает исключение RecordNotFoundException.В этом случае мне придется заключить конструкцию всех таких классов в блок try..catch.

В отличие от этого у меня может быть статический фабричный метод для этих классов, который будет возвращать значение null, если запись не найдена.

Какой подход в данном случае лучше: конструктор или фабричный метод?

Это было полезно?

Решение

Со страницы 108 Шаблоны проектирования:Элементы многоразового объектно-ориентированного программного обеспечения Гаммы, Хелма, Джонсона и Влиссидса.

Используйте шаблон Factory Method, когда

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

Другие советы

Спросите себя, кто они и зачем они у нас. Они оба созданы для создания экземпляра объекта.

ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside

Разницы пока нет. Теперь представьте, что у нас есть различные типы школ, и мы хотим переключиться с использования ElementarySchool на HighSchool (который получен из ElementarySchool или реализует тот же интерфейс ISchool, что и ElementarySchool). Изменение кода будет следующим:

HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside

В случае интерфейса у нас будет:

ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside

Теперь, если у вас есть этот код в нескольких местах, вы можете увидеть, что использование фабричного метода может быть довольно дешевым, потому что, как только вы измените фабричный метод, вы закончите (если мы используем второй пример с интерфейсами).

И это главное отличие и преимущество. Когда вы начинаете работать со сложными иерархиями классов и хотите динамически создавать экземпляр класса из такой иерархии, вы получаете следующий код. Затем фабричные методы могут принимать параметр, который сообщает методу, какой конкретный экземпляр нужно создать. Допустим, у вас есть класс MyStudent, и вам нужно создать экземпляр соответствующего объекта ISchool, чтобы ваш ученик был членом этой школы.

ISchool school = SchoolFactory.ConstructForStudent(myStudent);

Теперь у вас есть одно место в вашем приложении, которое содержит бизнес-логику, которая определяет, какой объект ISchool создавать для различных объектов IStudent.

Итак, для простых классов (объектов-значений и т. д.) конструктор вполне подходит (вы не хотите чрезмерно улучшать приложение), но для сложных иерархий классов предпочтительным способом является фабричный метод.

Таким образом, вы следуете первому принципу дизайна из банды из четырех книг " Программируйте интерфейс, а не реализацию.

Вам нужно прочитать (если у вас есть доступ) Эффективная Java 2 Элемент 1. Рассмотрим статические фабричные методы вместо конструкторов .

Преимущества статических фабричных методов:

<Ол>
  • У них есть имена.
  • Они не обязаны создавать новый объект каждый раз, когда их вызывают.
  • Они могут возвращать объект любого подтипа своего возвращаемого типа.
  • Они уменьшают многословность создания экземпляров параметризованного типа.
  • Недостатки статических фабричных методов:

    <Ол>
  • При предоставлении только статических фабричных методов классы без открытых или защищенных конструкторов не могут быть разделены на подклассы.
  • Их трудно отличить от других статических методов
  • По умолчанию конструкторы должны быть предпочтительнее, потому что их проще понимать и писать. Однако, если вам необходимо отделить конструктивные особенности объекта от его семантического значения, понимаемого клиентским кодом, лучше использовать фабрики.

    Разница между конструкторами и фабриками аналогична, скажем, переменной и указателю на переменную. Есть другой уровень косвенности, который является недостатком; но есть и другой уровень гибкости, что является преимуществом. Поэтому, делая выбор, советуем вам провести анализ затрат и выгод.

    Цитата из «Эффективной Java», 2-е изд., пункт 1: Рассмотрим статические фабричные методы вместо конструкторов, с. 5:

    " Обратите внимание, что статический метод фабрики не совпадает с шаблоном фабричного метода из Design Patterns [Gamma95, с. 107]. Статический фабричный метод, описанный в этот элемент не имеет прямого эквивалента в шаблонах проектирования. "

    Используйте фабрику только тогда, когда вам нужен дополнительный контроль при создании объекта, способом, который нельзя сделать с помощью конструкторов.

    Фабрики имеют возможность кэширования, например.

    Другой способ использовать фабрики - это сценарий, в котором вы не знаете тип, который хотите построить. Часто вы видите этот тип использования в сценариях фабрики плагинов, где каждый плагин должен происходить из базового класса или реализовывать некоторый интерфейс. Фабрика создает экземпляры классов, которые являются производными от базового класса или реализуют интерфейс.

    Конкретный пример из приложения CAD / CAM.

    Путь резки будет сделан с помощью конструктора. Это серия линий и дуг, определяющих путь к разрезу. Хотя серии линий и дуг могут быть разными и иметь разные координаты, это легко обрабатывается путем передачи списка в конструктор.

    Форма будет создана с использованием фабрики. Потому что, хотя существует класс фигур, каждая фигура будет настроена по-разному, в зависимости от типа фигуры. Мы не знаем, какую форму мы будем инициализировать, пока пользователь не сделает выбор.

    В дополнение к "эффективная Java" (как упоминалось в другом ответе), еще одна классическая книга также предлагает:

    Предпочитайте статические фабричные методы (с именами, описывающими аргументы) перегруженным конструкторам.

    Например.не пиши

    Complex complex = new Complex(23.0);
    

    но вместо этого напиши

    Complex complex = Complex.fromRealNumber(23.0);
    

    Книга заходит так далеко, что предлагает создать Complex(float) частный конструктор, чтобы заставить пользователя вызвать статический фабричный метод.

    Скажем, у меня есть конструктор класса, который ожидает значение идентификатора.Конструктор использует это значение для заполнения класса из базы данных.

    Этот процесс определенно должен находиться вне конструктора.

    1. Конструктор не должен иметь доступ к базе данных.

    2. Задача и смысл создания конструктора состоит в том, чтобы инициализировать элементы данных и чтобы установить инвариант класса используя значения, переданные в конструктор.

    3. Для всего остального лучше использовать статический фабричный метод или в более сложных случаях отдельный фабрика или строитель сорт.

    Некоторые рекомендации для конструкторов от Microsoft:

    Проделайте минимальную работу в конструкторе.Конструкторам не следует выполнять большую работу, кроме как фиксировать параметры конструктора.Стоимость любой другой обработки следует отложить до тех пор, пока она не потребуется.

    И

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

    Иногда вам нужно проверять / вычислять некоторые значения / условия при создании объекта. И если он может бросить исключение - конструктор очень плохой путь. Так что вам нужно сделать что-то вроде этого:

    var value = new Instance(1, 2).init()
    public function init() {
        try {
            doSome()
        }
        catch (e) {
            soAnotherSome()
        }
    }
    

    Где все дополнительные вычисления находятся в init (). Но только вы как разработчик действительно знаете об этом init (). И конечно, через несколько месяцев вы просто забудете об этом. Но если у вас есть фабрика - просто сделайте все, что вам нужно, в одном методе, скрыв этот init () от прямого вызова - так что никаких проблем. При таком подходе нет проблем с падением на создание и утечкой памяти.

    Кто-то рассказал вам о кешировании. Хорошо. Но вы также должны помнить о шаблоне Flyweight, который удобно использовать в Factory style.

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top