Комментарии 75
Один напишет абстрактную фабрику по созданию абстрактных фабрик для создания абстрактных молотков.
А другой напишет удобную расширяемую систему, с которой приятно работать.
Не забываем также про принципы KISS (Keep it short and simple) и YAGNI (You ain't gonna need it).
Вообще, эта позиция плохо сочетается с A Pattern Language Александера, который и заложил формат "проблема — контекст — решение". Даже если говорить о распознавании, то оно здесь применимо к "проблемной" части, а не к решению. Но на самом же деле, pattern у Александера — это не что-то, по чему распознают. Это образец, по которому решают проблему.
Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice
Другое дело, что люди часто забывают ознакомиться с проблемой-контекстом-последствиями, и сразу хватаются за решение.
В большей степени соглашусь, хотя в своей книге «Применение шаблонов проектирования: дополнительные штрихи» Влиссидес критикует определение Александера, называя его первым в числе основных заблуждений о шаблонах
Можно сколько угодно критиковать определение Александера, но отрицать тот факт, что GoF построен по тому же принципу, достаточно сложно.
Т.е. применимость шаблонов для распознавания заложена в них изначально, по их определению и способу появления на свет.
Во-первых, если что-то появилось в результате распознавания, это еще не значит, что это что-то само подходит для распознавания. Во-вторых, важный вопрос — какая часть паттерна подходит для распознавания: проблема или реализация?
Но во втором случае, применение паттернов направо и налево не влечет за собой каких-либо негативных последствий, в отличии от.
Влечет. Можно увидеть проблему, которой не было.
Он не зависит от языка.
Ага. Не зависит. Пока это язык вполне определенного класса. А потом вдруг бац — и сразу либо исчезает за ненужностью, либо трансформируется во что-то совсем другое.
В мире ООП одни подходы, описанные в статье, в функциональном мире — другие, а в js как получится =)
Вот к примеру в js любой callback — это реализация паттерна стратегия, хоть и выглядит совсем не так, но суть у них одна.
А система обработки событий — это ничто иное как chain of responsibility.
Перенося паттерны из одной идеологии в другую они видоизменяются и часто даже называются иначе.
Да не, дело скорее не в терминах.
Есть определение паттерна Фабрика (в чем суть проблемы, и что делать), и есть его реализация. Так вот, реализация очень сильно языковозависима. Вплоть до полного ее отсутствия за ненужностью, потому что в конкретном языке именно эта проблема не актуальна/тривиальна.
Вплоть до полного ее отсутствия за ненужностьюФабрика — это, фактически, улучшенная разновидность
new
, способная динамически заменять класс подклассом, возвращат относительно древним языком Smalltalk, но краем уха слышал, что там new
является методом объекта типа «класс» и может быть переопределён для конкретного класса. Это, наверное, и есть пример встроенной в язык фабрики.Погуглите скажем несколько заметок Mario Fusco, о том, во что вырождаются ряд паттернов GoF после появления в Java 8 лямбд.
Я в курсе. Просто это свежий пример того, как в давно существующем языке при добавлении в него новых возможностей, паттерны вдруг стали совершенно непохожи на то, какими были недавно.
Кстати, вырождаются не паттерны вообще, а некоторые (как правило, специфичные для ООП), потому что у функциональных языков есть свои типовые надязыковые конструкции, даже если их паттернами не зовут.
Вот к примеру в js любой callback — это реализация паттерна стратегияС объектно-ориентированной точки зрения, всё функциональное программирование — это применение паттерна «стратегия». А асинхронная обработка с continuation-passing всегда будет напоминать chain of responsibility.
Паттерны «стратегия», если простыми словами, — это когда аргумент функции (метода) принимает на вход «кусок кода» (функцию или имплементацию стратегии в ООП), который будет выполняться в работе данной функции.
Т.е. когда есть конкретная функция, которая вызывает другие конкретные функции — это не будет стратегией.
Функция, которая вызывает другую функцию для получения третьей (к примеру керринг) — это не будет стратегией.
А вот если в функцию передается другая функция — это уже будет стратегией.
> асинхронная обработка с continuation-passing
Это больше похоже на стратегию. Вызывается асинхронный метод, и в него передается callback функция, в которую будет выполнен возврат после выполнения асинхронного кода.
Chain of responsibility — это механизм, лежащий в основе pub/sub.
Dispatcher (на него подписываются и через него кидают event) внутри содержит хеш-таблицу сообщение -> список слушателей. Когда кто-то публикует сообщение, диспатчер берет список слушателей и поочередно выполняет их.
(но тут я не прав: «chain of responsibility по учебнику» будет заходить в каждую функцию и говорить «пришло такое сообщение», функция же должна будет посмотреть на это сообщение и решить выполнять над ним логику или нет)
Ну я бы не называл это костылями, по одной простой причине — это все-таки изначально некие над-языковые конструкции, которые описывают, как решать типовую проблему, имея в наличии некоторый набор инструментов. Ну т.е., грубо говоря, есть список чисел, нужно получить "сумму". На выходе для одного языка цикл, а для другого скажем какой-то вариант fold/reduce. И кто из них костыли?
В той же фабрике в общем-то нет ничего плохого — это просто механизм абстрагироваться от деталей создания чего-либо. Костылем она становится тогда и потому, когда в языке нет удобных механизмов для выражения этого в общем-то весьма простого действия.
Иными словами — "что-то совсем очевидное" не перестает быть решением типовой проблемы. Оно просто становится простым решением.
Но говорят о такой конструкции как о паттерне люди как правило тогда, когда реализация такой внеязыковой конструкции является не абсолютно тривиальной на некотором количестве значимых языков. Например, редко говорят о паттерне «цикл» или «вызов подпрограммы» (хотя во времена ассемблера это были самые что ни на есть настоящие паттерны).
Т.е. паттерн в широком смысле — полезная внеязыковая конструкция. Паттерн в узком смысле — полезная внеязыковая конструкция, не совсем тривиальная в реализации.
Второй важный момент: интерфейс работает как концепт. Фактически, он требует, чтобы класс имел определённый набор методов и свойств, при этом не имеет значения, где они реализованы: в текущем классе или базовых, ничего не знающих об интерфейсе. Такого эффекта невозможно достичь в языках, допускающих множественное наследование.
Всё остальное — действительно техническая ерунда. Важно только понимать, что интерфейсы дешёвые, они не замусоривают vtable и накладные расходы при их использовании ниже, чем при использовании полноценных классов. Особенно актуально это для C++, где виртуальное множественное наследование, используемое для эмуляции интерфейсов, приводит к раздуванию размеров объектов.
Звучит очень круто, хотелось бы узнать. В голову ничего не лезет, а запросы в гугл ничего не дали.
1. Разная стратегия использования при наследовании. Если вы хотите иметь возможность перезаписать метод в классе-потомке, то делаете его виртуальным и оставляете открытым. При этом любой вызов открытых виртуальных методов будет осуществляться через vtable.
Методы интерфейса же являются автоматически virtual и sealed. Если хотите перезаписать метод интерфейса, то просто наследуете его второй раз, при этом старые методы будут неявно объявлены во второй раз. Это приводит к тому, что при вызове метода компилятор генерит оптимальный код, а не лезет в vtable.
Единственный подвох такого подхода: если преобразовать класс к базовому, а потом к интерфейсу, то будет использоваться интерфейс от базового класса. Но на практике я с таким не сталкивался.
2. Огромная разница в потреблении памяти. В C++ создаётся по отдельному полю vtable для каждого из множественно наследуемых классов, дополнительная память тратится на разруливание виртуального наследования. В C# информация о множественном наследовании сидит в единственном vtable на каждый объект в виде ссылок на vtable интерфейсов.
Обычно такая двойная адресация не вызывает проблем, т.к. выполняется только один раз. Единственное исключение: generic-методы с interface constraint, где двойная адресация происходит при каждом вызове интерфейсного метода.
В C++ же такое сделать попросту невозможно, т.к. объекты при множественном наследовании не могут располагаться по одному адресу. Из-за этого для каждого из базовых классов и приходится заводить по vtable.
P.S. Подумал — а ведь если это развить, то на отдельную публикацию пойдёт, надо бы заняться. Реально куча интересных нюансов вылезает.
Самолет — это механизм.
Птица — это живое существо.
Самолет и Птица может летать (летающий объект).
В небе можно увидеть Летающие объекты.
Между тем вся страна электрифицирована, а коммунизм не наступил, хотя должен был, кажется.
Даже если принять как догму, что «все в отрасли должны это знать» (кому, кстати, должны-то?) — в отрасли всегда полно и новичков, и кустарей «на пару строк».
А когда все программисты до одного будут это знать — это будет признаком застоя и печали.
А на счет застоя, я не соглашусь. Когда все будут знать ответы на базовые вопросы, тогда произойдет качественный скачек развития всего ИТ мира.
Вот только чтобы знать ответы на все базовые вопросы, нужно очень много времени потратить. «Программирование — это не работа, это образ жизни».
Сколько раз вам за карьеру программиста пригодилось знание о том как устроенна хеш-таблица? То что редко используется — забывается, это естественный процесс. В нужный момент прочитать/вспомнить — не составит проблем.
Или привыкли вы за O(1) забирать из ArrayList в UI, а сейчас решили из середины LinkedList достать и снова UI подвис.
Или удалить из середины ArrayList запись, или, что еще хуже, прямо в UI перебрать все элементы в нем и удалить по фильтру что-то.
Продолжать примеры?
Хорошего разработчика от посредственного эти вещи и отличают — он знает, что использовать и как это использовать.
Массив от списка — ладно, вставка/удаление О(n) против О(1), индексация O(1) против O(n). Хотя неплохо бы ещё знать про константные составляющие, которые на современных процессорах в некоторых случаях делают выгодным использование массива вместо списка.
Устройство хеш-таблицы? Хм. Вообще-то их много разных. Достаточно знать, что при большом количестве коллизий производительность падает.
А общее отличие абстрактного класса от интерфейса зачем? Слишком зависит от языка. В С++ абстрактные классы и интерфейсы различаются только наличием non-pure methods. В C# интерфейсы — это отдельная языковая сущность. В Rust вообще абстрактных классов нет.
Если вы пишете код и вам кажется, что этот кусок похож на паттерн, тогда и оформите его как паттерн — код станет легче читать. Применять же паттерны не к месту, начитавшись умных книжек — только портить артихектуру.
Фабрики фабрик вообще идут параллельно и растут корнями из TDD.
У всего хорошего всего есть побочный эффект, если этим злоупотреблять. Витамины в большом количестве тоже причиняют вред. Понимание когда нужно использовать конкретный паттерн — это показатель зрелости специалиста.
Что дальше? Туториал по использованию колеса? Или добротная обзорная статья о всех плюсах и минусах использования огня?
Мыслите стереотипно. Я провел множество собеседований по Java, почти все люди с высшим образованием в сфере it. Но и имея 5-летний опыт работы люди могли не знать, как работает Хэш Таблица, не знать, что такое шаблон Адаптер и т.д.
И, кстати, мыслить стереотипно не так уж и плохо. Стереотипы — это шаблоны мышления. С их помощью очень легко делать выводы, не погружаясь глубоко в детали. Иногда выводы получаются неверными, но не в данном случае. Если человек не знает, как работает хэш-таблица, то про него никак нельзя сказать, что у него есть профильное образование. Возможно, у него есть какой-то документ, который позволяет ему вводить окружающих в заблуждение, но не более того. О действительном наличии того, что называется словом «образование», речь в данном случае не идёт.
Ответ на этот вопрос содержится в цитате самого Расмуса Лердорфа: «Я ненастоящий программист. Я компоную всякие штуки, пока это не начинает работать. Потом иду дальше. Настоящий программист скажет: „Ок, это работает, но тут утечка памяти, надо пофиксить“. А я просто перезапускаю Apache каждые 10 запросов»
Есть C# to Native от unity3d — теперь язык плохой? Есть даже JS to Native. Есть множество JVM для Java, в том числе и AoT под Android. Так чего удивляться существованию Php to Navite?
Вот, кстати, про Java любопытно. Вы часто встречали проекты, в которых Java компилируется в бинарный код? Я имею в виду, кроме как под Android. А ведь такие компиляторы есть и для десктопных платформ. Но только никто ими не пользуется. Потому что это никому не нужно.
Я и не говорю, что php плох. Я ничего про уровень языка не писал. Все языки хороши по своему. Но у каждого свой скоуп применения.
Да и Windows множество участков кода имеет на c# — нет проблем в таком подходе.
Разве кто-то сменил технологию? Транслятор сменили и всё. Как писали на php, так и пишут.
Ну это смотря что под технологией понимать. К тому же насколько помню сам язык тоже притерпел некоторые модификации.
Разве кто-то сменил технологию? Транслятор сменили и всё. Как писали на php, так и пишут.
Да и Windows множество участков кода имеет на c# — нет проблем в таком подходе.
Я выделил жирным проблему. Windows начал использовать c# не вместо, а вместе с существующим кодом. И к тому моменту c# уже протестирован. Ну совсем разные вещи. Попробуйте взглянуть на ситуацию с точки зрения бизнеса.
Зачем плодить полупустые сущности, раз там все равно тупой статический метод?
Согласен с этим.
Но как это знание тут может помешать на практике? Т.е. грубо говоря, что в случае с отдельным классом, что в случае с тем же классом — при добавлении наследников в иерархию, статический метод должен дорабатываться.
> Например, есть у вас статичный метод, принимающий код ошибки, а возвращающий строку с её описанием для UI
А при чем тут фабрика вообще, если возвращается строка?
Т.е. я могу прокинуть любую фабрику для инстанциирования объекта.
Возвращается объект типа String — каким он будет, определяет фабрика.
Если на выходе будет не String, а Product, как в примерах из википедии — суть не изменится, это фабрика.
Но, по мере усложнения приложения, как правило не долго.
Разделение ответственности: Инстанцирование кнопки — одна ответственность. Реализация функциональности кнопки — другая.
Т.о. отделив создание объекта от него самого, через ввод фабрики, вы сможете
а). Вводить в приложение различные фабрики, например на этапе исполнения, либо через конфигурацию
б). Замочить сам процесс создания кнопки, что позволит оттестировать код с разных сторон.
Это облегчает жизнь, когда нужно поддерживать обратную совместимость. Появляется возможность безболезненно менять строение внутренних классов, главное, чтобы они соответствовали интерфейсу. Можно добавлять, удалять, изменять имплементации (они внутренние и никто не может на них завязаться).
Пример тому в Java — EnumSet.
Позволяет создать множество из enum объектов. При необходимости, в метод, принимающий EnumSet можно передать мок или свою реализацию. Но вот залезть в этот «фреймворк» и каким-то образом изменить его стандартное поведение, не получится.
В зависимости от количества элементов в Enum, этот класс может выдать разные реализации, некоторые будут построены на битовой маске int, некоторые на битовой маске long, если 32 или 62 бита не хватит — можно использовать массив. Нужно будет создать множество из одного элемента — пожалуйста. Если тесты производительности покажут, что Конкретная имплементация для 2 элементов будет работать лучше, чем имплементация с битовой маской — можно добавить ее и просто обновив версию пользователь получит прирост производительности без каких либо изменений своего кода.
Про паттерны уже много более подробных статей. А еще есть GoF, которая лучше любой статьи.
Шаблон фасад (англ. Facade) — структурный шаблон проектирования, позволяющий скрыть сложность системы путём сведения всех возможных внешних вызовов к одному объекту, делегирующему их соответствующим объектам системы.
Что такое шаблоны проектирования?