Страница 15 из 20
Рис. 7.1. Класс Employee
Этот класс нарушает принцип единственной ответственности, потому что три его метода отвечают за три разных актора.
• Реализация метода calculatePay() определяется бухгалтерией.
• Реализация метода reportHours() определяется и используется отделом по работе с персоналом.
• Реализация метода save() определяется администраторами баз данных.
Поместив исходный код этих трех методов в общий класс Employee, разработчики объединили перечисленных акторов. В результате такого объединения действия сотрудников бухгалтерии могут затронуть что-то, что требуется сотрудникам отдела по работе с персоналом.
Например, представьте, что функции calculatePay() и reportHours() используют общий алгоритм расчета не сверхурочных часов. Представьте также, что разработчики, старающиеся не дублировать код, поместили реализацию этого алгоритма в функцию с именем regularHours() (рис. 7.2).
Рис. 7.2. Общий алгоритм
Теперь вообразите, что сотрудники бухгалтерии решили немного изменить алгоритм расчета не сверхурочных часов. Сотрудники отдела по работе с персоналом были бы против такого изменения, потому что вычисленное время они используют для других целей.
Разработчик, которому было поручено внести изменение, заметил, что функция regularHours() вызывается методом calculatePay(), но, к сожалению, не заметил, что она также вызывается методом reportHours().
Разработчик внес требуемые изменения и тщательно протестировал результат. Сотрудники бухгалтерии проверили и подтвердили, что обновленная функция действует в соответствии с их пожеланиями, после чего измененная версия системы была развернута.
Разумеется, сотрудники отдела по работе с персоналом не знали о произошедшем и продолжали использовать отчеты, генерируемые функцией reportHours(), но теперь содержащие неправильные цифры. В какой-то момент проблема вскрылась, и сотрудники отдела по работе с персоналом разом побледнели от ужаса, потому что ошибочные данные обошлись их бюджету в несколько миллионов долларов.
Все мы видели нечто подобное. Эти проблемы возникают из-за того, что мы вводим в работу код, от которого зависят разные акторы. Принцип единственной ответственности требует разделять код, от которого зависят разные акторы.
Признак 2: слияния
Слияния – обычное дело для исходных файлов с большим количеством разных методов. Эта ситуация особенно вероятна, если эти методы отвечают за разных акторов.
Например, представим, что коллектив администраторов баз данных решил внести простое исправление в схему таблицы Employee. Представим также, что сотрудники отдела по работе с персоналом пожелали немного изменить формат отчета, возвращаемого функцией reportHours().
Два разных разработчика, возможно, из двух разных команд, извлекли класс Employee из репозитория и внесли изменения. К сожалению, их изменения оказались несовместимыми. В результате потребовалось выполнить слияние.
Я думаю, мне не нужно рассказывать вам, что всякое слияние сопряжено с некоторым риском. Современные инструменты довольно совершенны, но никакой инструмент не сможет правильно обработать все возможные варианты слияния. В итоге риск есть всегда.
В нашем примере процедура слияния поставила под удар администраторов баз данных и отдел по работе с персоналом. Вполне возможно, что риску подверглась также бухгалтерия.
Существует много других признаков, которые мы могли бы рассмотреть, но все они сводятся к изменению одного и того же исходного кода разными людьми по разным причинам.
И снова, исправить эту проблему можно, разделив код, предназначенный для обслуживания разных акторов.
Решения
Существует много решений этой проблемы. Но каждое связано с перемещением функций в разные классы.
Наиболее очевидным, пожалуй, является решение, связанное с отделением данных от функций. Три класса, как показано на рис. 7.3, используют общие данные EmployeeData – простую структуру без методов. Каждый класс включает только исходный код для конкретной функции. Эти три класса никак не зависят друг от друга. То есть любое непреднамеренное дублирование исключено.
Недостаток такого решения – разработчик теперь должен создавать экземпляры трех классов и следить за ними. Эта проблема часто решается применением шаблона проектирования «Фасад» (Facade), как показано на рис. 7.4.
Класс EmployeeFacade содержит очень немного кода и отвечает за создание экземпляров трех классов и делегирование вызовов методов.
Рис. 7.3. Три класса, не зависящих друг от друга
Рис. 7.4. Шаблон «Фасад»
Некоторые разработчики предпочитают держать наиболее важные бизнес-правила как можно ближе к данным. Это можно сделать, сохранив важные методы в оригинальном классе Employee, и затем использовать этот класс как фасад для низкоуровневых функций (рис. 7.5).
Рис. 7.5. Наиболее важные методы остаются в оригинальном классе Employee и используются как фасад для низкоуровневых функций
Вы можете возразить против такого решения на том основании, что каждый класс содержит только одну функцию. Однако в реальности такое едва ли возможно. Количество функций, необходимых для расчета зарплаты, создания отчета или сохранения данных, наверняка будет больше в любом случае. Каждый из таких классов может иметь также много приватных методов.
Каждый такой класс, содержащий подобное семейство методов, образует свою область видимости. Вне этой области никто не знает о существовании приватных членов семейства.
Заключение
Принцип единственной ответственности (Single Responsibility Principle; SRP) касается функций и классов, но он проявляется в разных формах на еще двух более высоких уровнях. На уровне компонентов он превращается в принцип согласованного изменения (Common Closure Principle; CCP), а на архитектурном уровне – в принцип оси изменения (Axis of Change), отвечающий за создание архитектурных границ. Все эти идеи мы обсудим в последующих главах.
8. Принцип открытости/закрытости
Принцип открытости/закрытости (Open-Closed Principle; OCP) был сформулирован Бертраном Мейером в 1988 году[23]. Он гласит:
Программные сущности должны быть открыты для расширения и закрыты для изменения.
23
Bertrand Meyer. Object Oriented Software Construction, Prentice Hall, 1988, p. 23 (Бертран Мейер. Объектно-ориентированное конструирование программных систем. Русская редакция, 2005. – Примеч. пер.).