Шаблоны дизайна, чтобы избежать
Многие люди, похоже, согласны с тем, что шаблон Singleton имеет ряд недостатков, а некоторые даже предлагают полностью отказаться от шаблона. Здесь отличная дискуссия. Пожалуйста, присылайте любые комментарии о модели Singleton на этот вопрос.
Мой вопрос: есть ли другие шаблоны дизайна, которые следует избегать или использовать с большой осторожностью?
12 ответов
Узоры сложны
Все шаблоны дизайна должны использоваться с осторожностью. По моему мнению, вы должны рефакторинг к шаблонам, когда есть веская причина, чтобы сделать это, а не сразу внедрять шаблон. Общая проблема с использованием шаблонов заключается в том, что они добавляют сложность. Чрезмерное использование шаблонов делает данное приложение или систему обременительной для дальнейшей разработки и обслуживания.
В большинстве случаев существует простое решение, и вам не нужно применять какой-либо конкретный шаблон. Хорошее практическое правило - использовать шаблон всякий раз, когда фрагменты кода имеют тенденцию заменяться или требуют частой замены, и быть готовым принять предостережение о сложном коде при использовании шаблона.
Помните, что вашей целью должна быть простота и использование шаблона, если вы видите практическую необходимость поддержки изменений в вашем коде.
Принципы над шаблонами
Может показаться спорным использование шаблонов, если они могут привести к чрезмерно сложным и сложным решениям. Однако вместо этого программисту гораздо интереснее ознакомиться с методами и принципами проектирования, которые закладывают основу для большинства шаблонов. На самом деле одна из моих любимых книг по "шаблонам проектирования" подчеркивает это, повторяя, какие принципы применимы к рассматриваемому шаблону. Они достаточно просты, чтобы быть полезными, чем шаблоны с точки зрения актуальности. Некоторые из этих принципов достаточно общие, чтобы охватить нечто большее, чем объектно-ориентированное программирование (ООП), например принцип подстановки Лискова, при условии, что вы можете создавать модули своего кода.
Существует множество принципов проектирования, но те, которые описаны в первой главе книги GoF, довольно полезны для начала.
- Запрограммируйте на "интерфейс", а не на "реализацию". (Банда Четырех 1995:18)
- Пользуйтесь "композицией объектов", а не "наследованием классов". (Банда четырех 1995:20)
Позвольте этим на некоторое время погрузиться в вас. Следует отметить, что когда GoF был написан, интерфейс означает все, что является абстракцией (что также означает суперклассы), не следует путать с интерфейсом как типом в Java или C#. Второй принцип исходит из наблюдаемого чрезмерного использования наследства, которое, к сожалению, все еще широко распространено сегодня.
Оттуда вы можете прочитать о принципах SOLID, которые были сделаны Робертом Сесилом Мартином (он же Дядя Боб). Скотт Хансельман взял интервью у дяди Боба в подкасте об этих принципах:
- Единый принцип ответственности
- Открыто закрытый принцип
- Принцип замещения Лискова
- Интерфейс сегрегации принцип
- Принцип инверсии зависимости
Эти принципы - хорошее начало для чтения и обсуждения со своими сверстниками. Вы можете обнаружить, что принципы переплетаются друг с другом и с другими процессами, такими как разделение интересов и внедрение зависимостей. Пройдя некоторое время TDD, вы также можете обнаружить, что эти принципы естественны на практике, так как вам нужно в какой-то степени следовать им, чтобы создавать изолированные и повторяемые модульные тесты.
Больше всего их беспокоили сами авторы Design Patterns - шаблон "Посетитель".
Это "необходимое зло", но часто используется слишком часто, и необходимость в нем часто выявляет более фундаментальный недостаток в вашем дизайне.
Альтернативное имя для шаблона "Посетитель" - "Multi-dispatch", потому что шаблон "Посетитель" - это то, с чем вы сталкиваетесь, когда хотите использовать однотипный язык OO для диспетчеризации, чтобы выбрать код для использования на основе типа двух. (или более) разные объекты.
Классическим примером является то, что у вас есть пересечение между двумя фигурами, но есть еще более простой случай, который часто упускается из виду: сравнение равенства двух разнородных объектов.
Во всяком случае, часто вы в конечном итоге что-то вроде этого:
interface IShape
{
double intersectWith(Triangle t);
double intersectWith(Rectangle r);
double intersectWith(Circle c);
}
Проблема в том, что вы соединили все свои реализации IShape. Вы подразумевали, что всякий раз, когда вы хотите добавить новую фигуру в иерархию, вам нужно будет также изменить все другие реализации "Shape".
Иногда это правильный минимальный дизайн - но продумайте это до конца. Действительно ли ваш дизайн требует от вас отправки двух типов? Готовы ли вы написать каждый комбинаторный взрыв мульти-методов?
Часто, вводя другую концепцию, вы можете уменьшить количество комбинаций, которые вам действительно придется написать:
interface IShape
{
Area getArea();
}
class Area
{
public double intersectWith(Area otherArea);
...
}
Конечно, это зависит - иногда вам действительно нужно написать код для обработки всех этих различных случаев - но стоит сделать паузу и подумать, прежде чем окунуться и использовать Visitor. Это может спасти вас много боли позже.
Singletons - класс, использующий singleton X, имеет зависимость, которую трудно увидеть и которую трудно выделить для тестирования.
Они используются очень часто, потому что они удобны и просты для понимания, но они действительно могут усложнить тестирование.
Я считаю, что шаблонный метод шаблона, как правило, очень опасный шаблон.
- Много раз он использует вашу иерархию наследования по "неправильным причинам".
- Базовые классы имеют тенденцию становиться заваленными всевозможными не связанными кодами.
- Это заставляет вас блокировать дизайн, часто довольно рано в процессе разработки. (Преждевременная блокировка во многих случаях)
- Изменение этого на более позднем этапе становится все сложнее и сложнее.
Я не думаю, что вам следует избегать Design Patterns (DP), и я не думаю, что вы должны заставлять себя использовать DP при планировании своей архитектуры. Мы должны использовать ПЛ только тогда, когда они естественным образом возникают из нашего планирования.
Если мы с самого начала определим, что мы хотим использовать данный DP, многие наши будущие проектные решения будут зависеть от этого выбора, без гарантии того, что выбранный нами DP подходит для наших нужд.
Одна вещь, которую мы также не должны делать, это рассматривать DP как неизменную сущность, мы должны адаптировать модель к нашим потребностям.
Итак, подводя итог, я не думаю, что нам следует избегать ПЛ, мы должны принимать их, когда они уже формируются в нашей архитектуре.
Я думаю, что Active Record - это слишком часто используемый шаблон, который поощряет смешивание бизнес-логики с кодом персистентности. Он не очень хорошо скрывает реализацию хранилища от уровня модели и связывает модели с базой данных. Существует множество альтернатив (описанных в PoEAA), таких как Table Data Gateway, Row Data Gateway и Data Mapper, которые часто предоставляют лучшее решение и, безусловно, помогают обеспечить лучшую абстракцию хранилища. Кроме того, ваша модель не должна храниться в базе данных; как насчет хранения их в виде XML или доступа к ним с помощью веб-сервисов? Насколько легко было бы изменить механизм хранения ваших моделей?
Тем не менее, Active Record не всегда плохая и идеально подходит для простых приложений, где другие варианты будут излишними.
Это просто... избегайте шаблонов дизайна, которые вам не понятны, или те, в которых вы не чувствуете себя комфортно.
Чтобы назвать некоторые...
Есть некоторые непрактичные модели, например:
Interpreter
Flyweight
Есть также некоторые из них сложнее понять, например:
Abstract Factory
- Полная абстрактная фабричная модель с семействами созданных объектов не такая легкая, как кажетсяBridge
- Может стать слишком абстрактным, если абстракция и реализация разделены на поддеревья, но в некоторых случаях это очень полезная модельVisitor
- Понимание механизма двойной отправки действительно НЕОБХОДИМО
и есть некоторые шаблоны, которые выглядят ужасно простыми, но не настолько очевидным выбором из-за различных причин, связанных с их принципом или реализацией:
Singleton
- не совсем плохая картина, просто СЛИШКОМ перегружен (часто там, где он не подходит)Observer
- отличный шаблон... просто делает код намного труднее читать и отлаживатьPrototype
- торгует проверками компилятора на динамизм (который может быть хорошим или плохим... зависит)Chain of responsibility
- слишком часто просто принудительно / искусственно вталкиваются в дизайн
Для тех "непрактичных" нужно подумать, прежде чем их использовать, потому что где-то обычно есть более элегантное решение.
Для "трудных для понимания"... они действительно очень помогают, когда их используют в подходящих местах и когда они хорошо реализованы... но они являются кошмаром, когда используются ненадлежащим образом.
Теперь, что дальше...
- Head First Design Patterns ОБЯЗАН
- Создание источников - это "первая помощь"
Надеюсь, меня за это не побьют слишком сильно. Кристер Эрикссон написал две статьи ( одну, две) на тему шаблонов проектирования в своем блоге по обнаружению столкновений в реальном времени. Его тон довольно резкий и, возможно, немного провокационный, но этот человек знает свое дело, поэтому я бы не стал воспринимать его как бред сумасшедшего.
Дополнение к сообщению Спойка " Рефакторинг к паттернам" - это хорошее чтение.
Я полагаю, что шаблону наблюдателя есть за что ответить, он работает в очень общих случаях, но по мере усложнения систем он становится кошмаром, требующим уведомлений OnBefore(), OnAfter() и частой публикации асинхронных задач, чтобы избежать повторных запросов. entrancy. Гораздо лучшим решением является разработка системы автоматического анализа зависимостей, которая обрабатывает все обращения к объектам (с барьерами чтения) во время вычислений и автоматически создает ребро в графе зависимостей.
Итератор - это еще один паттерн GoF, который следует избегать или, по крайней мере, использовать его, только когда ни одна из альтернатив не доступна.
Альтернативы:
для каждого цикла. Эта конструкция присутствует в большинстве основных языков и в большинстве случаев может использоваться для избежания итераторов.
селекторы а-ля LINQ или jQuery. Они должны использоваться, когда for-each не подходит, потому что не все объекты из контейнера должны быть обработаны. В отличие от итераторов, селекторы позволяют в одном месте манифестировать, какие объекты нужно обрабатывать.