Блог автора:
http://sergeyteplyakov.blogspot.com/
Введение
Чтобы использовать паттерн проектирования по максимуму, нужно вникнуть в его суть, понять, какую проблему он призван решить и каким образом он это делает.
Канонические примеры большинства паттернов, приведенные "бандой четырех", включают в себя наследование. Но это не значит, что вы должны слепо использовать его. Наследование должно применяться осознанно и лишь тогда, когда обеспечиваемая им гибкость деуствительно необходима.
Паттерн "Стратегия"
Стратегия нужна тогда, когда не просто требуется спрятать алгоритм, а важно иметь возможность заменить его во время исполнения.
Паттерн "Шаблонный метод"
(Для обеспечения тестируемости) Вместо выделения интерфейса можно воспользоваться разновидностью паттерна "Шаблонный метод" под названием "Выделение и переопределение зависимости". Суть техники заключается в выделении изменчивого поведения в виртуальный метод, поведение которого затем можно переопределить в тесте.
В юнит-тестах практически невозможно учесть все тонкости, которые могут возникнуть. При работе с внешним окружением, помимо юнит-тестов, обязательно должны присутствовать интеграционные тесты, которые проверят граничные условия в сложных ситуациях.
Паттерн освобождения ресурсов (Dispose Pattern)
Почему вообще появилась необходимость управления ресурсами в системе с автоматической сборкой мусора? Потому что, помимо памяти, приложение может владеть и другими важными ресурсами, такими как дескрипторы файлов и потоков, мьютексы, семафоры и т.п. Время сборки мусора зависит от многих фактором и не может (и не должно) контролироваться приложением. С ресурсами дело обстоит иначе: разработчик должен приложить все усилия, чтобы время владения ими было минимальным...
Управление ресурсами в .NET основывается на интерфейсе IDisposable, метод Dispose которого вызывается пользовательским кодом, и на финализаторе (finalizers), который вызывается во время сборки мусора. Разница между финализатором и методом Dispose состоит в том, что первый вызывается сборщиком мусора в неизвестый момент времени. Второй вызывается пользовательским кодом, после чего ссылка на "мертвый" объект продолжает существовать.
Паттерн "Посредник"
Классы чтения и сохранения логов являются независимыми шагами импорта лог-файлов и не должны знать друг о друге...
Посредник в этом случае выступает барьером, который гасит изменения одной части системы, не давая им распространится на другие части.
Внутри модуля может быть довольно много связей, и вполне нормально, если эти связи будут явными.
Слабую связанность нужно обеспечивать на границах модуля, а не внутри него.
Обобщение простого решения, в котором нет никакой необходимости, убило не один проект.
Значительно лучше начинать с явного решения и обобщать его лишь тогда, когда стало понятно, в чем заключается обобщение и что оно действительно нужно.
Если предметная область говорит о наличии связи между понятием А и понятием Б, то в дизайне системы лучше всего отразить эти отношения явным образом.
Любая критически важная логика должна быть подвержена тестам. Точка.
Ненужно разделать с помощью посредника тесно связанные вещи.
Паттерн "Итератор"
Для поддержки цикла foreach не обязательно наличие интерфейса IEnumerable/IEnumerable<T>. достаточно, чтобы класс коллекции содержал метод GetEnumerator, который будет возвращать тип с методом bool MoveNext() и свойством Current.
Футбольная команда
не является спииском игроков, поэтому класс FootballTeam не должен наследовать List<Player>.
Принцип единственной обязанности
Гибкость всегда приводит к увеличению сложности.
Чем меньше у метода, класса или модуля вспомогательных задач, тем ниже вероятность случайных изменений.
...очень важно иметь возможность сосредоточиться на главной задаче метода, класса или модуля и выбросить из рассмотрения все второстепенные детали.
Наличие или отсутствие нарушения SRP очень зависит от того, насколько сложным является каждый из описанных ране шагов. Метод будет нарушать SRP, если валидация аргументов занимает 40 строк кода и находится в разных его частях.
Но класс может и не нарушать SRP, если он читает и сохраняет данные, но на каждую операцию требуется две строки кода.
Принцип "открыт/закрыт"
Мы должны ограничить каскад изменений и свести их количество к минимуму.
Открытость дизайна не означает расширения функциональности совсем без внесения изменений. Гибкий (supple) дизайн позволяет изменять поведение путем внесения изменений в модули, ответственные за данное поведение. При этом изменения второстепенных модулей отсутствует либо их число сведено к минимуму.
Принцип единственного выбора: всякий раз, когда система программного обеспечения должна поддерживать множество альтернатив, их полный список должен быть известен только одному модулю системы.
Классический объектный подход позволяет легко добавлять новые типы в существующую иерархию типов, а функциональный подход позволяет легко добавлять новые операции.
Принцип подстановки Лисков
"Должна существовать возможность использовтаь объекты производного класса вместо объектов базового класса". Это значит, что объекты производного класса должны вести себя согласованно, согласно контракту базового класса."
Основной смысл любой иерархии наследования в том, что она позволяет использовать базовые классы полиморфным образом, не задумываясь о том, экземпляр какого конкретного класса был передан.
Именно наличие контракта позволяет четко понять, нарушает производный класс принцип подстановки Лисков или нет.
Принцип разделения интерфейсов
Добиться простоты использования кода можно за счет проектирования чистых интерфейсов - интерфейсов, предоставляющие цельный набор операций, которого будет необходимо и достаточно большинству клиентов.
Принцип инверсии зависимостей
"Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Абстракции не должны зависеть от деталей. Детали должны зависить от абстракций"
Наличие интерфейсов образует дополнительный уровень косвенности (или дополнительный уровень абстрации), что затрудняет понимание системы.
Не для всех классов нужно выделять интерфейсы, и не все зависимости следует требовать извне в виде интерфейсов.
Легко столкнуться с проблемой чрезмерно абстрактных решений, когда интерфейсов слишком много и решение настолько гибкое и слабосвязное, что просто невозможно понять, кто за что отвечает и откуда начать изучение.
Исходный смысл принципа инверсии зависимостей в том, чтобы классы нижнего уровня взаимодействовали с верхним уровнем косвенно, ничего о нем не зная.
Принцип инверсии зависимостей не сводится лишь к выделению интерфейсов и передаче их через конструктор. DIP объясняет, для чего это нужно делать.
Чтобы на завязываться на класс верхнего уровня, класс может объявить некоторый интерфейс и потребовать его экземляр через аргумент конструктора.
Зависимост класса должны располагаться на текущем или более высоком уровне абстракции.
Принцип инверсии зависсимостей (DIP) - это принцип проектирования, который говорит, что классы должны зависеть от высокоуровневых абстракций.