четверг, 17 марта 2011 г.

Принципы объектно-ориентированного проектирования. OO Design Principles

Само использование объектно-ориентированного языка программирования, совсем не означает хорошего объектно-ориентированного дизайна приложений на нем написанных. Конечно не для каждого приложения правильный дизайн критичен, но он явно выходит на первые позиции при дальнейшей поддержке и развитии.

Думаю большинство разработчиков знает о шаблонах проектирования GOF, важных, полезных и готовых практических решений. И все-таки шаблоны GOF - это лишь частные случаи более общих принципов объектно-ориентированного дизайна. Лучшее, что я читал по этой теме - книга Роберта Мартина "Быстрая разработка программ. Принципы, примеры, практика".

Принцип персональной ответственности (SRP)
Существует лишь одна причина, приводящая к изменению класса. Если существует несколько мотивов для изменения класса, ему соотвествует более одной ответственности.
Ось изменения является таковой лишь в том случае, если изменения действительно происходят. Применять принцип SRP следует только в том случае, когда это оправдано.

Принцип открытия-закрытия (OCP)
Программные объекты (классы, модули, функции и т.д.) должны быть открыты для расширения, но в тоже время закрыты для модификации.
Модули, соответствующие принципу открытия-закрытия, имеют два основных признака.
  1. «Открыто для расширения» означает, что поведение модуля может быть расширено. По мере изменения требований приложение можно расширить модуль за счет включения новых типов поведения, соответствующих этим изменениям.
  2. «Закрыто для модификации» означает что в результате расширения поведения модуля изменения в исходном или двоичном коде модуля не производятся. Двоичная исполняемая версия модуля (DLL, JAR, EXE) остается неизменной.
Принцип подстановки Лискоу (LSP)

  1. Подтипы должны быть заменяемы их исходными типами.
  2. Необходимо четко усвоить следующее правило. Если каждому объекту О1 типа S соответствует объект О2 типа Т, причем S является подтипом Т. Для всех программ Р, определенных на основе Т, поведение Р не меняется при замене О1 на О2.
Основными механизмами, следующими по значимости за принципом OCP, являются абстракция и полиморфизм. В статически типизированных языках (С++, Java, Delphi), одним из ключевых принципов, поддерживающих абстракцию и полиморфизм является наследование.

Какими основными принципами следует руководствоваться при использовании механизма наследования? Какими характеристиками должна обладать оптимальная структура наследования? Какие причины ведут к созданию иерархий, структура которых не согласуется с системой OCP? Ответы на эти вопросы и дает LSP.

Принцип инверсии зависимостей (DIP)
Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба типа модулей обязаны зависеть от абстракций. Абстракции не должны зависеть от подробностей. Подробностям следует зависеть абстракций.

«... любые хорошо структурированные объектно-ориентированные архитектуры имеют четко определенные слои, каждый из которых поддерживает некоторый компактный набор служб с помошью хорошо определенного и контролируемого интерфейса» Г. Буч

Это имеет примерно следующий вид Policy Layer - > Mechanism Layer -> Utility Layer
Более совершенная модель появляется когда слой верхнего уровня объявляет абстрактный интерфейс для необходимых служб. Затем на основе этих абстрактных интерфейсов реализуются слои нижних уровней. Каждый класс более высокого уровня с помощью абстрактного интерфейса использует следующий нижайший уровень. Таким образом, слои верхнего уровня не зависят от слоев нижнего уровня. Вместо этого слои расположенные ниже, зависят от абстрактных служебных интерфейсов, объявленных в верхних слоях. Нарушаются не только транзитивные зависимости PolicyLayer от UtilityLayer но и также непосредственная
зависимость PolicyLayer от MechanismLayer.
Policy Layer - > Policy Layer Interface - > Mechanism Layer -> Mechanism Layer
Interface - > Utility Layer

Проще говоря, рекомендуется не использовать зависимость от статичного класса – все взаимоотношения в программе поддерживаются через абстрактные интерфейсы.
  • ни одна переменная не должна содержать указатель или ссылку на статичный класс;
  • ни один класс не должен порождаться статичным классом;
  • ни один метод не должен отвергать метода реализации любого из базовых классов.
Данное решение не является исчерпывающим. Случается, что в интерфейс непостоянного класса следует вносить изменения, и эти изменения должны распространяться на абстрактный интерфейс, представляющий класс. Подобные изменения нарушают изоляцию абстрактного интерфейса. С другой стороны, если обратить внимание, что клиентские классы объявляют необходимые им сервисные интерфейсы, можно заметить, что интерфейс изменяется только в случае изменения клиента. Изменения же в классах, реализующих абстрактный интерфейс, не оказывают влияния на клиента.

Принцип отделения интерфейса (ISP)
Клиенты не должны попадать в зависимость от методов, которыми они не пользуются.

Классы, имеющие «тучные» интерфейсы, недостаточно компактны. Эти интерфейсы можно разбить на группы методов, и каждая группа обслуживает различный набор клиентов. Клиенты вынуждено зависят от методов, которыми они не пользуются, они становятся субъектами тех изменений, которыми подвержены эти методы. В результате между всеми клиентами могут возникать непредсказуемые состыковки. Другими словами, если клиент находится в зависимости от класса, содержащего неиспользуемые данным клиентом методы, которые применяются другими клиентам, то клиент находится под влиянием изменений, вносимых в класс этими клиентами. Желательно избегать подобных состыковок, поэтому необходимо отделить интерфейсы.

PS

Если заинтересовала тема - обязательно читайте далее в качестве продолжения

Метрики ООП проектирования
Принципы упаковки программных проектов
Шаблоны распределения обязанностей

и обратите внимание на оригинальные материалы по теме


OO Design Quality Metrics
Design Principles and Design Patterns

3 комментария :

Iv комментирует...

GRASP шаблоны неплохо раскрыты в книге "Применение UML 2.0 и шаблонов проектирования"
www.williamspublishing.com/Books/978-5-8459-1185-8.html

Vasyl Stashuk комментирует...

Книжка Мартина - очень крутая.
Но, мне кажется, всем она вряд ли подойдет, так как требует определенный опыт набитых шишек в ООП.

Николай Войнов комментирует...

Кто как привык учится. Я пришел как и наверно многие через набитые шишки и потом шаблоны проектирования GoF. И Мартин как раз сложил все это единую систему. Что самое интересное - система гораздо стройнее и меньше производных шишек и нагромождений в виде шаблонов проектирования.

Думаю этому нужно учить в ВУЗе сразу за инкапсуляцией поведения, наследованием и полиморфизмом. Но в ВУЗе нет нормального размера проектов чтобы проявились все сложности дизайна, возможно поэтому (все-таки больше готов списать на неграмотность преподавательского состава) поднимаемые в книге проблемы не проявляются в учебных проектах. О том, что ООП не заканчивается инкапсуляцией, наследованием и полиморфизмом мало кто знает наверное и сейчас :)

Сам сначала усвоил порядок и единообразие, через пару небольших проектов пришел к разделению ПО на уровни (представления, сервисные и хранения). К четвертому проекту вышел на шаблоны проектирования и только уже потом перешел к принципам. Сейчас практически не занимаюсь программированием, все больше специализированных средах и фреймворках типа 1С и Rails. Но старые знания помогают и здесь.

Опять же, иногда этого совсем не нужно! В большинстве случаев я бы полагался на правило трех ударов Бека - просто нужно менять дизайн когда это действительно нужно.