Я видел историю нескольких проектов библиотек классов С # и Java на GitHub и CodePlex, и я вижу тенденцию перехода к фабричным классам в отличие от непосредственного создания объектов.
Почему я должен широко использовать фабричные классы? У меня есть довольно хорошая библиотека, где объекты создаются старомодным способом - путем вызова открытых конструкторов классов. В последних коммитах авторы быстро изменили все открытые конструкторы тысяч классов на внутренние, а также создали один огромный фабричный класс с тысячами CreateXXX
статических методов, которые просто возвращают новые объекты, вызывая внутренние конструкторы классов. API внешнего проекта сломан, молодец.
Почему такое изменение будет полезным? Какой смысл рефакторинга таким образом? Каковы преимущества замены вызовов конструкторов открытых классов статическими вызовами методов фабрики?
Когда я должен использовать публичные конструкторы, а когда я должен использовать фабрики?
Ответы:
Фабричные классы часто реализуются, потому что они позволяют проекту более точно следовать принципам SOLID . В частности, принципы разделения интерфейсов и инверсии зависимостей.
Заводы и интерфейсы обеспечивают гораздо большую гибкость в долгосрочной перспективе. Это позволяет создать более разобщенный и, следовательно, более проверяемый дизайн. Вот неполный список причин, по которым вы можете пойти по этому пути:
Рассмотрим эту ситуацию.
Сборка А (-> означает, зависит от):
Я хочу переместить класс B в сборку B, которая зависит от сборки A. Благодаря этим конкретным зависимостям мне нужно переместить большую часть всей моей иерархии классов. Если я использую интерфейсы, я могу избежать большей части боли.
Сборка А:
Теперь я могу переместить класс B в сборку B безо всякой боли. Это все еще зависит от интерфейсов в сборке А.
Использование контейнера IoC для разрешения ваших зависимостей обеспечивает еще большую гибкость. Нет необходимости обновлять каждый вызов конструктора всякий раз, когда вы изменяете зависимости класса.
Следование принципу сегрегации интерфейса и принципу инверсии зависимостей позволяет нам создавать очень гибкие, не связанные между собой приложения. После того, как вы поработали над одним из этих типов приложений, вам больше никогда не захочется возвращаться к использованию
new
ключевого слова.источник
Как сказал whatsisname , я считаю, что это случай разработки программного обеспечения для культа грузов . Фабрики, особенно абстрактные, используются только тогда, когда ваш модуль создает несколько экземпляров класса, и вы хотите дать пользователю этого модуля возможность указать, какой тип создавать. Это требование на самом деле довольно редко, потому что в большинстве случаев вам нужен только один экземпляр, и вы можете просто передать этот экземпляр напрямую, а не создавать явную фабрику.
Дело в том, что фабрики (и синглеты) чрезвычайно легко внедрить, и поэтому люди часто их используют, даже там, где они не нужны. Поэтому, когда программист думает: «Какие шаблоны проектирования я должен использовать в этом коде?» Фабрика - первое, что приходит ему в голову.
Многие фабрики создаются потому, что «возможно, однажды мне нужно будет создать эти классы по-другому». Что является явным нарушением ЯГНИ .
И фабрики устаревают, когда вы вводите IoC-фреймворк, потому что IoC - это просто некая фабрика. И многие фреймворки IoC способны создавать реализации конкретных фабрик.
Кроме того, нет шаблона проектирования, в котором говорится о создании огромных статических классов с
CreateXXX
методами, которые вызывают только конструкторы. И это особенно не называют Фабрикой (ни Абстрактной Фабрикой).источник
Модельный паттерн «Фабрика» проистекает из почти догматического убеждения программистов на языках «C-стиля» (C / C ++, C #, Java), что использование «нового» ключевого слова плохо, и его следует избегать любой ценой (или по крайней мере). наименее централизованный). Это, в свою очередь, исходит из ультра-строгой интерпретации принципа единой ответственности («S» SOLID), а также принципа обращения зависимостей («D»). Проще говоря, SRP говорит, что в идеале у объекта кода должна быть одна «причина для изменения» и только одна; эта «причина изменения» является центральной целью этого объекта, его «ответственностью» в кодовой базе и всем остальным, что требует изменения кода, не должно требовать открытия этого файла класса. DIP еще проще; объект кода никогда не должен зависеть от другого конкретного объекта,
Например, используя «new» и открытый конструктор, вы связываете вызывающий код с конкретным методом конструирования конкретного конкретного класса. Теперь ваш код должен знать, что класс MyFooObject существует и имеет конструктор, который принимает строку и int. Если этому конструктору когда-либо понадобится дополнительная информация, все виды использования конструктора должны быть обновлены, чтобы передать эту информацию, включая ту, которую вы пишете сейчас, и поэтому они должны иметь что-то допустимое для передачи, и поэтому они должны либо иметь это или быть изменено, чтобы получить это (добавление большего количества обязанностей к потребляющим объектам). Кроме того, если MyFooObject когда-либо будет заменен в кодовой базе на BetterFooObject, все виды использования старого класса должны измениться, чтобы создать новый объект вместо старого.
Таким образом, вместо этого все потребители MyFooObject должны напрямую зависеть от «IFooObject», который определяет поведение реализации классов, включая MyFooObject. Теперь потребители IFooObjects не могут просто создать IFooObject (не зная, что конкретный конкретный класс является IFooObject, который им не нужен), поэтому вместо этого им должен быть предоставлен экземпляр класса или метода, реализующего IFooObject. извне, другим объектом, который несет ответственность за знание того, как создать правильный IFooObject для обстоятельств, которые, на нашем языке, обычно называют Factory.
Теперь вот где теория встречается с реальностью; объект никогда не может быть закрыт для всех типов изменений все время. Например, IFooObject теперь является дополнительным объектом кода в базе кода, который должен меняться всякий раз, когда изменяется интерфейс, требуемый потребителями или реализациями IFooObjects. Это вводит новый уровень сложности, связанный с изменением способа взаимодействия объектов друг с другом в этой абстракции. Кроме того, потребители все равно должны будут измениться, и более глубоко, если сам интерфейс будет заменен на новый.
Хороший кодер знает, как сбалансировать YAGNI («Вам это не нужно») с SOLID, анализируя дизайн и находя места, которые с большой вероятностью могут измениться определенным образом, и реорганизуя их, чтобы они были более терпимыми к что тип изменения, потому что в этом случае «вы это собираетесь это нужно».
источник
Foo
указать, что общедоступный конструктор должен использоваться для созданияFoo
экземпляров или создания других типов в том же пакете / сборке , но не должен использоваться для создания типы получены в другом месте. Я не знаю какой-либо особенно убедительной причины, по которой язык / среда не могли определить отдельные конструкторы для использования вnew
выражениях, в отличие от вызова из конструкторов подтипов, но я не знаю ни одного языка, который бы делал это различие.Конструкторы хороши, когда они содержат короткий, простой код.
Когда инициализация становится более чем назначением нескольких переменных для полей, имеет смысл использовать фабрику. Вот некоторые из преимуществ:
Длинный, сложный код имеет больше смысла в выделенном классе (фабрике). Если тот же код помещен в конструктор, который вызывает несколько статических методов, это загрязнит основной класс.
В некоторых языках и в некоторых случаях создание исключений в конструкторах - это действительно плохая идея , так как это может привести к ошибкам.
Когда вы вызываете конструктор, вы, вызывающая сторона, должны знать точный тип экземпляра, который вы хотите создать. Это не всегда так (как
Feeder
, мне просто нужно построитьAnimal
, чтобы кормить его; мне все равно, если этоDog
илиCat
).источник
Feeder
может не использовать ни один, и вместо этого вызывать свой методKennel
объектаgetHungryAnimal
.Builder Pattern
. Не так ли?Если вы работаете с интерфейсами, вы можете оставаться независимым от фактической реализации. Фабрика может быть сконфигурирована (через свойства, параметры или какой-либо другой метод) для реализации одной из нескольких различных реализаций.
Один простой пример: вы хотите общаться с устройством, но вы не знаете, будет ли оно через Ethernet, COM или USB. Вы определяете один интерфейс и 3 реализации. Во время выполнения вы можете выбрать, какой метод вы хотите, и фабрика даст вам соответствующую реализацию.
Используйте это часто ...
источник
Это признак ограничения в модульных системах Java / C #.
В принципе, нет причин, по которым вы не сможете поменять одну реализацию класса на другую с одинаковыми сигнатурами конструктора и метода. Там являются языки , которые позволяют это. Однако Java и C # настаивают на том, чтобы у каждого класса был уникальный идентификатор (полное имя), а клиентский код заканчивался жестко закодированной зависимостью от него.
Вы можете рода обойти это возились с файловой системой и опцией компилятора , так что
com.example.Foo
карты к другому файлу, но это удивительно и неинтуитивная. Даже если вы это сделаете, ваш код все еще привязан только к одной реализации класса. Т.е. если вы пишете класс,Foo
который зависит от классаMySet
, вы можете выбрать реализациюMySet
во время компиляции, но вы по-прежнему не можете создать экземплярFoo
с использованием двух разных реализацийMySet
.Это неудачное дизайнерское решение вынуждает людей без необходимости использовать
interface
s, чтобы защитить свой код в будущем от вероятности того, что им позже понадобится другая реализация чего-либо, или для облегчения модульного тестирования. Это не всегда возможно; если у вас есть какие-либо методы, которые проверяют закрытые поля двух экземпляров класса, вы не сможете реализовать их в интерфейсе. Вот почему, например, вы не видитеunion
вSet
интерфейсе Java . Тем не менее, вне числовых типов и коллекций, двоичные методы не распространены, так что вы обычно можете сойти с рук.Конечно, если вы звоните, у
new Foo(...)
вас все еще есть зависимость от класса , поэтому вам нужна фабрика, если вы хотите, чтобы класс мог напрямую создавать экземпляр интерфейса. Однако, как правило, лучше принять экземпляр в конструкторе и позволить кому-то другому решить, какую реализацию использовать.Вам решать, стоит ли раздувать вашу кодовую базу с помощью интерфейсов и фабрик. С одной стороны, если рассматриваемый класс является внутренним для вашей кодовой базы, рефакторинг кода с тем, чтобы в будущем он использовал другой класс или интерфейс, был тривиальным; Вы можете вызвать YAGNI и рефакторинг позже, если возникнет такая ситуация. Но если класс является частью открытого API библиотеки, которую вы опубликовали, у вас нет возможности исправить код клиента. Если вы не используете,
interface
а позже вам нужно несколько реализаций, вы застрянете между молотом и наковальней.источник
new
просто были бы синтаксическим сахаром для вызова специально названного статического метода (который генерировался бы автоматически, если бы у типа был открытый конструктор) "но явно не включал метод). ИМХО, если код просто хочет реализовать скучную вещь по умолчанию, котораяList
должна быть реализована, интерфейс должен иметь возможность дать ему такую информацию без необходимости знать клиенту какую-либо конкретную реализацию (напримерArrayList
).По моему мнению, они просто используют простую фабрику, которая не является правильным шаблоном проектирования и не должна быть перепутана с абстрактной фабрикой или фабричным методом.
И так как они создали «огромный класс фабрики с тысячами статических методов CreateXXX», это звучит как анти-паттерн (возможно, Бог-класс?).
Я думаю, что в некоторых случаях могут быть полезны методы Simple Factory и статические создатели (для которых не требуется внешний класс). Например, когда построение объекта требует различных шагов, таких как создание экземпляров других объектов (например, создание композиции).
Я бы даже не назвал это Фабрикой, а просто набором методов, инкапсулированных в некоторый случайный класс с суффиксом «Фабрика».
источник
int x
иIFooService fooService
. Вы не хотите распространятьсяfooService
везде, поэтому вы создаете фабрику с помощью методаCreate(int x)
и внедряете сервис внутри фабрики.IFactory
вместо того, чтобы ходить, вы должны проходить вездеIFooService
.Как пользователь библиотеки, если в библиотеке есть фабричные методы, вы должны их использовать. Можно предположить, что фабричный метод дает автору библиотеки возможность вносить определенные изменения, не влияя на ваш код. Например, они могут возвращать экземпляр некоторого подкласса в методе фабрики, который не будет работать с простым конструктором.
Как создатель библиотеки вы должны использовать фабричные методы, если хотите использовать эту гибкость самостоятельно.
В описываемом вами случае создается впечатление, что замена конструкторов фабричными методами была просто бессмысленной. Это было, конечно, боль для всех участников; библиотека не должна ничего удалять из своего API без веской причины. Поэтому, если бы я добавил фабричные методы, я бы оставил существующие конструкторы доступными, возможно, не рекомендуется, пока фабричный метод больше не просто вызывает этот конструктор, а код, использующий простой конструктор, работает не так хорошо, как следовало бы. Ваше впечатление вполне может быть правильным.
источник
Это кажется устаревшим в эпоху Scala и функционального программирования. Прочная основа функций заменяет газиллион классов.
Также следует отметить, что Java double
{{
больше не работает при использовании фабрики, т.е.Который может позволить вам создать анонимный класс и настроить его.
В дилемме пространства-времени фактор времени теперь сильно сократился благодаря быстрым процессорам, что функциональное программирование, позволяющее уменьшить пространство, то есть размер кода, стало практичным.
источник