Прототип. Структурные паттерны

Содержание

Слайд 2

Прототип.

Суть паттерна. Прототип — это порождающий паттерн проектирования, который позволяет копировать объекты, не

Прототип. Суть паттерна. Прототип — это порождающий паттерн проектирования, который позволяет копировать
вдаваясь в подробности их реализации.
Проблема. У вас есть объект, который нужно скопировать. Как это сделать? Нужно создать пустой объект такого же класса, а затем поочерёдно скопировать значения всех полей из старого объекта в новый.
Не каждый объект удастся скопировать таким образом, ведь часть его состояния может быть приватной, а значит — недоступной для остального кода программы.
Но есть и другая проблема. Копирующий код станет зависим от классов копируемых объектов. Ведь, чтобы перебрать все поля объекта, нужно привязаться к его классу. Из-за этого вы не сможете копировать объекты, зная только их интерфейсы, а не конкретные классы.

Слайд 3

Решение. Паттерн Прототип поручает создание копий самим копируемым объектам. Он вводит общий

Решение. Паттерн Прототип поручает создание копий самим копируемым объектам. Он вводит общий
интерфейс для всех объектов, поддерживающих клонирование. Это позволяет копировать объекты, не привязываясь к их конкретным классам. Обычно такой интерфейс имеет всего один метод clone.
Реализация этого метода в разных классах очень схожа. Метод создаёт новый объект текущего класса и копирует в него значения всех полей собственного объекта. Так получится скопировать даже приватные поля, так как большинство языков программирования разрешает доступ к приватным полям любого объекта текущего класса.
Объект, который копируют, называется прототипом (откуда и название паттерна). Когда объекты программы содержат сотни полей и тысячи возможных конфигураций, прототипы могут служить своеобразной альтернативой созданию подклассов.

Слайд 4

В вашей игре используется покадровая анимация. Каждая анимация может встречаться несколько раз.

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

Слайд 5

Реализация паттерна

class Meal {
public:
virtual ~Meal();
virtual void eat() = 0;

Реализация паттерна class Meal { public: virtual ~Meal(); virtual void eat() =

virtual Meal *clone() const = 0;
//...
};
class Spaghetti : public Meal {
public:
Spaghetti( const Spaghetti &);
void eat();
Spaghetti *clone() const { return new Spaghetti( *this ); }
//... };

Слайд 6

Применимость

Когда ваш код не должен зависеть от классов копируемых объектов.
Когда вы имеете

Применимость Когда ваш код не должен зависеть от классов копируемых объектов. Когда
уйму подклассов, которые отличаются начальными значениями полей. Кто-то мог создать все эти классы, чтобы иметь возможность легко порождать объекты с определённой конфигурацией.

Слайд 7

Преимущества и недостатки

«+»:
Позволяет клонировать объекты, не привязываясь к их конкретным классам.
 Меньше повторяющегося

Преимущества и недостатки «+»: Позволяет клонировать объекты, не привязываясь к их конкретным
кода инициализации объектов.
 Ускоряет создание объектов.
 Альтернатива созданию подклассов для конструирования сложных объектов.
«-»
Сложно клонировать составные объекты, имеющие ссылки на другие объекты.

Слайд 8

Структурные паттерны

Структурные шаблоны — шаблоны проектирования, в которых рассматривается вопрос о том, как из классов и объектов

Структурные паттерны Структурные шаблоны — шаблоны проектирования, в которых рассматривается вопрос о
образуются более крупные структуры. При этом могут использоваться следующие механизмы:
Наследование - базовый класс определяет интерфейс, а подклассы - реализацию. Структуры на основе наследования получаются статичными.
Композиция - структуры строятся путем объединения объектов некоторых классов. Композиция позволяет получать структуры, которые можно изменять во время выполнения.

Слайд 9

Список структурных паттернов

адаптер (Adapter);
мост (Bridge);
компоновщик (Composite);
декоратор (Decorator);
фасад (Facade);
приспособленец (Flyweight);
заместитель (Proxy).

Список структурных паттернов адаптер (Adapter); мост (Bridge); компоновщик (Composite); декоратор (Decorator); фасад

Слайд 10

Адаптер

Суть паттерна. Адаптер — это структурный паттерн проектирования, который позволяет объектам с несовместимыми

Адаптер Суть паттерна. Адаптер — это структурный паттерн проектирования, который позволяет объектам
интерфейсами работать вместе.
Проблема. Представим, что у вас на карте памяти есть какие-то изображения и вам надо перенести их на ваш компьютер. Чтобы это сделать, вам нужен какой-то адаптер, который совместим с портами вашего компьютера. В этом случае карт-ридер — это адаптер. Другим примером будет блок питания. Вилку с тремя ножками нельзя вставить в розетку с двумя отверстиями. Для того, чтобы она подошла, надо использовать адаптер. Ещё одним примером будет переводчик, переводящий слова одного человека для другого.

Слайд 11

Решение. Вы можете создать адаптер. Это объект-переводчик, который трансформирует интерфейс или данные одного

Решение. Вы можете создать адаптер. Это объект-переводчик, который трансформирует интерфейс или данные
объекта в такой вид, чтобы он стал понятен другому объекту.
При этом адаптер оборачивает один из объектов, так что другой объект даже не знает о наличии первого. Например, вы можете обернуть объект, работающий в метрах, адаптером, который бы конвертировал данные в футы.
Адаптеры могут не только переводить данные из одного формата в другой, но и помогать объектам с разными интерфейсами работать сообща. Это работает так:
Адаптер имеет интерфейс, который совместим с одним из объектов.
Поэтому этот объект может свободно вызывать методы адаптера.
Адаптер получает эти вызовы и перенаправляет их второму объекту, но уже в том формате и последовательности, которые понятны второму объекту.
Иногда возможно создать даже двухсторонний адаптер, который работал бы в обе стороны.

Слайд 12

Реализация. Задание

Пусть мы разрабатываем систему климат-контроля, предназначенной для автоматического поддержания температуры окружающего

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

Слайд 13

Классическая реализация

#include
using namespace std;
// Уже существующий класс температурного датчика окружающей среды
class

Классическая реализация #include using namespace std; // Уже существующий класс температурного датчика
FahrenheitSensor
{
public:
// Получить показания температуры в градусах Фаренгейта
float getFahrenheitTemp() {
float t = 32.0;
// ... какой то код
return t;
}
};
class Sensor
{
public:
virtual ~Sensor() {}
virtual float getTemperature() = 0;
};

Слайд 14

class Adapter : public Sensor
{
public:
Adapter( FahrenheitSensor* p ) :

class Adapter : public Sensor { public: Adapter( FahrenheitSensor* p ) :
p_fsensor(p) {}
~Adapter() {
delete p_fsensor;
}
float getTemperature() {
return (p_fsensor->getFahrenheitTemp()-32.0)*5.0/9.0;
}
private:
FahrenheitSensor* p_fsensor;
};
int main()
{
Sensor* p = new Adapter( new FahrenheitSensor);
cout << "Celsius temperature = " << p->getTemperature() << endl;
delete p;
return 0;
}

Слайд 15

Закрытое наследование

Пусть наш температурный датчик системы климат-контроля поддерживает функцию юстировки для получения

Закрытое наследование Пусть наш температурный датчик системы климат-контроля поддерживает функцию юстировки для
более точных показаний. Эта функция не является обязательной для использования, поэтому соответствующий метод adjust() объявлен разработчиками защищенным в существующем классе FahrenheitSensor.
Разрабатываемая нами система должна поддерживать настройку измерений. Так как доступ к защищенному методу через указатель или ссылку запрещен, то классическая реализация паттерна Adapter здесь уже не подходит. Единственное решение - наследовать от класса FahrenheitSensor. Интерфейс этого класса должен оставаться недоступным пользователю, поэтому наследование должно быть закрытым.

Слайд 16

class FahrenheitSensor
{
public:
float getFahrenheitTemp() {
float t = 32.0;
// ...

class FahrenheitSensor { public: float getFahrenheitTemp() { float t = 32.0; //

return t;
}
protected:
void adjust() {} // Настройка датчика (защищенный метод)
};
class Sensor
{
public:
virtual ~Sensor() {}
virtual float getTemperature() = 0;
virtual void adjust() = 0;
};

Слайд 17

class Adapter : public Sensor, private FahrenheitSensor
{
public:
Adapter() { }

class Adapter : public Sensor, private FahrenheitSensor { public: Adapter() { }
float getTemperature() {
return (getFahrenheitTemp()-32.0)*5.0/9.0;
}
void adjust() {
FahrenheitSensor::adjust();
}
};
int main()
{
Sensor * p = new Adapter();
p->adjust();
cout << "Celsius temperature = " << p->getTemperature() << endl;
delete p;
return 0;
}

Слайд 18

Применимость

Когда вы хотите использовать сторонний класс, но его интерфейс не соответствует остальному

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

Слайд 19

Преимущества и недостатки

«+»
Отделяет и скрывает от клиента подробности преобразования различных интерфейсов.
«-»
Усложняет код

Преимущества и недостатки «+» Отделяет и скрывает от клиента подробности преобразования различных
программы из-за введения дополнительных классов.
Задача преобразования интерфейсов может оказаться непростой в случае, если клиентские вызовы и (или) передаваемые параметры не имеют функционального соответствия в адаптируемом объекте.

Слайд 20

Мост

Суть паттерна. Мост — это структурный паттерн проектирования, который разделяет один или несколько

Мост Суть паттерна. Мост — это структурный паттерн проектирования, который разделяет один
классов на две отдельные иерархии — абстракцию и реализацию, позволяя изменять их независимо друг от друга.
Проблема. У вас есть класс геометрических Фигур, который имеет подклассы Круг и Квадрат. Вы хотите расширить иерархию фигур по цвету, то есть иметь Красные и Синие фигуры. Но чтобы всё это объединить, вам придётся создать 4 комбинации подклассов, вроде СиниеКруги и КрасныеКвадраты. При добавлении новых видов фигур и цветов количество комбинаций будет расти в геометрической прогрессии. Например, чтобы ввести в программу фигуры треугольников, придётся создать сразу два новых подкласса треугольников под каждый цвет. После этого новый цвет потребует создания уже трёх классов для всех видов фигур. Чем дальше, тем хуже.

Слайд 21

Решение. Корень проблемы заключается в том, что мы пытаемся расширить классы фигур

Решение. Корень проблемы заключается в том, что мы пытаемся расширить классы фигур
сразу в двух независимых плоскостях — по виду и по цвету. Именно это приводит к разрастанию дерева классов.
Паттерн Мост предлагает заменить наследование композицией. Для этого нужно выделить одну из таких «плоскостей» в отдельную иерархию и ссылаться на объект этой иерархии, вместо хранения его состояния и поведения внутри одного класса.
Таким образом, мы можем сделать Цвет отдельным классом с подклассами Красный и Синий. Класс Фигур получит ссылку на объект Цвета и сможет делегировать ему работу, если потребуется. Такая связь и станет мостом между Фигурами и Цветом. При добавлении новых классов цветов не потребуется трогать классы фигур и наоборот.

Слайд 22

Абстракция и Реализация

Абстракция  — это образный слой управления чем-либо. Он не делает работу

Абстракция и Реализация Абстракция — это образный слой управления чем-либо. Он не
самостоятельно, а делегирует её слою реализации (иногда называемому платформой).
Если говорить о реальных программах, то абстракцией может выступать графический интерфейс программы (GUI), а реализацией — низкоуровневый код операционной системы (API), к которому графический интерфейс обращается по реакции на действия пользователя.
Можно развивать программу в двух разных направлениях:
иметь несколько видов GUI (например, для простых пользователей и администраторов);
поддерживать много видов API (например, работать под Windows, Linux и MacOS).
Такая программа может выглядеть как один большой клубок кода, в котором намешаны условные операторы слоёв GUI и API.

Слайд 23

Вы можете попытаться структурировать этот хаос, создав для каждой вариации интерфейса-платформы свои

Вы можете попытаться структурировать этот хаос, создав для каждой вариации интерфейса-платформы свои
подклассы. Но такой подход приведёт к росту классов комбинаций, и с каждой новой платформой их будет всё больше.
Мы можем решить эту проблему, применив Мост. Паттерн предлагает распутать этот код, разделив его на две части:
Абстракцию: слой графического интерфейса приложения.
Реализацию: слой взаимодействия с операционной системой.
Абстракция будет делегировать работу одному из объектов реализаций. Причём, реализации можно будет взаимозаменять, но только при условии, что все они будут следовать общему интерфейсу.
Таким образом, вы сможете изменять графический интерфейс приложения, не трогая низкоуровневый код работы с операционной системой. И наоборот, вы сможете добавлять поддержку новых операционных систем, создавая подклассы реализации, без необходимости менять классы графического интерфейса.

Слайд 24

#include
using namespace std;
class Drawer {
public:
virtual void drawCircle(int x, int y,

#include using namespace std; class Drawer { public: virtual void drawCircle(int x,
int radius) = 0;
};
class SmallCircleDrawer : public Drawer {
public:
const double radiusMultiplier = 0.25;
void drawCircle(int x, int y, int radius) override
{
cout << "Small circle center " << x << ", " << y << " radius = " <<
radius*radiusMultiplier << endl;
}
};
class LargeCircleDrawer : public Drawer {
public:
const double radiusMultiplier = 10;
void drawCircle(int x, int y, int radius) override
{
cout << "Large circle center " << x << ", " << y << " radius = " <<
radius*radiusMultiplier << endl;
}
};

Слайд 25

class Shape {
protected:
Drawer* drawer;
public:
Shape(Drawer* drw) {drawer = drw;}
Shape() {}

class Shape { protected: Drawer* drawer; public: Shape(Drawer* drw) {drawer = drw;}
virtual void draw() = 0;
virtual void enlargeRadius(int multiplier) = 0;
};
class Circle : public Shape {
int x, y, radius;
public:
Circle(int _x, int _y, int _radius, Drawer* drw)
{
drawer = drw;
setX(_x);
setY(_y);
setRadius(_radius);
}
void draw() override {
drawer->drawCircle(x, y, radius);
}
void enlargeRadius(int multiplier) override {radius *= multiplier;}
void setX(int _x) {x = _x;}
void setY(int _y) {y = _y;}
void setRadius(int _radius) {radius = _radius; }
};

Слайд 26

int main(int argc, char *argv[])
{
Shape* shapes[2] = {
new Circle(5,10,10, new

int main(int argc, char *argv[]) { Shape* shapes[2] = { new Circle(5,10,10,
LargeCircleDrawer()),
new Circle(20,30,100, new SmallCircleDrawer())};
for (int i =0 ; i < 2; i++)
{
shapes[i]->draw();
}
return 0;
}

Слайд 27

Применимость

Когда вы хотите разделить монолитный класс, который содержит несколько различных реализаций какой-то

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

Слайд 28

Преимущества и недостатки

«+»
Позволяет строить платформо-независимые программы.
 Скрывает лишние или опасные детали реализации от

Преимущества и недостатки «+» Позволяет строить платформо-независимые программы. Скрывает лишние или опасные
клиентского кода.
 Реализует принцип открытости/закрытости.
«-»
Усложняет код программы из-за введения дополнительных классов.

Слайд 29

Фасад

Суть паттерна. Фасад — это структурный паттерн проектирования, который предоставляет простой интерфейс к

Фасад Суть паттерна. Фасад — это структурный паттерн проектирования, который предоставляет простой
сложной системе классов, библиотеке или фреймворку.
Проблема. Вашему коду приходится работать с большим количеством объектов некой сложной библиотеки или фреймворка. Вы должны самостоятельно инициализировать эти объекты, следить за правильным порядком зависимостей и так далее.
В результате бизнес-логика ваших классов тесно переплетается с деталями реализации сторонних классов. Такой код довольно сложно понимать и поддерживать.

Слайд 30

Решение. Фасад — это простой интерфейс для работы со сложной подсистемой, содержащей множество

Решение. Фасад — это простой интерфейс для работы со сложной подсистемой, содержащей
классов. Фасад может иметь урезанный интерфейс, не имеющий 100% функциональности, которой можно достичь, используя сложную подсистему напрямую. Но он предоставляет именно те фичи, которые нужны клиенту, и скрывает все остальные.
Фасад полезен, если вы используете какую-то сложную библиотеку со множеством подвижных частей, но вам нужна только часть её возможностей.
К примеру, программа, заливающая видео котиков в социальные сети, может использовать профессиональную библиотеку сжатия видео. Но все, что нужно клиентскому коду этой программы — простой метод encode(filename, format). Создав класс с таким методом, вы реализуете свой первый фасад.

Слайд 31

#include
#include
#include
#include
/** Абстрактный музыкант - не является обязательной составляющей

#include #include #include #include /** Абстрактный музыкант - не является обязательной составляющей
паттерна, введен для упрощения кода */
class Musician {
const char* name;
public:
Musician(std::string_view name) {
this->name = name.data();
}
virtual ~Musician() =default;
protected:
void output(std::string_view text) {
std::cout << this->name << " " << text << "." << std::endl;
}
};

Слайд 32

class Vocalist: public Musician {
public:
Vocalist(std::string_view name): Musician(name) {}
void singCouplet(int coupletNumber)

class Vocalist: public Musician { public: Vocalist(std::string_view name): Musician(name) {} void singCouplet(int
{
std::string text = "спел куплет №";
text += std::to_string(coupletNumber);
output(text); }
void singChorus() {
output("спел припев");}
};
class Guitarist: public Musician {
public:
Guitarist(std::string_view name): Musician(name) {}
void playCoolOpening() {
output("начинает с крутого вступления");}
void playCoolRiffs() {
output("играет крутые риффы"); }
void playAnotherCoolRiffs() {
output("играет другие крутые риффы"); }
void playIncrediblyCoolSolo() {
output("выдает невероятно крутое соло"); }
void playFinalAccord() {
output("заканчивает песню мощным аккордом"); }
};

Слайд 33

class Bassist: public Musician {
public:
Bassist(std::string_view name): Musician(name) {}
void followTheDrums() {

class Bassist: public Musician { public: Bassist(std::string_view name): Musician(name) {} void followTheDrums()
output("следует за барабанами");
}
void changeRhythm(std::string_view type) {
std::string text = ("перешел на ритм ");
text += type;
text += "a";
output(text);
}
void stopPlaying() {
output("заканчивает играть");
}
};
class Drummer: public Musician {
public:
Drummer(std::string_view name): Musician(name) {}
void startPlaying() {
output("начинает играть");
}
void stopPlaying() {
output("заканчивает играть");
}
};

Слайд 34

/** Фасад, в данном случае - знаменитая рок-группа */
class BlackSabbath {
std::unique_ptr

/** Фасад, в данном случае - знаменитая рок-группа */ class BlackSabbath {
vocalist;
std::unique_ptr guitarist;
std::unique_ptr bassist;
std::unique_ptr drummer;
public:
BlackSabbath() {
vocalist = std::make_unique("Оззи Осборн");
guitarist = std::make_unique("Тони Айомми");
bassist = std::make_unique("Гизер Батлер");
drummer = std::make_unique("Билл Уорд");
}

Слайд 35

void playCoolSong() {
guitarist->playCoolOpening();
drummer->startPlaying();
bassist->followTheDrums();
guitarist->playCoolRiffs();
vocalist->singCouplet(1);
bassist->changeRhythm("припев");
guitarist->playAnotherCoolRiffs();
vocalist->singChorus();
bassist->changeRhythm("куплет");

void playCoolSong() { guitarist->playCoolOpening(); drummer->startPlaying(); bassist->followTheDrums(); guitarist->playCoolRiffs(); vocalist->singCouplet(1); bassist->changeRhythm("припев"); guitarist->playAnotherCoolRiffs(); vocalist->singChorus(); bassist->changeRhythm("куплет");
guitarist->playCoolRiffs();
vocalist->singCouplet(2);
bassist->changeRhythm("припев");
guitarist->playAnotherCoolRiffs();
vocalist->singChorus();
bassist->changeRhythm("куплет");
guitarist->playIncrediblyCoolSolo();
guitarist->playCoolRiffs();
vocalist->singCouplet(3);
bassist->changeRhythm("припев");
guitarist->playAnotherCoolRiffs();
vocalist->singChorus();
bassist->changeRhythm("куплет");
guitarist->playCoolRiffs();
bassist->stopPlaying();
drummer->stopPlaying();
guitarist->playFinalAccord();
}
};

Слайд 36

int main() {
std::cout << "OUTPUT:" << std::endl;
BlackSabbath* band = new

int main() { std::cout BlackSabbath* band = new BlackSabbath(); band->playCoolSong(); return 0; }
BlackSabbath();
band->playCoolSong();
return 0;
}

Слайд 37

/**
* OUTPUT:
* Тони Айомми начинает с крутого вступления.
* Билл

/** * OUTPUT: * Тони Айомми начинает с крутого вступления. * Билл
Уорд начинает играть.
* Гизер Батлер следует за барабанами.
* Тони Айомми играет крутые риффы.
* Оззи Осборн спел куплет №1.
* Гизер Батлер перешел на ритм припевa.
* Тони Айомми играет другие крутые риффы.
* Оззи Осборн спел припев.
* Гизер Батлер перешел на ритм куплетa.
* Тони Айомми играет крутые риффы.
* Оззи Осборн спел куплет №2.
* Гизер Батлер перешел на ритм припевa.
* Тони Айомми играет другие крутые риффы.
* Оззи Осборн спел припев.
* Гизер Батлер перешел на ритм куплетa.
* Тони Айомми выдает невероятно крутое соло.
* Тони Айомми играет крутые риффы.
* Оззи Осборн спел куплет №3.
* Гизер Батлер перешел на ритм припевa.
* Тони Айомми играет другие крутые риффы.
* Оззи Осборн спел припев.
* Гизер Батлер перешел на ритм куплетa.
* Тони Айомми играет крутые риффы.
* Гизер Батлер заканчивает играть.
* Билл Уорд заканчивает играть.
* Тони Айомми заканчивает песню мощным аккордом.
*/

Слайд 38

Применимость

Когда вам нужно представить простой или урезанный интерфейс к сложной подсистеме.
Когда вы

Применимость Когда вам нужно представить простой или урезанный интерфейс к сложной подсистеме.
хотите разложить подсистему на отдельные слои.
Имя файла: Прототип.-Структурные-паттерны.pptx
Количество просмотров: 48
Количество скачиваний: 0