3. Что затрудняет изменение кода?
Класс А
Класс Б
Использует Класс А
Класс В
Использует Класс А
Класс Г
Использует Класс А
Класс Д
Используется Классом А
Класс Е
Используется Классом А
Класс Ё
Используется Классом А
4. 4
Локализация модификаций
1. Поддержка семантической связанности.
Ответственности модулей разбиваются на стадии проектирования, т.к. что бы каждая
ответственность попадала внутрь определенного модуля и не делилась. Выделение общих
функций в отдельные модули (Framework).
2. Предвосхищение ожидаемых изменений.
На стадии проектирования задаются вопросы, какие изменения в системе возможны? Цель
-‐ что бы однотипные изменения приводили к доработкам одних и тех же модулей.
3. Обобщение модуля.
Использование модифицируемых интерфейсов (расширяемые протоколы,
интерпретируемый формат данных). Модуль разделяется на интерпретатор внешних
запросов и сам модуль. Позволяет модифицировать модуль меняя интерпретатор внешних
запросов а не сам модуль.
4. Ограничение модифицируемости.
Для каждого модуля на стадии проектирования определяются возможности модификации.
Что дает возможность уменьшить влияние модификаций (но и ухудшить модифицируемость
продукта).
5. Зависимости между модулями [1/5]
Синтаксические -‐ сигнатуры данных производимых модулем А, должны соответствовать
ожиданием модуля Б, сигнатуры вызываемых методов -‐ аналогично.
Пример:
◦ Модуль A вызывает метод “int doHelloWorld(string name)”
◦ Модуль B реализует метод “int doHelloWorld(string name)”
5
6. 6
Зависимости между модулями [2/5]
Семантические -‐ смысл данных производимых модулем А, совпадает со смыслом ожидаемым
модулем Б, аналогично с методами.
Пример:
◦ Модуль A вызывает метод “int doHelloWorld(string name)”. Где имя передается в
формате «Имя Отчество Фамилия»
◦ Модуль B реализует метод “int doHelloWorld(string name)” Где имя ожидается в
формате «Имя Отчество Фамилия»
7. 7
Зависимости между модулями [3/5]
Последовательность -‐ если определена последовательность в которой передаются
данные, то она должна пониматься одинакова обеими сторонами. Так же если второй
вызов может идти вслед за первым в установленный промежуток времени.
Пример
1. Передается запрос на открытие сессии.
2. Передаются данные
3. Получаются данные
4. Передается запрос на закрытие сессии.
8. 8
Зависимости между модулями [4/5]
1. Идентификация интерфейса. Модуль Б должен знать корректную идентификацию (имя)
интерфейса А.
Пример
Если модуль B реализует интерфейс IHelloWorld. То модуль А должен знать что это интерфейс
IHelloWorld.
2. Расположение модуля Что бы Б запускался правильно он должен знать откуда будет вызываться
модуль А.
Пример
При вызове .NET Remoting необходимо знать URL хоста сервиса.
9. 9
Зависимости между модулями [5/5]
1. Качество данных. Могут быть предположения о структуре данных (определенная точность).
Пример
В бухгалтерской программе может быть заранее оговорено, что деньги имеют
точность 4 знака после запятой, что бы исключить расхождения из-за неверного
округления.
2. Существование модуля. Если модуль не существует то другой не сможет его использовать.
Пример
В коде модуля А может быть явно прописана процедура создания экземпляра модуля
B (скажем вызов конструктора). Если бы нужный интерфейс реализовывался бы
другим модулем, то пришлось бы вызывать другой конструктор.
3. Управление ресурсами.
Модуль должен работать с ресурсами на основании предположений вызывающего модуля. Например,
использовать с ним общую память.
10. 10
Тактики: Уменьшение связанности модулей
1. Скрытие внутреннего поведения модуля. Скрытие внутренних функций в модуле с
обеспечением доступа через public интерфейсы.
2. Уменьшение путей коммуникации. Уменьшение числа модулей которые разделяют
информацию с текущим модулем. Это уменьшает связи между модулями. И позволяет
уменьшить эффект каскадных изменений.
3. Поддержка ранее созданных интерфейсов. В случае если мы зафиксировали
интерфейс, то меняя реализацию вызываемого модуля мы оставляем вызывающий
модуль неизменным (в случае если не меняется семантика данных, качество и т.д.)
11. 11
Тактики: Применение посредников
Применение модулей-‐посредников позволяет оградить один модуль от модификаций
другого (в случае, если не меняется семантика данных).
Типы посредников:
◦ Изменение данных. Создаются словари для преобразования данных из одного формата в другой
формат.
◦ Изменение сервисов. Façade, bridge, mediator, strategy,proxy and factory позволяют создать и
работать с промежуточным слоем.
◦ Изменение названия интерфейса. Паттерн Broker позволяет не менять вызывающий модуль, если
на сервере поменялось название интерфейса путем настроек брокера.
◦ Изменение места расположения сервисов. Использование Name-‐серверов, отвечающих за
определение места положения сервиса по имени.
◦ Изменение метода управления ресурсами. Применение централизованного модуля управления
ресурсами позволяет всем модулям работать с ресурсами одинаково (например, может быть
единый модуль создающий потоки выполнения и гарантирующий что количество потоков не
будет превышено).
◦ Существование модуля. Factory может создавать как сам модуль так и заглушку.
12. 12
Тактики: Отложенное связывание
1. Runtime registration использование механизмов plugin.
2. Configuration files конфигурирование системы при старте.
3. Polymorphism использование наследования для переопределения классов.
4. Component replacement подмена компонент (библиотек) при загрузки системы.
5. Строгое соблюдение протокола позволяет связывать в runtime независимые процессы.
13. Уровни зрелости модульной структуры
ПО
1. Level 1: Ad Hoc
Ни какой специальной модульной структуры нет.
Плюсы: просто и дешево.
2. Level 2: Modules
Модули отделены от друг друга, имеют версию и свой идентификатор.
Плюсы: отделение модулей от кода, упрощение работы с зависимостями в коде и версионной сборки.
3. Level 3: Modularity
Модуль может манятся автономно, не зависимо от других модулей. Модуль описывается своим «контрактом».
Плюсы: Возможность предсказать сложность внесения изменений в систему.
4. Level 4: Loose coupling
Разделение интерфейса и реализации.
Плюсы: Независимость поставщика и получателя интерфейса.
5. Level 5: Devolution (ограниченная автономия)
Артефакты модулей храниться в репозиториях. Модули собираются из артефактов в зависимости от необходимых сервисов.
Плюсы: уменьшение дублирования, улучшенная независимость модулей.
6. Level 6: Dynamism
Динамическое управление жизненным циклом модуля. Поддержка операций добавления./ изменения модуля.
Плюсы: Динамическое развитие системы и управление.
13
14. Объектно-‐ориентированное
программирование
Абстракция выделяет существенные характеристики некоторого объекта,
отличающие его от всех других видов объектов и, таким образом, четко определяет
его концептуальные границы с точки зрения наблюдателя.
1. Абстракция сущности
Объект представляет собой полезную модель некой сущности в предметной области
2. Абстракция поведения
Объект состоит из обобщенного множества операций
3. Абстракция виртуальной машины
Объект группирует операции, которые либо вместе используются более высоким
уровнем управления, либо сами используют некоторый набор операций более низкого
уровня
4. Произвольная абстракция
Объект включает в себя набор операций, не имеющих друг с другом ничего общего
15. Абстракция сущности
Domain Driven Design
Подход, позволяющий быстро
проектировать стабильную архитектуру
приложения, основываясь на терминах
предметной области.
Основные способы определения модулей:
◦ Layers
◦ Entity
◦ Value Object
◦ Services
◦ Repository
15
16. Слои программного обеспечения
Разделение программного обеспечения на
слои согласно выполняемым функциям с
точки зрения программного обеспечения.
Например:
◦ Слой пользовательского интерфейса;
◦ Слой бизнес-‐логики;
◦ Слой информации о домене сущностей;
◦ Слой доступа к данным;
Должно быть четкое разделение слоев.
Должны быть определены правила
взаимодействия слоев (интерфейсы).
16
17. Сущности – объекты реального мира
Представляют объекты реального мира;
Обладают идентификацией (например,
номер паспорта + дата и место выдачи для
человека)
Обладают явно выраженным жизненным
циклом (процедурой создания, удаления,
изменения состояния)
Обладают поведением.
17
18. Объект-‐значение
Не обладают идентификацией
Могут копироваться и передаваться от
функции к функции без ограничений.
Не обладают жизненным циклом
Создаются когда надо и удаляются, когда ни
кем не используются
Зачастую не меняют своих атрибутов
Могут быть доступны для коллективного
доступа из разных частей программы.
Атрибуты составляющие Value Object
должны быть концептуально полными
(соответствовать какой-‐либо абстракции)
18
19. Сервисы
Основные свойства:
◦ Описывают процессы, работающие с Entity и Value Object в терминах предметной области
◦ Не имеют состояния;
◦ Не заменяют операции, которые принадлежат Entity, а дополняет их. Обычно сервис является важным
процессом с точки зрения домена;
Разделяют сервисы, принадлежащие уровню домена и сервисы, принадлежащие
инфраструктуре (например, доступ к данным)
Command Query Separation
“every method should either be a command that performs an action, or a query that returns data to
the caller, but not both. In other words, asking a question should not change the answer”
Bertrand Meyer
19
20. Модули
1. Modules
Состоят из элементов, логически связанных друг с другом.
2. Агрегаты
Группа связанных объектов, которые могут рассматриваться как одно
целое
3. Фабрики
Инкапсулирую процесс создания сложных объектов или группы объектов
4. Репозитории
Реализуют логику получения ссылки на объекты предметной области по
разным критериям
20
22. SOLID
как организовать структуру классов?
• SRP Принцип единственной обязанности.
На каждый объект должна быть возложена одна единственная обязанность.
• OCP Принцип открытости/закрытости.
Программные сущности должны быть открыты для расширения, но закрыты для изменения.
• LSP Принцип подстановки Барбары Лисков.
Объекты в программе могут быть заменены их наследниками без изменения свойств
программы.
• ISP Принцип разделения интерфейса.
Много специализированных интерфейсов лучше, чем один универсальный.
• DIP Принцип инверсии зависимостей.
Зависимости внутри системы строятся на основе абстракций. Модули верхнего уровня не
зависят от модулей нижнего уровня. Абстракции не должны зависеть от деталей. Детали
должны зависеть от абстракций.
23. SRP: Single Responsibility Principle
(принцип единственной обязанности)
Не должно существовать более одного
мотива для изменения данного класса
class 01 SRP OFF
Customer
+ GetName(): const char*
Order
Product
+ CalculateTax(): double
+ GetName(): const char*
+ GetPrice(): double
+ MakeReservation(): bool
class 01 SRP ON
Customer
+ GetName(): const char*
Order
Product
+ GetName(): const char*
+ GetPrice(): double
TaxCalculator
+ CalculateTax(Product): double
ProductReservation
+ ReserveProduct(Product): bool
*антипаттерн
24. OCP: Open/Closed Principle (принцип
открытия/закрытия)
Объекты проектирования (классы, функции,
модули и т.д.) должны быть открыты для
расширения, но закрыты для модификации.
class 02 OCP OFF
Customer
+ GetName(): const char*
Order
Product
+ GetName(): const char*
+ GetPrice(): double
TaxCalculator
+ CalculateTax(Product): double
ProductReservation
+ ReserveProduct(Product): bool
Catalogue
+ FindByName(const char*): Product
class 02 OCP ON
Customer
+ GetName(): const char*
Order
Product
+ GetName(): const char*
+ GetPrice(): double
TaxCalculator
+ CalculateTax(Product): double
ProductReservation
+ ReserveProduct(Product): boolCatalogue
+ FindByName(IFindSpecification): Product
«interface»
IFindSpecification
+ isIt(Product): bool
FindByNameSpecification
+ isIt(Product): bool
FindByGroupSpecification
+ isIt(Product): bool
*антипаттерн
25. LSP: Liskov Substitution Principle (принцип замещения
Лисков)
пример SOLID_LSP
Функции, которые используют ссылки на базовые классы, должны иметь
возможность использовать объекты производных классов, не зная об этом
class Rectangle
{
protected:
int width;
int height;
public:
Rectangle(int w, int h) : width(w), height(h) {};
virtual void SetWidth(int value) { width = value; }
virtual void SetHeight(int value) { height = value;
}
virtual int GetSquare() { return width * height; }
};
class Square : public Rectangle
{
public:
Square(int w, int h) :
Rectangle(w,w) {};
virtual void SetWidth(int value) {
width = value; height = value; }
virtual void SetHeight(int value) {
width = value; height = value; }
};
26. LSP: Нарушение принципа LSP (class diagram)
антипаттерн
class 03 LSP
Rectangle
# height: int
# width: int
+ GetSquare(): int
+ Rectangle(int, int)
+ SetHeight(int): void
+ SetWidth(int): void
Square
+ SetHeight(int): void
+ SetWidth(int): void
+ Square(int, int)
27. ISP: Interface Segregation Principle (принцип
изоляции интерфейса)
пример SOLID_ISP
Клиент не должен вынужденно зависеть от элементов интерфейса, которые он не
использует.
Другими словами этот принцип можно сформулировать так: зависимость между классами
должна быть ограничена как можно более узким интерфейсом.
class IBusinessMenu
{
protected:
virtual const char* GetFirstItem() =0 ;
virtual const char* GetSecondItem() =0;
virtual const char* GetCompot() =0 ;
public:
void PrintMenu()
{
if (GetFirstItem() != nullptr) std::cout << "Item:" << GetFirstItem() << std::endl;
if (GetSecondItem() != nullptr) std::cout << "Item:" << GetSecondItem() << std::endl;
if (GetCompot() != nullptr) std::cout << "Item:" << GetCompot() << std::endl;
}
};
30. DIP: Dependency Inversion Principle
(принцип обращения зависимости)
пример SOLID_DIP
Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа
модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей.
Детали должны зависеть от абстракций.
class IItem
{
public:
virtual void print() = 0;
};
class Menu
{
private:
std::vector<IItem*> items;
public:
void add(IItem *i) {
items.insert(items.end(),i); }
void menu() {
for (auto i : items) i->print(); }
};Стратегия Menu применяется к
интерфейсу IItem. Этим мы устранили
недостатки предыдущего варианта.
31. DIP: Class Diagram
class 05 DIP
ItemBulka
+ print(): void
IItem
+ print(): void
ItemCoffe
+ print(): void
Menu
- items: std::vector<IItem*>
+ add(IItem*): void
+ menu(): void
33. Tell Don’t Ask
пример SOLID_TDA, CQS
Один из основополагающих принципов ООП: Необходимо
делегировать объекту действия, вместо того, что запрашивать его
детали реализации.Это помогает достичь многократного
использование класса (поскольку ни кто не знает его деталей
реализации).
Command Query Separation – принцип разделение методов,
которые выполняют какие-‐либо действия (tell) и методов,
которые осуществляют запросы данных (ask).
Law of Demeter – объект может вызывать методы только:
• Себя
• Своих параметров
• Объектов, созданных внутри метода
34. Модифицируемость vs Performance
Разделение данных на домены ведет к увеличению
распределенности объектов между сервисами, что ухудшает
производительность.
Разделение системы на слои, увеличивает количество
преобразований данных, что так же отрицательно влияет на
производительность.
Использование операций в терминах домена приводит к
тому, что появляется дополнительное преобразование
данных из внутренней модели во внешнюю. Что так же
отрицательно влияет на производительность.
34