Сергей Осипчук попросил меня прокомментировать свою статью про зло, заключенное в публичных конструкторах. Я решил это сделать отдельной статьей потому что кратко вряд ли получится, да и многим, думаю будет интересно.
Итак, Сергей говорит о том, что публичные конструкторы классов побуждают программиста создавать непосредственно объекты классов с помощью явно заданных конструкторов. От этого теряется гибкость, потому что если объект создается с помощью явно заданного конкретного имени класса, то вы не можете параметризовать этот вызов в зависимости от условий (например, создать кнопку не типа Button, а типа какого-то его наследника).
Смысл понятен, однако с выводом я кардинально не согласен! Отвечу по-порядку.
Сначала маленькая фактическая некорректность. Цитата:
Однако, для конструкторов во ВСЕХ языках у нас нет возможности сделать их виртуальными.
Отчего же... Та же Delphi умеет виртуальные конструкторы, причем на них там построена вся система сохранения и загрузки форм: конструктор базового класса всех компонентов TComponent как раз виртуальный, и это сильно упрощает генерирование их в рантайме в зависимости от того, что написано в файле формы.
type
SomeClass:TClass;
...
SomeClass:=TButton;
Button:=SomeClass.Create(Owner); {Вызывается конструктор TButton.Create}
Причем, Delphi - язык со статической типизацией. А если взять динамические языки (на ум приходят Python и Smalltalk), то там это в порядке вещей. В Python вообще любой метод (и конструктор тоже) вызывается с поздним связыванием, то есть, фактически является виртуальным. Там еще и сам реальный класс можно получить, составив его имя из произвольной строки :-)
Короче говоря, в любом языке, где классы - это "жители первого класса" (то есть они реально существуют в памяти, и с ними можно работать, как с переменными), виртуальные конструкторы не только возможны, но и вовсю используются.
Но основное мое несогласие не в этом. Да, виртуальные конструкторы - это удобный способ параметризовать процесс создания объектов, но если они не поддерживаются синтаксически, то они имитируются фабричными методами.
С чем действительно я не согласен, так это с необходимостью создавать для каждого конструктора объекта фабричный метод. В классической "Design Patterns" описаны два фабричных шаблона: "Abstract Factory" и "Factory Method".
-
Abstract Factory определяет семантику создания целой группы классов. Она описывает, что создается (кнопка), но не как создается (кнопка с картинкой, кнопка с рамкой).
-
Factory Method используется в фреймворках, для создания объектов для его пользователей. Там это нужно, чтобы фреймворк можно было легко расширять, дополняя собственными наследниками его объектов.
Но вот в других случаях, когда вам надо просто создать объект класса, вашего собственного класса, который вообще и не видно будет снаружи вашего кода, тогда фабрика - это просто излишнее усложнение. Сергей пишет, что отказ от создания явными публичными конструкторами заставляет всех разработчиков думать в терминах фабрик. Но это-то как раз и плохо. Потому что это - лишняя абстракция, которую мозгу надо разрешать каждый раз, когда он с ней сталкивается.
Часто универсальность вводят на всякий случай, думая, что если не понадобится - так и ладно. Это не верно, потому что любая гибкость - не бесплатна, она выливается в сложность обслуживания кода и в сложность его использования. Я в этих случаях люблю говорить подсмотренную где-то фразу: "In order for code to be reusable, it has to be usable first".
Почти нереально угадать, какая где гибкость понадобится в развитии системы в некотором отдаленном будущем. И когда систему надо будет менять, то гораздо лучше вносить уже известную необходимую гибкость в простой код, чем в сложный, в котором тонна универсальностей, которые так никогда и не потребуются. В этом смысле код, который содержит только то, что нужно, масштабируется гораздо проще и дешевле.
Например, Сергей пишет, что если мы будем создавать объекты явно, то при необходимости заменить их созданием наследников, нам придется менять эти вызовы по всему коду. И тогда он рекомендует создать фабричную структуру, в которой мы можем в одном месте подменить один класс на другой. А если нам нужно заменить только часть кнопок? Вот тут эта самая фабрика оказывается полностью бесполезной: все равно придется прошерстить весь код и поменять нужные вызовы на другие. Да и, честно говоря, не так уж это и трудно. Да, муторно, но не трудно :-)
Комментарии: 3
ага, принцип KISS надо помнить всегда, а не только паттерны
В первую очередь, спасибо за первый отзыв.
То что я был категоричен в написанном, совсем не значит что я такой же в жизни.
Я пользуюсь двумя правилами в жизни:
1. В жизни каждое правило имеет исключения.
2. Первое правило тоже имеет исключения (т.е. есть правила без исключений)
Можно придумать много частных случаев, когда живые паблик конструкторы принесут пользу, но это должны быть частные случаи, а не правило.
Все мы знаем кучи бест практисес и регулярно по разным причинам им уступаем. Я предложил ещё одну из них.
На счёт фабрики и необходимости заменить только часть порождаемых объектов - у меня была фабрика которую просто можно было конфигурировать, т.е. заменять не всё, а часть объектов, более того вносить какую-то логику в выбор инстанциируемого объекта. Она не сложная но ещё сыровата для официального релиза, надо доработать и она ответит на этот вопрос.
рыскал в поисках статей о виртуальных конструкторах, набрел на эту :). Отличный ответ, так же как и исходный пост.
Хотя при использование инструментов рефакторинга, многие проблемы уже не будут кажутся такими такими нудными.