- •Введение
- •1. Цели и задачи архитектора информационных систем
- •1.1. Цели создания информационных систем
- •1.2. Базовые структуры информационных систем
- •1.3. Уровни реализации архитектуры ИС
- •1.4. Атрибуты качества информационных систем
- •1.5. Сопровождение бизнес-ориентированных ИС
- •2.2. Структурное программирование
- •2. Парадигмы программирования
- •2.1. Модульное программирование
- •2.4. Функциональное программирование
- •2.5. Общие тенденции развития парадигм программирования
- •3. Чистая архитектура
- •3.1. Кричащая архитектура
- •3.2. Принципы SOLID
- •3.2.1. Принцип единственной ответственности
- •3.2.2. Принцип открытости/закрытости
- •3.2.3. Принцип подстановки Б. Лисков
- •3.2.4. Принцип разделения интерфейсов
- •3.2.5. Принцип инверсии зависимостей
- •3.3. Программные компоненты и принципы их организации
- •3.3.1. Диаграмма компонентов
- •3.3.2. Принципы взаимосвязи компонентов
- •3.3.3. Принцип эквивалентности повторного использования и выпусков
- •3.3.4. Принцип согласованного изменения
- •3.3.5. Принцип совместного повторного использования
- •3.3.6. Сравнение принципов взаимосвязи компонентов
- •3.4. Компонентный подход как способ динамического изменения архитектуры ИС
- •3.4.1. Принцип ацикличности зависимостей
- •3.4.2. Принцип устойчивых зависимостей
- •3.4.3. Принцип устойчивости абстракций
- •3.4.4. Графическая интерпретация принципов SDP и SAP
- •3.5. Вычислительные архитектуры ИС
- •3.5.1. Клиент-серверная архитектура
- •3.5.2. Трёхзвенная архитектура
- •3.5.3. Микросервисная архитектура
- •3.6. Шаблоны и антишаблоны проектирования ИС
- •3.6.1. Шаблоны проектирования
- •3.6.2. Антишаблоны проектирования
лями функционала системы. В силу высокой сложности современных ИС внесение любого, даже, на первый взгляд, незначительного измене-
ния, потенциально может привести к непредсказуемому поведению системы. В связи с этим принцип открытости/закрытости предлагает не изменять уже существующие и использующиеся программные сущно-
сти, а создавать новые, пользуясь в первую очередь наследованием.
Использование принципа открытости / закрытости также позволя-
ет при необходимости без изменения программного кода возвращать ранее использовавшийся в системе функционал в случае, если новый не отвечает поставленным требованиям. Для достижения такого результа-
та в настоящее время используется технология «внедрения зависимо-
стей» (Dependency Injection) [32].
3.2.3.Принцип подстановки Б. Лисков
В1988 году Барбара Лисков написала следующие строки с форму-
лировкой определения подтипов [25]: «если для каждого объекта o1 ти-
па S существует такой объект o2 типа T, что для всех программ P, оп-
ределённых в терминах T, поведение P не изменяется при подстановке o1 вместо o2, то S является подтипом T1».
Другими словами, программный код должен быть реализован та-
ким образом, чтобы методы (подпрограммы P) могли работать с клас-
сами-наследниками точно так же (т.е. без необходимости изменения программного кода), как они работают с классами-родителями.
В качестве примера ниже рассмотрены два фрагмента программ-
ного кода. В первом фрагменте третий принцип SOLID не соблюдается
(см. листинг 1).
Листинг 1 – Пример несоблюдения принципа LSP
function AnimalLegCount(animals: Array<Animal>) {
32
let legCount = 0;
for(let i = 0; i <= animals.length; i++) { let animal = animals[i];
if (typeof(animal) == "Lion") legCount += LionLegCount(animal);
else if (typeof(animal) == "Mouse") legCount += MouseLegCount(animal);
else if (typeof(animal) == "Snake") legCount += SnakeLegCount(animal);
}
return legCount;
}
Метод AnimalLegCount принимает на вход массив животных и подсчитывает количество их ног. Внутри метода делается три предпо-
ложения относительно типа животного, и в зависимости от реального типа данных вызывается подходящая для получения числа ног подпро-
грамма. Проблема в коде листинга 1 заключается в том, что при необ-
ходимости подсчитать количество ног, например, у страуса потребуется вносить изменения в программный код, что потенциально приведёт к нарушению метода открытости/закрытости.
Во втором фрагменте третий принцип SOLID соблюдается (см.
листинг 2).
Листинг 2 – Измененная функция подсчета количества конечно-
стей
function AnimalLegCount(animals: Array<Animal>) { let legCount = 0;
for(let i = 0; i <= a.length; i++) { legCount += animals[i].LegCount();
}
return legCount;
33
}
abstract class Animal {
//прочие поля и поведение программной сущности abstarct function LegCount();
}
class Lion extends Animal{
//прочие поля и поведение программной сущности override function LegCount() {
//получение количества ног у льва
}
}
//аналогично Lion описываются классы Mouse, Snake
Новая функция не пытается узнать конкретный тип объекта, она обрабатывает каждый объект как подкласс основного класса Animal. В
результате обращения вызывается метод вычисления количества ног,
реализованный в каждом из подклассов. Теперь при добавлении нового класса «Страус» метод AnimalLegCount изменять не потребуется, что повысит стабильность работы разрабатываемой ИС.
Принцип подстановки Барбары Лисков может и должен распро-
страняться до уровня архитектуры. Простое нарушение совместимости может вызвать загрязнение архитектуры системы значительным коли-
чеством дополнительных механизмов обработки объектов.
Инициализация структур данных (в рассмотренном примере – мас-
сив Animal) при соблюдении принципа подстановки Б. Лисков будет осуществляться посредством шаблона проектирования «Абстрактная фабрика» (см. раздел 3.6) и шаблона внедрения зависимостей.
3.2.4. Принцип разделения интерфейсов
Происхождение названия принципа разделения интерфейсов на-
глядно иллюстрирует схема на рис. 3.
34
Рисунок 3 – Диаграмма классов, не соответствующая принципу разделения интерфейсов
На рис. 3 представлено несколько программных сущностей, поль-
зующихся операциями в классе OPS. Пусть класс User1 использует только операцию op1, User2 – только op2 и User3 – только op3. В такой ситуации исходный код User1 непреднамеренно будет зависеть от op2 и op3, даже с учётом того, что не использует (не реализует) их. Эта зави-
симость означает, что изменения в исходном коде метода op2 в классе
OPS потребуют повторной компиляции и развёртывания класса User1,
несмотря на то, что для него ничего не изменилось. Эту проблему мож-
но решить разделением операций по интерфейсам, как показано на рис. 4.
35
