Осенний семестр 2017 Преподаватель: асс. каф. Чуканов В.С
Содержание
Принцип Единственной Обязанности
SRP: Пример Rectangle
SRP: Решение Примера Rectangle
SRP: Пример Modem
SRP: Пример Modem
Шаблон Проектирования: Адаптер
Пример Адаптера: Код Библиотечных Классов
Пример Адаптера: Код Библиотечных Классов (2)
Пример Адаптера: Код Целевого Класса
Адаптер: Решение
Адаптер Класса
Адаптер Объекта
Принцип Разделения Интерфейсов
«Загрязнение» Интерфейса
Взаимодействие TimedDoor & Timer
Анализ
Жесткость и Вязкость Решения
Решение: Использование Адаптера
Решение: Использование Адаптера (2)
Анализ
Решение: Множественное Наследование
Заключение
113.43K
Категория: ПрограммированиеПрограммирование

Принцип единственной обязанности. Адаптер. Принцип разделения интерфейсов

1. Осенний семестр 2017 Преподаватель: асс. каф. Чуканов В.С

Peter the Great
Saint-Petersburg Рolytechnic University
Наука Программирования
Занятие №2
«Принцип единственной
обязанности. Адаптер. Принцип
разделения интерфейсов»
Осенний семестр 2017
Преподаватель: асс. каф. Чуканов В.С
11.09.17

2. Содержание

Принцип единственной обязанности
Адаптер
Соединение интерфейсов
Адаптер класса и адаптер объекта
Применение адаптера
Принцип разделения интерфейсов
Заключение
2

3. Принцип Единственной Обязанности

АТД – абстрактный тип данных
Замкнутое множество данные + методы
Single Responsibility Principle (SRP)
Класс должен иметь лишь одну причину для изменения
Обязанность = ось изменения
3
Атомарный набор методы + данные = АТД
Принцип SRP: каждый класс реализует 1 АТД

4. SRP: Пример Rectangle

Rectangle
Используется для расчета площади и визуализации
Две обязанности = 2 АТД
Какие проблемы это может вызвать?
Приложение
выч.
геометрии
Rectangle
+ draw()
+area(): double
Графический
интерфейс
4
Графическое
приложение

5. SRP: Решение Примера Rectangle

Избыточная связь между приложениями выч. геом и
визуализации
Изменение в модуле выч. геом могло привести к
необходимости пересобирать модуль визуализации
Решение: разделить обязанности Rectangle по двум
классам
5
GeometricRectangle
Rectangle
+area(): double
+ draw()
Приложение
выч.
геометрии
Графический
интерфейс
Графическое
приложение

6. SRP: Пример Modem

Modem
Интерфейс сетевого взаимодействия
class Modem
{
public:
virtual
virtual
virtual
virtual
};
6
void
void
void
char
dial(std::string) = 0;
hangup() = 0;
send(char) = 0;
receive() = 0;

7. SRP: Пример Modem

Разделение обязанностей не всегда является необходимым
Особенности оборудования/ОС могут обуславливать слияние
обязанностей в одном классе
Определяется постановкой задачи и возможностью изменения
обязанностей независимо
Разделение обязанностей может быть реализовано с помощью
паттернов Фасад (Facade) и Заместитель (Proxy)
DataChannel
+ send(:char)
+receive():char
Connection
+ dial(:string)
+hangup()
Реализация
интерфейса
Modem
7

8. Шаблон Проектирования: Адаптер

Позволяет повторно использовать реализованную
функциональность при несовместимых
интерфейсах
Технически – переадресация вызова от одного
интерфейса к другому
Пример
8
Имеется реализованный в библиотеке класс для
генерации случайных, равномерно распределенных
чисел в интервале [0, 1]
Необходимо написать класс для генерации чисел в
интервале [0, 100]

9. Пример Адаптера: Код Библиотечных Классов

class ValueGenerator
{
public:
virtual float getNormalizedValue() const = 0;
};
class ValueGeneratorStupid : public ValueGenerator
{
public:
virtual float getNormalizedValue() const
{
return static_cast<float> (rand() % 10000) * 0.0001f;
}
};
9

10. Пример Адаптера: Код Библиотечных Классов (2)

class ValueGeneratorUniform : public ValueGenerator
{
public:
virtual float getNormalizedValue() const
{
//! C++11 stuff
std::random_device device;
std::mt19937 generator(device());
std::uniform_real_distribution<float> distr(0.0f, 1.0f);
return distr(generator);
}
};
В С++11 существует множество генераторов случайных
чисел, в т.ч. с равномерным распределением в заданном
интервале
10

11. Пример Адаптера: Код Целевого Класса

Решение
Объявляем интерфейс класса для генерации чисел в
заданном диапазоне
Объявляем виртуальный метод getValue()
Создаем наследника с реализацией виртуального
метода getValue()
Реализация может адаптировать как
интерфейсный метод, так и быть привязанной к
одной выбранной реализации
11
Адаптер объекта VS адаптер класса

12. Адаптер: Решение

Интерфейс класса
class Value100Generator
{
public:
virtual float getValue() = 0;
};
12

13. Адаптер Класса

Реализация адаптера
class Value100GeneratorAdapterClassBased:
public Value100Generator,
private ValueGeneratorUniform //Inherit implementation
{
public:
//! Must return random value from range 1..100
virtual float getValue()
{
return getNormalizedValue() * 100.0f;
}
};
13

14. Адаптер Объекта

Реализация адаптера
class Value100GeneratorAdapterObjectBased:
public Value100Generator
{
public:
Value100GeneratorAdapterObjectBased(ValueGenerator
*generator): m_generator(generator) {}
//! Must return random value from range 1..100
virtual float getValue()
{
return m_generator->getNormalizedValue() * 100.0f;
}
private:
const ValueGenerator *m_generator;
};
14

15. Принцип Разделения Интерфейсов

«Жирные» интерфейсы
Состоят из множества несцепленных функций
Реализуют более 1 АТД
Перегруженные функциями интерфейсы приводят к
жесткости, хрупкости и тд
Рассмотрим класс Door
class Door
{
public:
virtual void Lock() = 0;
virtual void UnLock() = 0;
virtual bool IsDoorOpen() = 0;
};
15

16. «Загрязнение» Интерфейса

Новое требование
Новый тип дверей: вызывают сигнал тревоги, если слишком
долго открыты
Класс TimedDoor
Поддержка абстракции TimerClient
Класс, реагирующий на истечение времени таймера
class TimerClient
{
public:
virtual void TimeOut() = 0;
};
class Timer
{
public:
void Register(int timeout, TimerClient *client);
};
16

17. Взаимодействие TimedDoor & Timer

Взаимодействие TimedDoor & Timer
TimerClient
Timer
0..*
Door
TimedDoor
17

18. Анализ

Door теперь зависит от
TimerClient
18
Изначальная абстракция
Door не имела подобной
зависимости
Реализации Door, не
требующие отсчета
времени, будут обязаны
реализовать метод
TimeOut()
TimerClient
Timer
0..*
Door
TimedDoor

19. Жесткость и Вязкость Решения

Новое требование – регистрация более одного
запроса на истечение времени
Любое изменение TimerClient повлечет изменения
во всех объектах Door
class TimerClient
{
public:
virtual void TimeOut(int timeOutId) = 0;
};
class Timer
{
public:
void Register(int timeout, int timeOutId,
TimerClient *client);
};
19

20. Решение: Использование Адаптера

Адаптер
Разделяет иерархии Door & TimerClient
«Транслирует» интерфейс TimerClient в TimedDoor
Timer
0..*
TimerClient
+TimeOut()
Door
DoorTimerAdapter
+TimeOut()
TimedDoor
+DoorTimeOut()
Создает
20

21. Решение: Использование Адаптера (2)

class TimedDoor : public Door
{
public:
virtual void DoorTimeOut(int timeOutId);
};
class DoorTimerAdapter : public TimerClient
{
public:
DoorTimerAdapter(TimedDoor &door) : m_door(&door) { }
virtual void TimeOut(int timeOutId)
{
m_door->DoorTimeOut(timeOutId);
}
private:
TimedDoor *m_door;
};
21

22. Анализ

Каждый вызов регистрации запроса на таймер
вынуждает создать объект-адаптер
DoorTimerAdapter doorAdapter(door);
timer->Register(timeOut, timeOutId,
&doorAdapter);
Какое еще существует решение?
22

23. Решение: Множественное Наследование

Timer
0..*
TimerClient
+TimeOut()
Door
TimedDoor
+ TimeOut()
class TimedDoor : public Door, public TimerClient
{
public:
virtual void TimeOut(int timeOutId);
};
23

24. Заключение

Принцип единственной обязанности
Адаптер
Каждый класс должен реализовывать лишь одну «ось
изменения»
Паттерн проектирования для улучшения коэф.
повторного использования кода
Переадресовывает операции одного интерфейса в
другой
Принцип разделения интерфейсов
24
«Жирные» интерфейсы приводят к вязкости и
жесткости
Интерфейсы могут быть разделены посредством
адаптера или множественного наследования
English     Русский Правила