Дизайн программного обеспечения используется для представления структуры системы и определения потенциальных затрат и времени, необходимых для создания указанного программного обеспечения. Конечно, их следует использовать в качестве оценок; Успешное определение стоимости и времени, необходимых для создания программного обеспечения, является задачей, которая, вероятно, никогда не решалась в реальном мире или приводила либо к компромиссу в качестве, либо к части программного обеспечения, лишенной функций, которая не обеспечивает предполагаемую ценность. После всех затрат времени и усилий на создание программного обеспечения, если вам так повезло, что вы успешно создали часть программного обеспечения, которое представляет ценность, это программное обеспечение, скорее всего, перейдет в фазу обслуживания. Этот этап обслуживания, скорее всего, будет составлять примерно 80% времени и стоимости разработки всего проекта, при условии, что он продолжает приносить пользу организации (Гупта и Шарма, 2015). Поэтому крайне важно, чтобы с начальных этапов разработки мы реализовывали принципы проектирования, которые приведут к косвенному снижению затрат и усилий на этапе обслуживания, если мы создадим что-то, что стоит поддерживать.

Вот где вступают в действие принципы проектирования SOLID. Принципы были сформулированы Робертом С. Мартином, а аббревиатура была придумана Майклом Фезерсом в 2000 году. Вот пять принципов:

1. S: принцип единой ответственности

2. O: принцип открытия/закрытия

3. L: принцип подстановки Лисков

4. I: принцип разделения интерфейса

5. D: принцип инверсии зависимостей

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

S: Принцип единой ответственности:

У класса должна быть только одна причина для изменения (Martin, 2000). Если класс имеет более одной причины для изменения, то он считается хрупким, что напрямую соотносится с одним из принципов загнивающего дизайна. Когда часть программного обеспечения является хрупкой, она более склонна к поломке в областях, которые не имеют отношения к измененному компоненту. Со временем каждое исправление этого класса делает его хуже, создавая больше проблем и увеличивая вероятность поломки. Это приводит к тому, что программное обеспечение невозможно поддерживать, и в конечном итоге организация будет проводить исследовательское тестирование для каждого небольшого внесенного изменения, чтобы убедиться, что остальные нетронутые функции по-прежнему работают. Это означает, что время доставки увеличивается, а также стоимость каждого внесенного изменения или исправления. Всего этого можно избежать, логически разделив классы. Это не означает, что ваш класс должен делать только одну тривиальную вещь; вместо этого он должен выполнить одну задачу и выполнить ее хорошо.

Пример.Вместо создания одного класса с именем NotificationProvider для обработки отправки SMS и электронных писем создайте два отдельных класса с именами SmsProvider и EmailProvider. Это простой пример того, как вы можете внедрить принцип единой ответственности сегодня прямо сейчас в свой проект, и нет, еще не поздно. Это обеспечит ценность, даже если 99% кодовой базы не следует этому принципу.

O: Принцип открытия/закрытия:

Программные сущности, такие как классы, модули и функции, должны быть открыты для расширения, но закрыты для модификации (Martin, 2000). Вы должны попытаться спроектировать и реализовать классы, которые никогда не изменятся при изменении требований. Вместо этого лучше расширить поведение и существующую функциональность, добавив новый код, а не модифицируя существующий код, который работает. Это можно сделать, используя интерфейсы, наследование или даже шаблон стратегии.

Пример:

В приведенном выше фрагменте кода мы предполагаем, что метод CalculateDiscount был создан для определения скидки на основе стоимости покупки. Будем считать, что этот код активен и работает, как задумано. Требования изменились, и теперь скидка должна определяться типом продаваемого товара. Чтобы успешно реализовать принцип Open-Closed, вы должны расширить существующую функциональность, не изменяя существующий рабочий код. Это достигается путем перегрузки исходного метода новым параметром, тем самым оставляя существующий рабочий код нетронутым и расширяя функциональность сущности скидки. Конечно, это расширение может быть достигнуто и различными другими способами.

L: Принцип замены Лисков:

Производные типы должны полностью поддерживать замену своих базовых типов. Подтипы должны заменяться их базовыми типами (Martin, 2000). Другими словами, один класс не должен расширяться из другого только потому, что между ними есть общие черты. Для понимания нужен пример.

Итак, этот простой пример выше показывает, что есть базовый класс типа Employee, и у этого объекта есть зарплата. Разработчики, менеджеры, а также генеральные директора также получают зарплату. Так почему бы не наследовать от класса Employee? Что ж, проблема здесь в методе SetManagedBy. В выделенном коде вы можете видеть, что мы назначаем базовый объект сотрудника объекту менеджера, а затем назначаем этому объекту менеджера. Если бы вы изменили эту строку на new CEO(), следующая строка вызвала бы исключение. Это прямо противоречит принципу замещения Лискова. (Это исключение более вероятно, если используется отражение или реализован шаблон стратегии.)

Итак, простое эмпирическое правило: если каждое свойство или метод не используется потомком базового класса, то этот класс не должен наследоваться от базового класса. Это можно легко смягчить, создав меньшие по размеру более конкретные интерфейсы: вместо этого вы можете иметь интерфейс IEmployee и интерфейс IManagable, а затем соответствующим образом реализовать их подписи.

I: Принцип разделения интерфейсов:

Клиентов не следует заставлять зависеть от интерфейсов, которые они не используют (Martin, 2000). Это простой принцип, которому легко следовать. Мы будем использовать предыдущий пример, но с интерфейсами, чтобы продемонстрировать это.

Вот как это выглядело бы, если бы мы реализовали предыдущий пример с использованием интерфейсов. Обратите внимание, что сущности CEO не требуется свойство ManagedBy или метод SetManagedBy; однако он вынужден реализовать его из-за подписи IEmployee. Вместо этого мы можем создать два интерфейса, а именно IEmployee и IManageable.

Как вы можете видеть выше, сущность CEO должна реализовывать только подпись интерфейса IEmployee и не должна реализовывать интерфейс IManageable, поскольку она не использует свою подпись. Вышеизложенное приводит к соответствующему соблюдению принципа разделения интерфейса.

Фактическая выгода от реализации этого:

• Более высокая связность, что делает код более надежным.

• Нижняя муфта, обеспечивающая лучшую ремонтопригодность и устойчивость к изменениям

D: Принцип инверсии зависимостей

Модули высокого уровня не должны зависеть от модулей низкого уровня (Martin, 2000). Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Однако детали должны зависеть от абстракций. Инверсия зависимостей — это стратегия зависимости от интерфейсов или абстрактных функций, а не от конкретных функций и классов, поскольку вероятность изменения абстракций меньше, чем у их конкретных аналогов. Этого можно достичь, используя внедрение зависимостей, реализованное с помощью интерфейсов. Пример должен помочь.

В этом простом примере мы видим, что у нас есть RegistrationController, и после регистрации пользователя отправляется уведомление, здесь используется интерфейс INotificationProvider, а здесь внедряется SMSprovider. Если по какой-то причине компания решила изменить поток уведомлений на электронную почту, необходимым изменением будет создание нового EmailProvider, который реализует подпись интерфейсов INotificationProvider и внедрение зависимостей изменений. Этих двух небольших изменений будет достаточно, чтобы миграция была завершена. Изменения будут выглядеть так. Для простоты я пропущу «внедренный» провайдер через конструктор, но как вы можете видеть в исходном коде, для сохранения существующего функционала ничего менять не нужно; создается только новый поставщик и обновляется внедрение зависимостей.

Реальным примером этого может быть то, что вы внедрили стороннего поставщика услуг SmsProvider, который работает уже два года и используется на протяжении всего проекта. Теперь Amazon выпустили свои собственные смс-сервисы, которые стоят значительно дешевле, вы должны их использовать. Используя инверсию зависимостей, вы можете значительно быстрее перейти на эти службы amazon, поскольку независимо от того, сколько существует ссылок на старый SmsProvider, для завершения всей миграции все равно будет только два изменения.

Заключение

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

Ссылки:

Чебанюк Э. и Марков К. (2016) «Подход к проверке диаграмм классов в соответствии с принципами проектирования SOLID», MODELSWARD 2016 — Материалы 4-й Международной конференции по проектированию на основе моделей и разработке программного обеспечения , (Modelsward), стр. 435–441. дои:

Гупта А. и Шарма С. (2015) «Сопровождение программного обеспечения: вызовы и проблемы», International Journal of Computer Science Engineering (IJCSE), 4(01), стр. 23–25.

Принципы проектирования SOLID