3. Базовые шаблоны проектирования

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

В программной инженерии, начиная с книги “Банды четырех” было введено понятие паттернов (или шаблонов) проектирования, которые стали аналогом алгоритмов при кодировании, но применительно к сфере проектирования программ, показывая, как решать уже известные задачи проектирования программных систем стандартными способами.

Для решения таких проблем были разработаны паттерны проектирования:

  1. Оповещать объекты о наступлении событий, причем объекты могут отказаться в дальнейшем от такого оповещения.
  2. Наделить свои или чужие объекты новыми возможностями без модификации кода класса.
  3. Создавать уникальные объекты, существующие в единственном экземпляре.
  4. Заставить объекты имитировать интерфейс, которыми они не обладают

    и так далее.

3.1 Что такое GoF и GRASP

«Банда четырёх» в программировании (Gang of Four, сокращённо GoF) — распространённое название группы четырех авторов (Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес), выпустивших книгу Design Patterns.

GRASP (General Responsibility Assignment Software Patterns — общие паттерны распределения обязанностей) – это набор принципов проектирования по версии Крэга Лармана, автора книги “Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development”.

Принципы GRASP

  • Polymorphism (Полиморфизм)
  • Low Coupling (Низкая связность)
  • High Cohesion (Высокое зацепление)
  • Protected Variations (Устойчивый к изменениям)

Паттерны, отвечающие принципам (будут частично рассмотрены далее)

  • Information Expert (Информационные эксперт)
  • Creator (Создатель)
  • Controller (Контроллер)
  • Pure Fabrication (Чистая выдумка или чистое синтезирование)
  • Indirection (Посредник)

Рассмотрим основные принципы GRASP при разработке дизайна системы.

Polymorphism (Полиморфизм)

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

Все альтернативные реализации приводятся к общему интерфейсу.

Low Coupling (Низкая связность)

Если объекты в приложении сильно связаны, то любое изменение приводит к изменениям во всех связанных объектах. А это неудобно и порождает баги. Вот поэтому необходимо, чтобы код был слабо связан и зависел от абстракций. Например, если наш класс Sale (продажи) реализует интерфейс ISale и другие объекты зависят именно от ISale, т.е. от абстракции, то когда мы захотим внести изменения касательно Sale – нам нужно будет всего лишь подменить реализацию.

Программируйте на основе абстракций (интерфейс, абстрактный класс и т.п.), а не реализаций.

High Cohesion (Высокое зацепление) относится к слабой связанности, они идут в паре и одно всегда приводит к другому. Класс должен иметь какую-то одну ответственность.

Например, Sale (продажа) обладает всеми ответственностями, которые касаются продаж, например, вычисление общей суммы – Total. Но давайте представим, что мы совершили оплошность и привнесли в Sale еще такую ответственность как Payment (платеж). Что получится? Получится что одни члены класса, которые касаются Sale, будут между собой достаточно тесно связанны, и также члены класса, которые оперируют с Payment, между собой будут тесно связаны. Но в целом, сцепленность класса SaleAndPayment будет низкой, ведь по сути мы имеем дело с двумя обособленными частями в одном целом. И резонно будет провести рефакторинг и разделить класс SaleAndPayment на Sale и Payment, которые внутри будут тесно связаны.

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

Protected Variations (Устойчивый к изменениям)

Как спроектировать объекты, чтобы изменения в объекте не затрагивали других? Как избежать ситуации когда при изменении кода объекта придется вносить изменения в множество других объектов системы?

Мы пришли к выводу, что нужно использовать low coupling. Но суть данного принципа немного в другом. Суть в том, чтобы определить “точки изменений” и зафиксировать их в абстракции (интерфейсе). “Точки изменений” – не что иное как наши объекты, которые могут меняться.

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

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

3.2 Классификация шаблонов проектирования

Базовые шаблоны:

  • Delegation и Delegation Event Model
  • Interface и Abstract Superclass
  • Proxy или Surrogate

3.3 Делегирование (Delegation)

Задача
Построить игру, в которой есть автомобили. Автомобили умеют передвигаться по земле на колесах.

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

Расширение
Добавим роботов, которые умеют передвигаться по земле по-разному (некоторые на колесах, некоторые на ногах). Роботы летать не умеют, но некоторые из них умеют прыгать

Проблема
Наследование как основной принцип создания устройств, приводит к огромному количеству разнотипных вариантов.

Выход – делегировать выполнение другому классу.

«Задача о машинках»

// интерфейсы действий

#ifndef __ACTIONS
#define __ACTIONS
class IFlyAction{
public:
virtual void fly() =0; // интерфейс не имеет реализации
};
class IJumpAction{
public:
virtual void jump() =0; // интерфейс не имеет реализации
};
class IDriveAction{
public:
virtual void drive() =0; // интерфейс не имеет реализации
};
#endif

// классы делегатов

#ifndef __BEHAVIOUR
#define __BEHAVIOUR
#include <stdio.h>
#include <stdlib.h>
#include "actions.h"

// Летаем

class FlyWithWings : public IFlyAction { // класс поведения для устройств, которые умеют летать
public:
void fly(){
printf ("I am flying!\n");
}
};
class FlyWithoutWings : public IFlyAction { // класс поведения для устройств, которые HE умеют летать
public:
void fly(){
printf ("I can not fly...\n");
}
};

// Прыгаем

class JumpWithLegs : public IJumpAction{
 // класс поведения для устройств, которые умеют прыгать
public:
void jump(){
printf ("I am jumping!\n");
}
};
class JumpWithoutLegs : public IJumpAction{
 // класс поведения для устройств, которые HE умеют прыгать
public:
void jump(){
printf ("I can not jump...\n");
}
};

// Ездим

class DriveWithWheels : public IDriveAction{
 // класс поведения для устройств на колесах, которые умеют быстро ездить
public:
void drive(){
printf ("I can drive with high velocity!\n");
}
};
class DriveWithoutWheels : public IDriveAction{
 // класс поведения для устройств, которые HE имеют колес
public:
void drive(){
printf ("I can drive slowly...\n");
}
};
#endif
#ifndef __DEVICE
#define __DEVICE
#include "behaviour.h"
#include "actions.h"
// абстрактный класс устройства
class Device{
public:
IFlyAction * flyAction;
IJumpAction * jumpAction;
IDriveAction * driveAction;
Device(){}
 ~Device();
 // делегируем выполнение операции классам поведения :
void performFly(){ flyAction->fly(); }
void performJump(){ jumpAction->jump(); }
void performDrive(){ driveAction->drive(); }
};

// конкретный класс «Самолет», который умеет летать и ездить

class Plane : public Device{
public:
Plane (){
 flyAction = new FlyWithWings();
 driveAction = new DriveWithWheels;
 jumpAction= new JumpWithoutLegs;
}
};

// конкретный класс «Автомобиль», который умеет ездить

class Car : public Device{
public:
Car(){
 flyAction = new FlyWithoutWings;
 driveAction = new DriveWithWheels;
 jumpAction = new JumpWithoutLegs;
}
};

// конкретный класс «Робот», который умеет прыгать и медленно передвигаться

class Robot : public Device{
public:
Robot(){
 flyAction = new FlyWithoutWings;
 driveAction = new DriveWithoutWheels;
 jumpAction = new JumpWithLegs;
}
};
int main(){ // создаем объекты устройств
printf(" Robots\n");
Robot robot1, robot2;
robot1.performJump();
robot1.performDrive();
robot1.performFly();
robot2.performJump();
robot2.performDrive();
robot2.performFly();
 // добавим колеса роботу номер 2 :
robot2.driveAction = new DriveWithWheels;
printf("\n\n Robot 1 after modification\n");
robot1.performDrive();
robot2.performDrive();

Результат работы программы

Массив устройств

printf("\n\n List of devices \n");
Device device[10] = {robot1, robot2, car1, plane1};
for (int index =0; index <4; index ++)
 device[index].performDrive();

Результат работы программы

«Задача о множестве действий у одного объекта»

Задача
Есть несколько видов спорта. Надо построить класс спортсмена, который занимается определенным видом спорта.

Расширение
Можем добавить новые виды спорта.

Расширение
Один спортсмен может заниматься разными видами спорта.

Список делегатов у объекта

// интерфейсы действий

#ifndef __MOTION
#define __MOTION
class IMotion { // интерфейс
public:
virtual void doMotion() = 0;
};
// здесь конкретные делегаты
…
#endif

// конкретные делегаты:

class SwimmingMotion : public IMotion {
public:
void doMotion(){printf("A am swiming! \n");}
};
class FootballMotion : public IMotion {
public:
void doMotion(){printf("I play football! \n");}
};
class VolleyballMotion : public IMotion {
public:
void doMotion(){printf("I play volleyball! \n");}
};

Подписка

typedef IMotion * ptrMotion;

// класс спортсмен

class Sportsmen{
private:
vector <ptrMotion> items;
public:
void performAllMotions();
void addMotion(Motion *newMotion);
Sportsmen(){ items.clear(); }
 ~Sportsmen();
};
void perfomAllMotions(){
for (vector<ptrMotion>::iterator it = items.begin();
 it != items.end(); it++) {
(*it)->doMotion();
}
}
void addMotion(Motion *newMotion){
items.push_back(newMotion);
}
Sportsmen * Petr = new Sportsmen();
Sportsmen * Vera = new Sportsmen();
SwimmingMotion *typeSwim = new SwimmingMotion;
FootballMotion *typeFoot = new FootballMotion;
VolleyballMotion *typeVoll = new VolleyballMotion;
printf("\n\n Petr:\n");
Petr->addMotion(typeSwim);
Petr->addMotion(typeFoot);
Petr->performAllMotions();
printf("\n\n Vera:\n");
Vera->addMotion(typeSwim);
Vera->addMotion(typeVoll);
Vera->performAllMotions();

Результат работы программы

3.4 Модель событий, основанная на делегатах (Delegation Event Model)

Event Model базируется на концепции «Event Source» / «Event Listeners». Любой объект, который намеревается получать сообщения, называется Event Listener (слушатель событий), и любой объект, который генерирует эти сообщения, называется Event Source (источник событий).

Конкретный объект Event Source имеет список объектов, которые должны обрабатывать его сообщения. Объект Event Source имеет метод, который позволяет слушателям добавить или удалить их из такого списка. Когда Event Source генерирует сообщение, он сообщает всем слушателям, что событие произошло.

Сообщение поступает от «Source” к «Listener» с помощью вызова метода слушателя.

3.5 Заместитель (Proxy)

Заместитель – суррогат настоящего объекта. Заместитель прикидывается настоящим объектом, а на самом деле или взаимодействует с ним или просто работает «по умолчанию».

Типы заместителей:

  • Удаленный заместитель. При сетевой реализации заместитель действует как представитель удаленного объекта.
  • Виртуальный заместитель. Управляет доступом к ресурсу, создание которого требует больших затрат. Заместитель создает объект только тогда, когда это необходимо.
  • Защитный заместитель. Контролирует доступ к ресурсу в соответствии с системой привилегий.
  • Фильтрующий заместитель. Управляет доступом к группам ресурсов.
  • Синхронизирующий заместитель. Обеспечивает безопасный доступ из нескольких потоков к объекту.

Пример

class Math { // класс, для которого создадим Proxy
public:
 virtual void sum()=0;
 virtual void sub()=0;
 virtual void mult()=0;
 virtual void div()=0;
};
class M1 : public Math {
public:
 int a,b;
 virtual void sum() { cout << "Sum: " << a+b << endl; }
 virtual void sub() { cout << "Sub: " << a-b << endl; }
 virtual void mult() { cout << "Mult: " << a*b << endl; }
 virtual void div() {
 if( b == 0) { cout << "Div by zero!\n";
 } else {
 cout << "Div: " << a*b << endl;
 }
 }
 M1(int inA, int inB) { a = inA; b = inB; }
};
class ProxyM1 : public Math {
private:
 M1 *prox;
 void log() { cout << "a=" << prox->a << ", b=" << prox->b << endl; }
public:
 virtual void sum() { log(); prox->sum(); }
 virtual void sub() { log(); prox->sub(); }
 virtual void mult() { log(); prox->mult(); }
 virtual void div() { cout << "No div!" << endl; }
 ProxyM1(int inA, int inB) {
 prox = new M1(inA,inB); // здесь Proxy создает реальный объект М1
 }
 ~ProxyM1() {
 delete prox;
 }
};
int main(){
 Math *t = new M1(6,0);
 Math *p = new ProxyM1(6,0);
 cout << "M1\n";
 t->sum();
 t->sub();
 t->mult();
 t->div();
 cout << "\nProxyM1\n";
 p->sum();
 p->sub();
 p->mult();
 p->div();
 delete p;
 delete t;
 return 0;
}

Пример работы

3.6 Задания к разделу 3

  1. Рассмотреть задачу в неформальной постановке
  2. Перечислить список возможных расширений постановки задачи (новые свойства, действия, объекты, взаимодействия и т.д.)
  3. Сформировать перечень интерфейсов
  4. Сформировать перечень классов и их обязанности
  5. Делегировать обязанности классам-делегатам
  6. Построить диаграмму классов
  7. Проверить выполнение принципов низкой связности и высокого зацепления.
  8. Реализовать систему
  9. Представить варианты реализации расширения системы

Примерные варианты задач:

  1. Система покупок в Интернет-магазине
  2. Игра — лабиринт с препятствиями, призами, жителями
  3. Система доставки заказов с использованием самолетов, автомобилей, поездов
  4. Зоопарк с различными видами обезьян в вольерах, способных учиться навыкам, плавать, лазить по камням, есть бананы.
  5. Система визуализации заключения контрактов на продукты питания (пшеницу, сыры и т.д.)
  6. Игра ”Дюна”
  7. Танковое сражение
  8. Многофункциональный электронный переводчик
  9. Движение автотранспорта по дорогам города
  10. Завод по производству красок и растворителей
  11. Система управления и учета компьютеров и сетей в компании
  12. Система банкомата
  13. Система управления автоматом по продаже бутербродов
  14. Система управления температурным режимом в автоматизированной теплице
  15. Система бронирования билетов на театрально-зрелищные представления
  16. Система управления роботом-луноходом
Создайте подобный сайт на WordPress.com
Начало работы