Библиографическое описание:

Клочков К. С., Фатеев Д. С., Сабурова В. В. Принципы проектирования классов (SOLID) // Молодой ученый. — 2016. — №11. — С. 175-177.



При проектировании объектно-ориентированных программ необходимо подобрать подходящие объекты, отнести их к различным классам, соблюдая разумную степень детализации, определить интерфейсы классов и иерархию наследования и установить существенные отношения между классами. Дизайн должен, с одной стороны, соответствовать решаемой задаче, с другой — быть общим, чтобы удалось учесть все требования, которые могут возникнуть в будущем. Хотелось бы также полностью избежать или свести к минимуму необходимость перепроектирования. Решить эти задачи помогают принципы SOLID, рассмотренные ниже.

Принципединственнойобязанности (Single-Responsibility Principle— SRP).

У класса должна быть только одна причина для изменения.

Любое изменение требований проявляется в изменении распределения обязанностей между классами. Если класс берет на себя несколько обязанностей, то у него появляется несколько причин для изменения. Если класс отвечает за несколько действий, то его обязанности оказываются связанными. Изменение одной обязанности может привести к тому, что класс перестанет справляться с другими. Такого рода связанность — причина хрупкого дизайна, который неожиданным образом разрушается при изменении.

В контексте принципа SRP обязанностью называют причину изменения. Если вы можете найти несколько причин для изменения класса, то у такого класса более одной обязанности.

Допустим, в классе присутствуют две обязанности: управление соединением (методы Dial и Hangup) и передача данных (методы Send и Recv). Следует ли разделить эти обязанности? Все зависит от того, как именно изменяется приложение. Если модификация подразумевает изменение сигнатуры методов управления соединением, то дизайн начинает попахивать жесткостью, так как классы, вызывающие Send и Recv, придется повторно компилировать и развертывать чаще, чем хотелось бы. В таком случае обязанности следует разделить. Это защищает приложение-клиент от связанности двух обязанностей.

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

Принцип открытости/закрытости (Open/Closed Principle— OCP).

Программные сущности (классы, модули, функции и т. п.) должны быть открыты для расширения, но закрыты для модификации.

Любая система на протяжении своего жизненного цикла претерпевает изменения. Если единственное изменение в каком-то месте программы приводит к каскаду изменений в зависимых модулях, то дизайн попахивает жесткостью. Принцип OCP рекомендует переработать систему так, чтобы в будущем аналогичные изменения можно было реализовать путем добавления нового кода, а не изменения уже работающего.

Чтобы добиться возможности изменять поведение модуля, не трогая его исходного кода, необходимо пользоваться абстракциями. В любом объектно-ориентированном языке программирования, можно создавать абстракции, которые сами по себе фиксированы, но представляют неограниченное множество различных поведений. Абстракции — это абстрактные базовые классы, а поведения представляются производными от них классами. Модуль может манипулировать абстракцией. Такой модуль можно сделать закрытым для модификации, поскольку он зависит от фиксированной абстракции. И тем не менее поведение модуля можно расширять, создавая новые производные от абстракции.

В общем случае, каким бы «закрытым» ни был модуль, всегда найдется такое изменение, от которого он не закрыт. Не существует моделей, естественных во всех контекстах! Поскольку от всего закрыться нельзя, то нужно мыслить стратегически. Иными словами, проектировщик должен решить, от каких изменений закрыть дизайн: определить, какие изменения наиболее вероятны, а затем сконструировать абстракции, защищающие от них. Необходимо строить обоснованные гипотезы о том, с каким изменениями приложение может столкнуться в будущем.

Во многих отношениях принцип открытости/закрытости — основа основ объектно-ориентированного проектирования. Именно следование этому принципу позволяет получить от ООП максимум обещанного: гибкость, возможность повторного использования и удобство сопровождения.

Принцип подстановки Лисков (Liskov Substitution Principle).

Должна быть возможность вместо базового типа подставить любой его подтип.

Принцип LSP содержит правила, которыми необходимо руководствоваться, чтобы корректно воспользоваться наследованием и определяет характеристики наилучших иерархий наследования. Этот принцип был сформулирован Барбарой Лисков в 1988 году. Она писала:

Мы хотели бы иметь следующее свойство подстановки: если для каждого объекта o1 типа S существует объект o2 типа T, такой, что для любой программы P, определенной в терминах T, поведение P не изменяется при замене o1 на o2, то S является подтипом T.

Важность этого принципа становится очевидной, если рассмотреть последствия его нарушения. Предположим, что имеется функция f, принимающая в качестве аргумента ссылку на некоторый базовый класс B. Предположим также, что при передаче функции f ссылки на объект класса D, производного от B, она ведет себя неправильно. Тогда D нарушает принцип LSP. Понятно, что класс D оказывается хрупким в присутствии f.

Нарушение принципа LSP часто влечет за собой использование проверки типов во время выполнения способом, который находится в вопиющем противоречии с принципом открытости/закрытости. В таком случае мы видим предложения if или if/else, служащие для того, чтобы выбрать подходящее поведение в зависимости от типа объекта.

Проектируя архитектуру, которая соответствовала бы принципу подстановки Лисков нужно помнить одно очень важное правило: невозможно установить правильность модели, рассматриваемой изолированно. Правильность модели можно выразить только в терминах ее клиентов. Рассматривая окончательную версию классов подсистемы, взятых в изоляции от всего остального, можно прийти к выводу, что внутренне они непротиворечивы и правильны, но, взглянув на них с точки зрения пользователей данной подсистемы, обнаружить, что модель не годится.

Принципинверсиизависимости (Dependency-Inversion Principle— DIP).

A. Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те и другие должны зависеть от абстракций.

B. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

В традиционных методологиях разработки, например, в структурном анализе и проектировании, принято создавать программные конструкции, в которых модули верхнего уровня зависят от модулей нижнего уровня, а стратегия — от деталей. Собственно, одна из целей таких методологий состоит в том, чтобы определить иерархию подпрограмм, описывающую, как модули верхнего уровня обращаются к модулям нижнего уровня. Но именно в модулях верхнего уровня инкапсулированы важные стратегические решения. Эти модули и отличают одно приложение от другого, и если они зависят от модулей нижнего уровня, то изменение последних может напрямую отразиться на модулях верхнего уровня и стать причиной их изменения. Принцип DIP гласит, что модули верхнего уровня, определяющие стратегию, должны влиять на модули нижнего уровня, а не наоборот. Модули, которые содержат высокоуровневые правила системы, должны быть приоритетнее модулей, определяющих детали реализации, и независимы от них. Модули верхнего уровня вообще никак не должны зависеть от модулей более низкого уровня. Именно модули верхнего уровня, определяющие стратегию, мы и хотели бы использовать повторно. Но если модули верхнего уровня зависят от модулей нижнего уровня, то повторно использовать первые в различных контекстах становится трудно. Если же такой зависимости нет, то повторное использование модулей верхнего уровня существенно упрощается. Этот принцип лежит в основе проектирования всех каркасов. В правильно спроектированной объектно-ориентированной программе структура зависимостей «инвертирована» по отношению к той, что возникает в результате применения традиционных процедурных методик.

Чуть упрощенная, но все еще весьма действенная интерпретация принципа DIP выражается простым эвристическим правилом: «Зависеть надо от абстракций». Оно гласит, что не должно быть зависимостей от конкретных классов; все связи в программе должны вести на абстрактный класс или интерфейс.

Конечно, эти правило хотя бы раз да нарушаются в любой программе. Где-то ведь необходимо создавать экземпляры конкретных классов, и модуль, в котором это делается, будет от них зависеть. К тому же не видно причин соблюдать это правило для конкретных, но редко изменяющихся классов. Если класс не будет часто изменяться и не предполагается создавать аналогичные ему производные классы, то зависимость от такого класса не принесет особого вреда.

Принцип разделения интерфейсов (InterfaceSegregationPrinciple).

Клиенты не должны вынужденно зависеть от методов, которыми не пользуются.

Этот принцип относится к недостаткам «жирных» интерфейсов. Говорят, что класс имеет «жирный» интерфейс, если функции этого интерфейса недостаточно сцепленные. Иными словами, интерфейс класса можно разбить на группы методов. Каждая группа предназначена для обслуживания разнотипных клиентов. Одним клиентам нужна одна

группа методов, другим — другая. Принцип ISP допускает, что могут существовать объекты, нуждающиеся в несцепленных интерфейсах, однако предполагает, что клиентам необязательно знать, что это единый класс. Клиенты должны лишь знать об абстрактных интерфейсах, обладающих свойством сцепленности.

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

Литература:

  1. Роберт С. Мартин Принципы, паттерны и методики гибкой разработки на языке C# / Роберт С. Мартин, Мика Мартин. — Изд-во Символ-Плюс, 2011. — 768
  2. Гамма Э. Приёмы объектно-ориентированного проектирования. Паттерны проектирования / Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. — Изд-во Питер, 2013 г. — 386

Обсуждение

Социальные комментарии Cackle