Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Шаблоны разработки ПО. Часть 3. Шаблоны GoF

5,348 views

Published on

  • Be the first to comment

Шаблоны разработки ПО. Часть 3. Шаблоны GoF

  1. 1. Design Patterns. Шаблоны GoF Немчинский Сергей 2008
  2. 2. Типы шаблонов    Порождающие паттерны проектирования Структурные паттерны проектирования классов/объектов Паттерны проектирования поведения классов/объектов
  3. 3. Порождающие паттерны (Creational Patterns)      Абстрактная фабрика (Abstract Factory, Factory), др. название Инструментарий (Kit) Одиночка (Singleton) Прототип (Prototype) Строитель (Builder) Фабричный метод (Factory Method) или Виртуальный конструктор (Virtual Constructor)
  4. 4. Абстрактная фабрика (Abstract Factory, Factory)  Проблема   Создать семейство взаимосвязанных или взаимозависимых объектов (не специфицируя их конкретных классов). Решение  Создать абстрактный класс, в котором объявлен интерфейс для создания конкретных классов.
  5. 5. Абстрактная фабрика (Abstract Factory, Factory)
  6. 6. Abstract Factory. C++ /* * GUIFactory example */ #include <iostream> #include <string> #include <cstdlib> using namespace std; class GUIFactory { public: virtual ~GUIFactory() { } virtual Button* createButton() = 0; static GUIFactory* getFactory(); }; class Button { public: virtual ~Button() { } virtual void paint() = 0; }; class WinFactory : public GUIFactory { public: Button* createButton() { return new WinButton(); } }; class WinButton : public Button { public: void paint() { cout<<"I'm a WinButton: "; } }; class OSXButton : public Button { public: void paint() { cout<<"I'm an OSXButton: "; } }; class OSXFactory : public GUIFactory { public: Button* createButton() { return new OSXButton(); } }; int main() { GUIFactory* factory = GUIFactory::getFactory(); Button* button = factory->createButton(); button->paint(); delete factory; delete button; return 0; } // Output is either: // "I'm a WinButton:" // or: // "I'm an OSXButton:"
  7. 7. Abstract Factory. Критика  Преимущества    Изолирует конкретные классы. Поскольку "Абстрактная фабрика" инкапсулирует ответственность за создание классов и сам процесс их создания, то она изолирует клиента от деталей реализации классов. Упрощена замена "Абстрактной фабрики", поскольку она используется в приложении только один раз при инстанцировании. Недостатки  Интерфейс "Абстрактной фабрики" фиксирует набор объектов, которые можно создать. Расширение "Абстрактной фабрики" для изготовления новых объектов часто затруднительно.
  8. 8. Фабричный метод (Factory Method ) или Виртуальный конструктор (Virtual Constructor)  Проблема   Определить интерфейс для создания объекта, но оставить подклассам решение о том, какой класс инстанцировать, то есть, делегировать инстанцирование подклассам. Используется, когда:    классу заранее неизвестно, объекты каких подклассов ему нужно создавать. класс спроектирован так, чтобы объекты, которые он создаёт, специфицировались подклассами. класс делегирует свои обязанности одному из нескольких вспомогательных подклассов, и планируется локализовать знание о том, какой класс принимает эти обязанности на себя.
  9. 9. Factory Method. Решение    Абстрактный класс "Создатель" объявляет ФабричныйМетод, возвращающий объект типа "Продукт" (абстрактный класс, определяющий интерфейс обьектов, создаваемых фабричным методом). "Создатель также может определить реализацию по умолчанию ФабричногоМетода, который возвращает "КонкретныйПродукт". "КонкретныйСоздатель" замещает ФабричныйМетод, возвращающий объект "КонкретныйПродукт". "Создатель" "полагается" на свои подклассы в определении ФабричногоМетода, возвращающего объект "КонкретныйПродукт"
  10. 10. Factory Method. Диаграмма
  11. 11. Factory Method. Пример class Computer { public: virtual void Run() = 0; virtual void Stop() = 0; }; class Laptop: public Computer { public: virtual void Run(){mHibernating = false;} virtual void Stop(){mHibernating = true;} private: bool mHibernating; // Whether or not the machine is hibernating }; class Desktop: public Computer { public: virtual void Run(){mOn = true;} virtual void Stop(){mOn = false;} private: bool mOn; // Whether or not the machine has been turned on }; class ComputerFactory { public: static Computer *NewComputer(const std::string &description) { if(description == "laptop") return new Laptop; if(description == "desktop") return new Desktop; return NULL; } };
  12. 12. Factory Method. Критика  Преимущества   Избавляет проектировщика от необходимости встраивать в код зависящие от приложения классы. Недостатки  Возникает дополнительный уровень подклассов.
  13. 13. Структурные паттерны        Адаптер (Adapter) Декоратор (Decorator) или Оболочка (Wrapper) Заместитель (Proxy) или Суррогат (Surrogate) Компоновщик (Composite) Мост (Bridge), Handle (описатель) или Тело (Body) Приспособленец (Flyweight) Фасад (Facade)
  14. 14. Адаптер (Adapter)  Проблема   Необходимо обеспечить взаимодействие несовместимых интерфейсов или как создать единый устойчивый интерфейс для нескольких компонентов с разными интерфейсами. Решение  Конвертировать исходный интерфейс компонента к другому виду с помощью промежуточного объекта адаптера, то есть, добавить специальный объект с общим интерфейсом в рамках данного приложения и перенаправить связи от внешних обьектов к этому объекту - адаптеру.
  15. 15. Adapter. Диаграмма
  16. 16. Adapter. Пример // Purpose. Adapter design pattern demo // // Discussion. LegacyRectangle's interface is not compatible with the // system that would like to reuse it. An abstract base class is created // that specifies the desired interface. An "adapter" class is defined // that publicly inherits the interface of the abstract class, and // privately inherits the implementation of the legacy component. This // adapter class "maps" or "impedance matches" the new interface to the // old implementation. #include <iostream.h> typedef int Coordinate; typedef int Dimension; /////////////////////////// Desired interface /////////////////////////// class Rectangle { public: virtual void draw() = 0; }; /////////////////////////// Legacy component /////////////////////////// class LegacyRectangle { public: LegacyRectangle( Coordinate x1, Coordinate y1, Coordinate x2, Coordinate y2 ) { x1_ = x1; y1_ = y1; x2_ = x2; y2_ = y2; cout << "LegacyRectangle: create. (" << x1_ << "," << y1_ << ") => (" << x2_ << "," << y2_ << ")" << endl; } void oldDraw() { cout << "LegacyRectangle: oldDraw. (" << x1_ << "," << y1_ << ") => (" << x2_ << "," << y2_ << ")" << endl; } private: Coordinate x1_; Coordinate y1_; Coordinate x2_; Coordinate y2_; }; /////////////////////////// Adapter wrapper /////////////////////////// class RectangleAdapter : public Rectangle, private LegacyRectangle { public: RectangleAdapter( Coordinate x, Coordinate y, Dimension w, Dimension h ) : LegacyRectangle( x, y, x+w, y+h ) { cout << "RectangleAdapter: create. (" << x << "," << y << "), width = " << w << ", height = " << h << endl; } virtual void draw() { cout << "RectangleAdapter: draw." << endl; oldDraw(); } }; void main() { Rectangle* r = new RectangleAdapter( 120, 200, 60, 40 ); r->draw(); } // LegacyRectangle: create. (120,200) => (180,240) // RectangleAdapter: create. (120,200), width = 60, height = 40 // RectangleAdapter: draw. // LegacyRectangle: oldDraw. (120,200) => (180,240)
  17. 17. Adapter  Применяется в случаях   система поддерживает требуемые данные и поведение, но имеет неподходящий интерфейс. Плюсы   инкапсуляция реализации внешних классов (компонентов, библиотек), система становится независимой от интерфейса внешних классов; переход на использование других внешних классов не требует переделки самой системы, достаточно реализовать один класс Adapter.
  18. 18. Декоратор (Decorator) или Оболочка (Wrapper)  Проблема   Возложить дополнительные обязанности (прозрачные для клиентов) на отдельный объект, а не на класс в целом. Рекомендации  Применение нескольких "Декораторов" к одному "Компоненту" позволяет произвольным образом сочетать обязанности, например, одно свойство можно добавить дважды.
  19. 19. Decorator. Решение    Динамически добавить объекту новые обязанности не прибегая при этом к порождению подклассов (наследованию). "Компонент"определяет интерфейс для обьектов, на которые могут быть динамически возложены дополнительные обязанности, "КонкретныйКомпонент" определяет объект, на который возлагаются дополнительные обязанности, "Декоратор" - хранит ссылку на объект "Компонент" и определяет интерфейс, соответствующий интерфейсу "Компонента". "КонкретныйДекоратор" возлагает дополнительные обязанности на компонент. "Декоратор" переадресует запросы объекту "Компонент".
  20. 20. Decorator. Диаграмма
  21. 21. Decorator. Второй пример
  22. 22. Decorator. Пример кода #include <iostream> using namespace std; /* Component (interface) */ class Widget { public: virtual void draw() = 0; }; /* Decoder (interface)*/ class Decorator : public Widget { private: Widget* wid; public: Decorator( Widget* w ) { wid = w; } /* ConcreteComponent */ class TextField : public Widget { private: int width, height; public: TextField( int w, int h ){ width = w; height = h; } }; void draw() { cout << "TextField: " << width << ", " << height << 'n'; } // reference to Widget }; void draw() { wid->draw(); } /* ConcreteDecoderB */ class ScrollDecorator : public Decorator { public: ScrollDecorator( Widget* w ) : Decorator( w ) {} void draw() { Decorator::draw(); cout << " ScrollDecorator" << 'n'; } }; void main( void ) { Widget* aWidget = new BorderDecorator( new BorderDecorator( new ScrollDecorator( new TextField( 80, 24 )))); aWidget->draw(); /* ConcreteDecoderA */ class BorderDecorator : public Decorator { public: BorderDecorator( Widget* w ) : Decorator( w ) {} void draw() { Decorator::draw(); cout << " BorderDecorator" << 'n'; } }; }
  23. 23. Decorator. Пример На выходе программа напечатает: TextField: <width> , <height> ScrollDecorator . Обратите внимание, что метод getDescription обоих декораторов сначала вызывает getDescription декорируемого окна, а затем добавляет туда суффикс. BorderDecorator
  24. 24. Decorator. Критика  Преимущества    Большая гибкость, чем у статического наследования: можно добавлять и удалять обязанности во время выполнения программы в то время как при использовании наследования надо было бы создавать новый класс для каждой дополнительной обязанности. Данный паттерн позволяет избежать перегруженных методами классов на верхних уровнях иерархии - новые обязанности можно добавлять по мере необходимости. Недостатки  "Декоратор" и его "Компонент" не идентичны, и, кроме того, получается что система состоит из большого числа мелких объектов, которые похожи друг на друга и различаются только способом взаимосвязи, а не классом и не значениями своих внутренних переменных - такая система сложна в изучении и отладке.
  25. 25. Заместитель (Proxy) или Суррогат (Surrogate)  Проблема  Необходимо управлять доступом к объекту, так чтобы создавать громоздкие объекты "по требованию".
  26. 26. Заместитель (Proxy)  Решение    Создать суррогат громоздкого объекта. "Заместитель" хранит ссылку, которая позволяет заместителю обратиться к реальному субъекту (объект класса "Заместитель" может обращаться к объекту класса "Субъект", если интерфейсы "РеальногоСубъекта" и "Субъекта" одинаковы). Поскольку интерфейс "РеальногоСубъекта" идентичен интерфейсу "Субъекта", так, что "Заместителя" можно подставить вместо "РеальногоСубъекта", контролирует доступ к "РеальномуСубъекту", может отвечать за создание или удаление "РеальногоСубъекта". "Субъект" определяет общий для "РеальногоСубъекта" и "Заместителя" интерфейс, так, что "Заместитель" может быть использован везде, где ожидается "РеальныйСубъект". При необходимости запросы могут быть переадресованы "Заместителем" "РеальномуСубъекту".
  27. 27. Заместитель (Proxy)
  28. 28. Заместитель (Proxy)  "Заместитель" может иметь и другие обязанности, а именно:    удаленный "Заместитель" может отвечать за кодирование запроса и его аргументов и отправку закодированного запроса реальному "Субъекту", виртуальный "Заместитель" может кэшировать дополнительную информацию о реальном "Субъекте", чтобы отложить его создание, защищающий "Заместитель" может проверять, имеет ли вызывающий объект необходимые для выполнения запроса права.
  29. 29. Компоновщик (Composite)  Проблема   Как обрабатывать группу или композицию структур объектов одновременно? Решение  Определить классы для композитных и атомарных объектов таким образом, чтобы они реализовывали один и тот же интерфейс.
  30. 30. Composite. Диаграмма
  31. 31. Composite. Example // file graphic.h starting here #include <iostream.h> class Composite; class Graphic { friend class Composite; Graphic *next; virtual int pictureDetect(){return(O);} public: virtual void draw(){} Graphic(){next=NULL;} }; class Line : public Graphic { int xl,yl,x2,y2; public: virtual void draw(void) {cout<<"Line:"<<xl<<yl<<x2<<y2<<'n';) Line(int Xl,int Yl,int X2,int Y2){xl=X1; yl=Y1; x2=X2; y2=Y2;} }; class Text : public Graphic { int x,y; char *text; public: virtual void draw(void){cout<<"Text:g <<X<<y<< text<<'n';} Text(int X,int Y. char *tx){x=X; y=Y; text=tx;} }; class Picture : public Graphic { friend class Composite; Graphic *first; virtual int pictureDetect(){return(1);} public: virtual void draw(void){cout<<"Picture:nN;} Picture(){first=NULL;} class Composite { public: void add(Picture *p, Graphic *g){g->next=p->first; p->first=g;} void remove(Picture *p, Graphic *g); void draw(Graphic *g); void dissolve(Graphic *g); }; void Composite::remove(Picture *p, Graphic *g){ Graphic *t; if(p->first==g){p->first=g->next; g->next=NULL;} else { for(t=p->first; t; t=t->next) if(t->next==g)break; if(t){t->next=g->next; g->next=NULL;} else cout<<"errorn"; } } void Composite::draw(Graphic *g){ static int level=O; // not necessary, just for better // displays int i; Graphic *t; } void Composite::dissolve(Graphic *g){ Graphic *t,*tn; } if(g->pictureDetect(){ for(t=((Picture *)g)->first; t; t=tn){ tn=t->next; t->next=NULL; } ((Picture *)g)->first=NULL; } int main(void){ Line *nl,*n2,*n3,*n4; Text *tl,*t2,*t3 Picture *pl,*p2,*p3; nl=new Line(1,1,11,11); n2=new Line(2,2,22,22); n3=new Line(3,3,33,33); n4=new Line(4,4,44,44); tl=new Text(l,l,"one"); t2=new Text(2,2,"two"); t3=new Text(3,3,"three"); pl=new Picture; p2=new Picture; p3=new Picture; Composite comp; for(i=O;i level;i++)cout<<" "; // just to indent the // print g->draw(); if(g->pictureDetect()){ level++; for(t=((Picture *)g)->first; t; t=t->next) draw(t); level-; } comp.add(pl,nl); comp.add(pl,n2); comp.add(p2,n3); comp.add(p2,n4); comp.add(p3,tl); comp.add(p3,t2); comp.add(p2,t3); comp.add(p2,pl); comp.add(p3,p2); comp.remove(p2,n4); comp.add(p3,n4); } comp.draw(p3); comp.dissolve(p2); cout<<'n'; comp.draw(p3); return(O);
  32. 32. Мост (Bridge), Handle (описатель) или Тело (Body)  Проблема   Требуется отделить абстракцию от реализации так, чтобы и то и другое можно было изменять независимо. При использовании наследования реализация жестко привязывается к абстракции, что затрудняет независимую модификацию. Решение  Поместить абстракцию и реализацию в отдельные иерархии классов.
  33. 33. Bridge. Диаграмма
  34. 34. Bridge. Пример using namespace std; /********************************************/ /* Implementor */ /********************************************/ // interface class DrawingAPI { public: virtual void drawCircle(double x, double y, double radius) = 0; }; //concrete implementor1 class DrawingAPI1 : public DrawingAPI { public: void drawCircle(double x, double y, double radius) { cout<<"API1.circle at"<<x<<":"<<y<<" "<< radius<<endl; } }; //concrete implementor2 class DrawingAPI2 : public DrawingAPI { public: void drawCircle(double x, double y, double radius) { cout<<"API2.circle at "<<x<<":"<<y<<" "<< radius<<endl; } }; /**************************************/ /* Abstraction */ /**************************************/ class Shape { public: virtual void draw() = 0; virtual void resizeByPercentage(double pct) = 0; }; class CircleShape:public Shape { public: CircleShape(double x, double y,double radius,DrawingAPI &drawingAPI): m_x(x),m_y(y),m_radius(radius),m_drawi ngAPI(drawingAPI) {} void draw() { } m_drawingAPI.drawCircle(m_x,m_y,m_r adius); void resizeByPercentage(double pct) { m_radius *= pct; } private: double m_x,m_y,m_radius; DrawingAPI& m_drawingAPI; }; //////////////////////////////////////// //Test typedef std::vector<Shape*>::iterator ShapeIt; int main(int argc, char* argv[]) { std::vector<Shape*> vecShapes; vecShapes.push_back(new CircleShape(1,2,3,*(new DrawingAPI1))); vecShapes.push_back(new CircleShape(5,7,11,*(new DrawingAPI2))); ShapeIt begin,end; begin = vecShapes.begin(); end = vecShapes.end(); for_each(begin,end,std::bind2nd(std::me m_fun(&Shape::resizeByPercentage),2.5 )); } for_each(begin,end,mem_fun(&Shape::d raw)); return 0;
  35. 35. Bridge. Пример
  36. 36. Bridge. Преимущества   Отделение реализации от интерфейса, то есть, "Реализацию" "Абстракции" можно конфигурировать во время выполнения. Разделение классов "Абстракция" и "Реализация" устраняет зависимости от реализации, устанавливаемые на этапе компиляции: чтобы изменить класс "Реализация" вовсе не обязательно перекомпилировать класс "Абстракция".
  37. 37. Фасад (Facade)  Проблема  Как обеспечить унифицированный интерфейс с набором разрозненных реализаций или интерфейсов, например, с подсистемой, если нежелательно высокое связывание с этой подсистемой или реализация подсистемы может измениться?
  38. 38. Фасад (Facade)  Решение     Определить одну точку взаимодействия с подсистемой фасадный объект, обеспечивающий общий интерфейс с подсистемой и возложить на него обязанность по взаимодействию с ее компонентами. Фасад - это внешний объект, обеспечивающий единственную точку входа для служб подсистемы. Реализация других компонентов подсистемы закрыта и не видна внешним компонентам. Фасадный объект обеспечивает реализацию паттерна «Устойчивый к изменениям» (Protected Variations) с точки зрения защиты от изменений в реализации подсистемы.
  39. 39. Фасад (Facade). Диаграмма
  40. 40. Поведенческие шаблоны (Behavioral)            Интерпретатор (Interpreter) Итератор (Iterator) или Курсор (Cursor) Команда (Command), Действие (Action) или Транзакция (Транзакция) Наблюдатель (Observer), Опубликовать - подписаться (Publish - Subscribe) или Delegation Event Model Посетитель (Visitor) Посредник (Mediator) Состояние (State) Стратегия (Strategy) Хранитель (Memento) Цепочка обязанностей (Chain of Responsibility) Шаблонный метод (Template Method)
  41. 41. Итератор (Iterator) или Курсор (Cursor)  Проблема  Составной объект, например, список, должен предоставлять доступ к своим элементам (объектам), не раскрывая их внутреннюю структуру, причем перебирать список требуется поразному в зависимости от задачи.
  42. 42. Итератор (Iterator).  Решение    Создается класс "Итератор", который определяет интерфейс для доступа и перебора элементов, "КонкретныйИтератор" реализует интерфейс класса "Итератор" и следит за текущей позицией при обходе "Агрегата". "Агрегат" определяет интерфейс для создания объекта итератора. "КонкретныйАгрегат" реализует интерфейс создания итератора и возвращает экземпляр класса "КонкретныйИтератор", "КонкретныйИтератор" отслеживает текущий объект в агрегате и может вычислить следующий объект при переборе.
  43. 43. Итератор (Iterator). Диаграмма
  44. 44. Итератор (Iterator). class StackIter { // 1. Design an "iterator" class { const Stack *stk; int items[10]; int index; int sp; public: public: StackIter(const Stack *s) friend class StackIter; { Stack() stk = s; { } sp = - 1; void first() } { void push(int in) index = 0; { } items[++sp] = in; void next() } { int pop() index++; { } return items[sp--]; bool isDone() } { bool isEmpty() return index == stk->sp + 1; { } return (sp == - 1); int currentItem() } { StackIter *createIterator()const; return stk->items[index]; // 2. Add a } createIterator() }; member class Stack }; StackIter *Stack::createIterator()const { return new StackIter(this); } bool operator == (const Stack &l, const Stack &r) { // 3. Clients ask the container object to create an iterator object StackIter *itl = l.createIterator(); StackIter *itr = r.createIterator(); // 4. Clients use the first(), isDone(), next(), and currentItem() protocol for (itl->first(), itr->first(); !itl->isDone();itl>next(), itr->next()) if (itl->currentItem() != itr>currentItem()) break; bool ans = itl->isDone() && itr>isDone(); delete itl; delete itr; return ans; } int main() { Stack s1; for (int i = 1; i < 5; i++) s1.push(i); Stack s2(s1), s3(s1), s4(s1), s5(s1); s3.pop(); s5.pop(); s4.push(2); s5.push(9); cout << "1 == 2 is " << (s1 == s2) << endl; cout << "1 == 3 is " << (s1 == s3) << endl; cout << "1 == 4 is " << (s1 == s4) << endl; cout << "1 == 5 is " << (s1 == s5) << endl; }
  45. 45. Итератор (Iterator). Преимущества   Поддерживает различные способы перебора агрегата, одновременно могут быть активны несколько переборов.
  46. 46. Команда (Command), Действие (Action) или Транзакция (Транзакция)  Проблема  Необходимо послать объекту запрос, не зная о том, выполнение какой операции запрошено и кто будет получателем.
  47. 47. Команда (Command). Решение     Инкапсулировать запрос как объект. "Клиент" создает объект "КонкретнаяКоманда", который вызывает операции получателя для выполнения запроса, "Инициатор" отправляет запрос, выполняя операцию "Команды" Выполнить(). "Команда" объявляет интерфейс для выполнения операции, "КонкретнаяКоманда" определяет связь между объектом "Получатель" и операцией Действие(), и, кроме того, реализует операцию Выполнить() путем вызова соответствующих операций объекта "Получатель". "Клиент" создает экземпляр класса "КонкретнаяКоманда" и устанавливает его получателя, "Инициатор" обращается к команде для выполнения запроса, "Получатель" (любой класс) располагает информацией о способах выполнения операций, необходимых для выполнения запроса.
  48. 48. Команда (Command). Диаграмма
  49. 49. Команда (Command). Пример #include <iostream> #include <string> using namespace std; class Person; class Command { // 1. Create a class that encapsulates an object Person *object; // and a member function void(Person:: *method)(); // a pointer to a member function (the attripublic: // bute's name is "method") Command(Person *obj = 0, void(Person:: *meth)() = 0) { object = obj; // the argument's name is "meth" method = meth; } void execute() { (object-> *method)(); // invoke the method on the object } }; class Person { string name; Command cmd; // cmd is a "black box", it is a method invocapublic: // tion promoted to "full object status" Person(string n, Command c): cmd(c) { name = n; } void talk() { // "this" is the sender, cmd has the receiver cout << name << " is talking" << endl; cmd.execute(); // ask the "black box" to callback the receiver } void passOn() { cout << name << " is passing on" << endl; cmd.execute(); // 4. When the sender is ready to callback to } // the receiver, it calls execute() void gossip() { cout << name << " is gossiping" << endl; cmd.execute(); } void listen() { cout << name << " is listening" << endl; } }; int main() { // Fred will "execute" Barney which will result in a call to passOn() // Barney will "execute" Betty which will result in a call to gossip() // Betty will "execute" Wilma which will result in a call to listen() Person wilma("Wilma", Command()); // 2. Instantiate an object for each "callback" // 3. Pass each object to its future "sender" Person betty("Betty", Command(&wilma, Person::listen)); Person barney("Barney", Command(&betty, &Person::gossip)); Person fred("Fred", Command(&barney, &Person::passOn)); fred.talk(); }
  50. 50. Команда (Command). Преимущества  Обеспечивает обработку команды в виде объекта, что позволяет сохранять её, передавать в качестве параметра методам, а также возвращать её в виде результата, как и любой другой объект.
  51. 51. Наблюдатель (Observer), Опубликовать - подписаться (Publish Subscribe) или Listener  Проблема   Один объект ("Подписчик") должен знать об изменении состояний или некоторых событиях другого объекта. При этом необходимо поддерживать низкий уровень связывания с объектом - "Подписчиком". Решение  Определить интерфейс "Подписки". Объекты подписчики реализуют этот интерфейс и динамически регистрируются для получении информации о некотором событии. Затем при реализации условленного события оповещаются все объекты - подписчики.
  52. 52. Observer
  53. 53. Observer. Область применения  Шаблон «наблюдатель» применяется в тех случаях, когда система обладает следующими свойствами:    существует, как минимум, один объект, рассылающий сообщения имеется не менее одного получателя сообщений, причём их количество и состав могут изменяться во время работы приложения. Данный шаблон часто применяют в ситуациях, в которых отправителя сообщений не интересует, что делают с предоставленной им информацией получатели.
  54. 54. Посетитель (Visitor).  Проблема  Над каждым объектом некоторой структуры выполняется операция. Определить новую операцию, не изменяя классы объектов.
  55. 55. Посетитель (Visitor).     Клиент, использующий данный паттерн, должен создать объект класса "КонкретныйПосетитель", а затем посетить каждый элемент структуры. "Посетитель" объявляет операцию "Посетить" для каждого класса "КонкретныйЭлемент" (имя и сигнатура данной операции идентифицируют класс, элемент которого посещает "Посетитель" - то есть, посетитель может обращаться к элементу напрямую). "КонкретныйПосетитель" реализует все операции, обьявленные в классе "Посетитель". Каждая операция реализует фрагмент алгоритма, определенного для класса соответствующего объекта в структуре.
  56. 56. Посетитель (Visitor).    Класс "КонкретныйПосетитель"предоставляет контекст для этого алгоритма и сохраняет его локальное состояние. "Элемент" определяет операцию "Принять", которая принимает "Посетителя" в качестве аргумента, "КонкретныйЭлемент" реализует операцию "Принять", которая принимает "Посетителя" в качестве аргумента. "СтруктураОбьекта" может перечислить свои аргументы и предоставить посетителю высокоуровневый интерфейс для посещения своих элементов.
  57. 57. Посетитель (Visitor).
  58. 58. Посетитель (Visitor). #include <iostream> #include <memory> #include <vector> #include <boost/shared_ptr.hpp> template <class T> class TVisitor { public: virtual ~TVisitor() {} virtual void Accept(T& t) = 0; }; class Fir; class Pine; class Tree{ public: virtual ~Tree() {} virtual void Visit(TVisitor<Fir>& v) {} virtual void Visit(TVisitor<Pine>& v) {} }; typedef boost::shared_ptr<Tree> TreePtr; class Pine : public Tree { public: }; }; forest.push_back(TreePtr(new Fir)); forest.push_back(TreePtr(new Pine)); forest.push_back(TreePtr(new Pine)); forest.push_back(TreePtr(new Fir)); forest.push_back(TreePtr(new Fir)); class SawFirVisitor : public TVisitor<Fir> { public: }; }; SawFirVisitor sawFir; DecorateFirVisitor decorateFir; virtual void Accept(Fir& fir) { std::cout << "Fir sawed!" << std::endl; } class DecorateFirVisitor : public TVisitor<Fir> { public: class Fir : public Tree { public: virtual void Visit(TVisitor<Fir>& v) { v.Accept(*this); } virtual void Visit(TVisitor<Pine>& v) { v.Accept(*this); } int main() { typedef std::vector<TreePtr> Forest; Forest forest; virtual void Accept(Fir& fir) { std::cout << "Fir decorated!" << std::endl; } } for(Forest::iterator i = forest.begin(); i != forest.end(); ++i) { (*i)->Visit(sawFir); (*i)->Visit(decorateFir); }
  59. 59. Посетитель (Visitor).  Рекомендации   Преимущества   Логично использовать, если в структуре присутствуют объекты многих классов с различными интерфейсами, и необходимо выполнить над ними операции, зависящие от конкретных классов, или если классы, устанавливающие структуру объектов изменяются редко, но новые операции над этой структурой добавляются часто. Упрощается добавление новых операций, объединяет родственные операции в классе "Посетитель". Недостатки  Затруднено добавление новых классов "КонкретныйЭлемент", поскольку требуется объявление новой абстрактной операции в классе "Посетитель".
  60. 60. Состояние (State)  Проблема   Варьировать поведение объекта в зависимости от его внутреннего состояния Решение   Класс "Контекст" делегирует зависящие от состояния запросы текущему объекту "КонкретноеСостояние" (хранит экземпляр подкласса "КонкретноеСостояние", которым определяется текущее состояние), и определяет интерфейс, представляющий интерес для клиентов. "КонкретноеСостояние" реализует поведение, ассоциированное с неким состоянием объекта "Контекст". "Состояние" определяет интерфейс для инкапсуляции поведения, ассоциированного с конкретным экземпляром "Контекста".
  61. 61. Состояние (State)
  62. 62. Состояние (State) class AbstractTool { public: virtual void MoveTo(const Point& inP) = 0; virtual void MouseDown(const Point& inP) = 0; virtual void MouseUp(const Point& inP) = 0; }; class PenTool : public AbstractTool { public: PenTool() : mMouseIsDown(false) {} virtual void MoveTo(const Point& inP) { if(mMouseIsDown) { DrawLine(mLastP, inP); } mLastP = inP; } virtual void MouseDown(const Point& inP) { mMouseIsDown = true; mLastP = inP; } virtual void MouseUp(const Point& inP) { mMouseIsDown = false; } private: bool mMouseIsDown; Point mLastP; }; class SelectionTool : public AbstractTool { public: SelectionTool() : mMouseIsDown(false) {} virtual void MoveTo(const Point& inP) { if(mMouseIsDown) { mSelection.Set(mLastP, inP); } } virtual void MouseDown(const Point& inP) { mMouseIsDown = true; mLastP = inP; mSelection.Set(mLastP, inP); } virtual void MouseUp(const Point& inP) { mMouseIsDown = false; } private: bool mMouseIsDown; Point mLastP; Rectangle mSelection; }; class DrawingController { public: DrawingController() { selectPenTool(); } // Start with some tool. void MoveTo(const Point& inP) {currentTool->MoveTo(inP)} void MouseDown(const Point& inP) {currentTool->MouseDown(inP)} void MouseUp(const Point& inP) {currentTool->MouseUp(inP)} selectPenTool() { currentTool.reset(new PenTool); } selectSelectionTool() { currentTool.reset(new SelectionTool); } private: std::auto_ptr<AbstractTool> currentTool; };
  63. 63. Состояние (State). Преимущества   Локализует зависящее от состояния поведение и делит его на части, соответствующие состояниям, переходы между состояниями становятся явными.
  64. 64. Стратегия (Strategy)  Проблема   Спроектировать изменяемые, но надежные алгоритмы или стратегии. Решение  Определить для каждого алгоритма или стратегии отдельный класс со стандартным интерфейсом.
  65. 65. Стратегия (Strategy). Пример   Обеспечение сложной логики вычисления стоимости товаров с учетом сезонных скидок, скидок постоянным клиентам и т. п. Данная стратегия может изменяться. Создается несколько классов "Стратегия", каждый из которых содержит один и тот же полиморфный метод "ЦенаРассчитать". В качестве параметров в этот метод передаются данные о продаже. Объект стратегии связывается с контекстным объектом (тем объектом, к которому применяется алгоритм).
  66. 66. Стратегия (Strategy).
  67. 67. Стратегия (Strategy). #include <iostream.h> #include <fstream.h> #include <string.h> class Strategy; class TestBed { public: enum StrategyType { Dummy, Left, Right, Center }; TestBed() { strategy_ = NULL; } void setStrategy( int type, int width ); void doIt(); private: Strategy* strategy_; }; class Strategy { public: Strategy( int width ) : width_( width ) { } void format() { char line[80], word[30]; ifstream inFile( "quote.txt", ios::in ); line[0] = '0'; inFile >> word; strcat( line, word ); while (inFile >> word) { if (strlen(line) + strlen(word) + 1 > width_) justify( line ); else strcat( line, " " ); strcat( line, word ); } justify( line ); } protected: int width_; private: virtual void justify( char* line ) = 0; }; class LeftStrategy : public Strategy { public: LeftStrategy( int width ) : Strategy( width ) { } private: /* virtual */ void justify( char* line ) { cout << line << endl; line[0] = '0'; } }; class RightStrategy : public Strategy { public: RightStrategy( int width ) : Strategy( width ) { } private: /* virtual */ void justify( char* line ) { char buf[80]; int offset = width_ - strlen( line ); memset( buf, ' ', 80 ); strcpy( &(buf[offset]), line ); cout << buf << endl; line[0] = '0'; } }; class CenterStrategy : public Strategy { public: CenterStrategy( int width ) : Strategy( width ) { } private: /* virtual */ void justify( char* line ) { char buf[80]; int offset = (width_ - strlen( line )) / 2; memset( buf, ' ', 80 ); strcpy( &(buf[offset]), line ); cout << buf << endl; line[0] = '0'; } }; class LeftStrategy : public Strategy { public: LeftStrategy( int width ) : Strategy( width ) { } private: /* virtual */ void justify( char* line ) { cout << line << endl; line[0] = '0'; } }; class RightStrategy : public Strategy { public: RightStrategy( int width ) : Strategy( width ) { } private: /* virtual */ void justify( char* line ) { char buf[80]; int offset = width_ - strlen( line ); memset( buf, ' ', 80 ); strcpy( &(buf[offset]), line ); cout << buf << endl; line[0] = '0'; } }; class CenterStrategy : public Strategy { public: CenterStrategy( int width ) : Strategy( width ) { } private: /* virtual */ void justify( char* line ) { char buf[80]; int offset = (width_ - strlen( line )) / 2; memset( buf, ' ', 80 ); strcpy( &(buf[offset]), line ); cout << buf << endl; line[0] = '0'; } };
  68. 68. Стратегия (Strategy).  Плюсы     инкапсуляция реализации различных алгоритмов, система становится независимой от возможных изменений бизнес-правил; вызов всех алгоритмов одним стандартным образом; отказ от использования переключателей и/или условных операторов. Минусы  создание дополнительных классов
  69. 69. Цепочка обязанностей (Chain of Responsibility)  Проблема   Запрос должен быть обработан несколькими объектами. Рекомендации  Логично использовать данный паттерн, если имеется более одного объекта, способного обработать запрос и обработчик заранее неизвестен (и должен быть найден автоматически) или если весь набор объектов, которые способны обработать запрос, должен задаваться автоматически.
  70. 70. Цепочка обязанностей  Шаблон рекомендован для использования в условиях:    в разрабатываемой системе имеется группа объектов, которые могут обрабатывать сообщения определенного типа; все сообщения должны быть обработаны хотя бы одним объектом системы; сообщения в системе обрабатываются по схеме «обработай сам либо перешли другому», то есть одни сообщения обрабатываются на том уровне, где они получены, а другие пересылаются объектам иного уровня.
  71. 71. Цепочка обязанностей
  72. 72. Цепочка обязанностей   Связать объекты - получатели запроса в цепочку и передать запрос вдоль этой цепочки, пока он не будет обработан. "Обработчик" определяет интерфейс для обработки запросов, и, возможно, реализует связь с преемником,
  73. 73. Цепочка обязанностей #include <iostream> #include <vector> #include <ctime> using namespace std; class Base { Base* next; // 1. "next" pointer in the base class public: Base() { next = 0; } void setNext( Base* n ) { next = n; } void add( Base* n ) { if (next) next->add( n ); else next = n; } // 2. The "chain" method in the base class always delegates to the next obj virtual void handle( int i ) { next->handle( i ); } }; class Handler1 : public Base { public: /*virtual*/ void handle( int i ) { if (rand() % 3) { // 3. Don't handle requests 3 times out of 4 cout << "H1 passsed " << i << " "; Base::handle( i ); // 3. Delegate to the base class } else cout << "H1 handled " << i << " "; } }; class Handler2 : public Base { public: /*virtual*/ void handle( int i ) { if (rand() % 3) { cout << "H2 passsed " << i << " "; Base::handle( i ); } else cout << "H2 handled " << i << " "; } }; class Handler3 : public Base { public: /*virtual*/ void handle( int i ) { if (rand() % 3) { cout << "H3 passsed " << i << " "; Base::handle( i ); } else cout << "H3 handled " << i << " "; } }; void main( void ) { srand( time( 0 ) ); Handler1 root; Handler2 two; Handler3 thr; root.add( &two ); root.add( &thr ); thr.setNext( &root ); for (int i=1; i < 10; i++) { root.handle( i ); cout << 'n'; } }
  74. 74. Цепочка обязанностей  Преимущества   Ослабляется связанность (объект не обязан "знать", кто именно обработает его запрос). Недостатки  Нет гарантий, что запрос будет обработан, поскольку он не имеет явного получателя.
  75. 75. Шаблонный метод (Template Method)  Проблема  Определить алгоритм и реализовать возможность переопределения некоторых шагов алгоритма для подклассов (без изменения общей структуры алгоритма.
  76. 76. Шаблонный метод (Template Method)  Решение  "АбстрактныйКласс" определяет абстрактные Операции(), замещаемые в конкретных подклассах для реализации шагов алгоритма, и реализует ШаблонныйМетод(), определяющий "скелет" алгоритма. "КонкретныйКласс" релизует Операции(), выполняющие шаги алгоритма способом, который зависит от подкласса. "КонкретныйКласс" предполагает, что инвариантные шаги алгоритма будут выполнены в "АбстрактномКлассе".
  77. 77. Шаблонный метод (Template Method)
  78. 78. Шаблонный метод (Template Method) #include <iostream> using namespace std; class Base { void a() { cout << "a "; } void c() { cout << "c "; } void e() { cout << "e "; } // 2. Steps requiring peculiar implementations are "placeholders" in base class virtual void ph1() = 0; virtual void ph2() = 0; public: // 1. Standardize the skeleton of an algorithm in a base class "template method" void execute() { a(); ph1(); c(); ph2(); e(); } }; class One : public Base { // 3. Derived classes implement placeholder methods /*virtual*/ void ph1() { cout << "b "; } /*virtual*/ void ph2() { cout << "d "; } }; class Two : public Base { /*virtual*/ void ph1() { cout << "2 "; } /*virtual*/ void ph2() { cout << "4 "; } }; void main( void ) { Base* array[] = { &One(), &Two() }; for (int i=0; i < 2; i++) { array[i]->execute(); cout << 'n'; } }
  79. 79. Model-view-controller  Model-view-controller (MVC, «Модельпредставление-поведение») — архитектура программного обеспечения, в которой модель данных приложения, пользовательский интерфейс и управляющая логика разделены на три отдельных компонента, так, что модификация одного из компонентов оказывает минимальное воздействие на другие компоненты.
  80. 80. Model-view-controller  Шаблон MVC позволяет разделить данные, представление и обработку действий пользователя на три отдельных компонента    Модель (Model). Модель предоставляет данные (обычно для View), а также реагирует на запросы (обычно от контролера), изменяя свое состояние . Представление (View). Отвечает за отображение информации (пользовательский интерфейс). Поведение (Controller). Интерпретирует данные, введенные пользователем, и информирует модель и представление о необходимости соответствующей реакции.
  81. 81. Model-view-controller
  82. 82. MVC
  83. 83. Model-view-controller   Важно отметить, что как представление, так и поведение зависят от модели. Однако модель не зависит ни от представления, ни от поведения. Это одно из ключевых достоинств подобного разделения. Оно позволяет строить модель независимо от визуального представления. Впервые данный шаблон проектирования был предложен для языка Smalltalk.
  84. 84. Итоги  Типы шаблонов    Порождающие паттерны проектирования Структурные паттерны проектирования классов/объектов Паттерны проектирования поведения классов/объектов

×