Чистый код: Принцип открытости закрытости (OCP)
Принцип открытости/закрытости гласит, что программные объекты (классы, методы, функции и т. д.) должны быть открыты для расширения, но закрыты для модификации.
Идеальной реализацией данного принципа является интерфейс. Ничего лишнего, нечего модифицировать, можно только расширять.
Отталкиваясь от этого приведу пример нарушающий рассматриваемый принцип. Пример взят с довольно популярного русскоязычного сайта, хотя он написан на языке C#, думаю он будет понятен и другим программистам.
Здесь представлены сразу несколько нарушений ограничивающих гибкость кода. Начнем с первого же метода, который возвращает все объекты из условного хранилища. Хочется задать вопрос, а зачем все объекты нужны разом, не проще ли сразу сделать отбор. Если мы в качестве параметра добавим предикат, то нарушим принцип открытости закрытости для данного интерфейса. Следовательно метод лучше выделить в отдельный интерфейс.
Следующий метод, получения одного объекта, тоже ограничивает гибкость. Опять вопрос, а если будет необходимо получить объект не по ID, а по другому признаку. Такой метод вообще не нужен, ведь если создать инструмент получения элементов по условию, получение одного элемента это частный случай.
Остались последние четыре метода. Очевидно, что автор примера пытался применить понятие CRUD — акроним, обозначающий четыре базовые функции, используемые при работе с базами данных: создание (англ. create), чтение (read), модификация (update), удаление (delete) (1). Но даже в определении сказано, что это только сокращение, не требующее строгого соответствия. Следовательно и включать все эти методы в один класс не целесообразно.
Здесь стоит сделать отступление. Искушенный читатель, может указать, что реализация CRUD в одном классе является шаблоном ActiveRecord (2). Но сам шаблон является довольно спорным. Читателю стоит ознакомиться с определением, материалами в примечаниях, и самому принять решение, использовать ли его в разработке.
Кстати, автору приведенного выше примера, при его дальнейшей реализации, пришлось отказаться от использовании метода сохранения. Оставив его пустым. Ведь все методы интерфейса подлежат обязательной реализации. Нарушив принципы SOLID, даже в небольшом примере, был создан артефакт.
Теперь, давайте создадим свой пример реализации, соответствующий понятию гибкости и читаемости кода.
Создадим функционал сериализации массива объектов с данными в формат JSON, с возможностью расширения для записи в других форматах.
Для того, чтобы соблюдался принцип открытости закрытости, необходимо разделить сущности таким образом, чтобы при внесении изменений не было необходимости модифицировать уже готовые классы.
Первое действие, разделить данные и действия над ними, то есть метод сериализации данных необходимо отделить от самих данных. Следующим действием необходимо отделить инструменты сериализации одного элемента от списка. В результате получаются три сущности, данные, сериализатор для одного элемента, сериализатор массива данных.
Согласно условию задачи, мы должны иметь возможность расширить функционал не нарушая описываемого принципа. Класс Product может быть расширен путем добавления полей в дочерних классах. Инструмент преобразования элемента в строку мы можем создавать для каждого дочернего класса с данными, не исключая базового. К тому же можем создавать новые форматы преобразования отличные от JSON, не меняя структуру программы. Класс сериализации массива данных, обеспечивает форматирование только концевых элементов, следовательно его можно изменять, когда существенно меняется формат данных, например с JSON на XML, или SQL.
Поясняющая диаграмма классов выглядит следующим образом.
Ниже приведен полный код примера.
- РУВИКИ - CRUD
- РУВИКИ - ActiveRecord