design+patterns esti 2010 (1)

73
Ministère de l’Enseignement Supérieur, de la Recherche Scientifique et de la Technologie Université du 7 Novembre à Carthage Ecole Supérieure de Technologie et d’Informatique A.U. 2010/2011 Design Pattern Afin d’améliorer l’architecture des programmes b "Une règle tripartite exprimant une relation entre un certain contexte, un certain problème qui apparait répétiivement dans ce contexte et une certaine configuration logiielle qui permet la résolution de ce problème" Brad Appleton 1997 Département : Informatique Appliquée Master Systèmes Informatiques et Logiciels Enseignant : Mohamed KHADRAOUI

Upload: mouhamed-maalaoui

Post on 22-Nov-2014

271 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: Design+Patterns ESTI 2010 (1)

Ministère de l’Enseignement Supérieur,

de la Recherche Scientifique et de la Technologie

Université du 7 Novembre à Carthage

Ecole Supérieure de Technologie

et d’Informatique

A.U. 2010/2011

Design Pattern

Afin d’améliorer l’architecture des programmes

b "Une règle tripartite exprimant une relation entre un certain contexte, un certain problème qui apparait répétiivement dans ce contexte et une certaine configuration logiielle qui permet la résolution de ce problème" Brad Appleton 1997

Département : Informatique Appliquée Master Systèmes Informatiques et Logiciels Enseignant : Mohamed KHADRAOUI

Page 2: Design+Patterns ESTI 2010 (1)

Table des matières

1. Problématique : ...................................................................... 3

2. Rappel POO ............................................................................ 3

3. Introduction ............................................................................ 4

1.1. Les Patterns en général ...................................................... 4 1.1. Les Design Pattern : la capitalisation de l’expérience ..... 5 1.2. Formalisme ......................................................................... 6

4. Classification ........................................................................... 7

5. Description .............................................................................. 8

1.3. Les pattern de création ...................................................... 8 1. Les prototypes .................................................................. 9

2. Le singleton .................................................................... 12

1.4. Les pattern de structure .................................................. 15 1. Le Adapter ...................................................................... 15

2. Le Composite .................................................................. 18

3. Le Décorateur ‘Decorator’ ............................................ 24 4. Le Pont ‘Bridge’ ............................................................. 27

5. Le Proxy ......................................................................... 31

1.5. Les patterns de comportement ........................................ 34 1. Le ‘Command ‘ .............................................................. 35 2. Le Iterator ...................................................................... 38

3. Le ‘Observer ‘ ................................................................ 41

4. Le ‘State ‘ ....................................................................... 44

5. Le ‘Chain Of Responsability ‘ ...................................... 49 6. Le Visitor ........................................................................ 53

7. Le Memento .................................................................... 58

Page 3: Design+Patterns ESTI 2010 (1)

1.Problématique : L'approche orientée objet tend à éclater les applications en composants plus simples et réutilisables. Cependant, cette approche peut vite devenir un piège lorsque le découpage s'effectue sans règles précises. Le concepteur finit par être saturé par la complexité du codage (effet spaghetti). Sans architecture de base, telle application devient progressivement ingérable avec pour conséquence l'émergence de bugs de plus en plus difficiles à corriger (effet dominos). Pour réduire les risques de maintenance, il a fallu exploiter un niveau supplémentaire dans la conception objet : Les modèles de conception ou design patterns.

2.Rappel POO Contrairement aux langages de type procéduraux comme le C ou le Pascal, la conception objet ne divise pas l'espace de données (attributs) et l'espace de traitements (méthodes). Cet espace commun s'applique à une partie du problème à gérer sous la forme d'une classe. Une classe est une représentation abstraite décrivant comment faire fonctionner des objets. Les objets sont construits à partir de cette classe lors de -l'exécution par un processus d'instanciation (l'opérateur new). Chacune des déclarations dans une classe peut être limitée dans sa portée (portée locale ou privée, portée de groupe ou package, portée de descendance ou protégée, portée globale ou publique). Une classe peut être associée à d’autres classes pour faciliter la réutilisation. L'association la plus commune est l'héritage. L'héritage sert à spécialiser une classe existante (lien de généralisation/spécialisation) par la modification/l'ajout de nouvelles méthodes et de nouvelles données. Cette spécialisation conduit à la construction de nouvelles classes (appelées aussi sous-classes). Le concept d'héritage peut être vu comme la conception d'un élément "est une sorte de". Par exemple, une Voiture peut être vue comme une sous-classe de la classe Véhicule, une Voiture hérite donc de la classe Véhicule, elle étend les caractéristiques du véhicule par des données et des méthodes supplémentaires (la vitesse de pointe, le moteur...). Lorsqu'une méthode d'une classe est redéfinie par une sous-classe, on dit qu'il y a surcharge. Comme nous le verrons dans la suite cette possibilité donne une grande souplesse à l'introduction de classes génériques déléguant certains comportements aux sous-classes. Certaines classes ne sont pas complètement construites afin d'obliger les sous-classes à effectuer une surcharge. On parle alors de classes abstraites ou d'interfaces. L'interface est employée en Java pour compenser l'interdiction d'utiliser l'héritage multiple (héritage de plusieurs classes), interdiction qui n'apparaît pas en C++. Une interface est associée à une ou plusieurs classes d'implémentation. Une classe d'implémentation contient donc le code de réalisation de l'interface. Alors que l'héritage concerne les classes (et leur hiérarchie), le polymorphisme est relatif aux méthodes des objets. 3 différents types de polymorphisme existent :

polymorphisme ad hoc : permet d'avoir des fonctions de même nom, avec des fonctionnalités similaires, dans des classes sans aucun rapport entre elles (surcharger l'opérateur + pour lui faire réaliser des actions différentes suivant e type de l’objet)

Polymorphisme paramétrique : appelé généricité, représente la possibilité de définir plusieurs fonctions de même nom mais possédant des paramètres différents (en nombre et/ou en type). Dans ce cas, c'est la signature de la méthode qui détermine laquelle sera appelée.

Page 4: Design+Patterns ESTI 2010 (1)

Polymorphisme d’héritage : La possibilité de redéfinir une méthode dans des classes héritant d'une classe de base s'appelle la spécialisation. Il est alors possible d'appeler une telle méthode d'un objet sans se soucier de son type intrinsèque.

3.Introduction

1.1. Les Patterns en général

Un pattern (modèle) est une façon de faire, une manière d’arriver à l’objectif fixé. Les patterns permettent d’utiliser des principes basés sur l’habileté des précurseurs en la matière. Les modèles sont une façon excellente de capturer et transporter l’habileté. Lorsque votre habileté à employer certaines méthodes communes seront maîtrisées, on se rend compte que ces méthodes pourraient être réutilisées dans plusieurs contextes. Les Patterns, sont apparus bien avant l’apparition de l’informatique, étaient utilisés en architecture. Les modèles architecturaux peuvent servir et inspirer les personnes qui occuperont les bâtiments. Les Patterns font référence aux modèles standardisés d’implémentation de certaines taches à réaliser… Les écrivains documentent ces modèles, en aidant à standardiser ces modèles. Ils assurent aussi que la sagesse accumulée d'une habileté est disponible aux futures générations des praticiens.

Patterns provide proven solutions to recurring design problems in OOP.

A design pattern describes how to structure classes to meet requirement.

Make code more readable to other programmers -> Vocabulary to

communicate in programming community

Learn from community wisdom & Implement proved solutions faster

Remember, knowing concepts like abstraction, inheritance, and polymorphism do not make you a good object oriented designer. A design guru thinks about how to create flexible designs that are maintainable and that can cope with change.

Page 5: Design+Patterns ESTI 2010 (1)

1.1. Les Design Pattern : la capitalisation de l’expérience

Lorsqu’on parle de librairies de fonctions : toolkit nous nous retrouvons devant une multitude de fonctions génériques réutilisables que nous prouvons appelé dans notre code : exemple : io, net, awt,… Dans un cadre relativement plus évolué : les Framework nous nous retrouvons entourés d’une multitude de classes qui coopèrent pour fournir un modèle d’application pour une catégorie d’application : MFC, WPF,…

Design Patterns (DP) Frameworks Les DP sont des solutions récurrentes pour des problèmes qui pourront apparaitre durant le cycle de développement d’une application

Un Framework est un goupe de composants qui coopérent les uns avec les autres pour fournir une architecture d’une application dans un domaine donné

L’objectif principal est de : - Aider à améliorer la qualité des logiciels à produire

en termes de réutilisabilité, maintenanbilité, etc,… - Améliorer la productivité

L’objectif principal est de : - Aider à améliorer la qualité des logiciels à produire en

termes de réutilisabilité, maintenanbilité, etc,… - Améliorer la productivité

Les DP sont des concepts logiques par nature Les frameworks sont plus physiques puisqu ’elles existent sous une forme donnée dans l’application produite

Les DP sont générique et peuvent être utilisé dans plusieurs domaines d’application

Les frameworks fournissent des fonctionnalités spécifiques à un domaine donnée

Un DP n’existe pas sous forme de composant logiciel, il à besoin d’être implémenté chaque fois l’occasion se présente

Un Framework ne fournit pas un application complète, on a besoin de compléter et détendre les composants qui existent

DP sont utilisés dans la conception et l’implémenation des frameworks.

Les Frameworks englobent des composants qui répondent aux desigtn patterns existants

Nous parlons de Design pattern quand il s’agit d’une problématique récurrente dans différents domaines de développement. Quand les patterns sont appliqués dans le domaine de développement logiciel ils prennent la dénomination de Design Pattern. Un Design Pattern est un modèle de conception, il utilise des classes et leurs méthodes dans un langage orientée objet. Les Design Patterns permettent de mettre en application des concepts spécifiques sans pour autant avoir à passer beaucoup de temps sur la méthode de développement à employer pour arriver à un objectif. Ils permettent donc de simplifier votre réflexion en réutilisant des principes de base de la conception, cela aura donc pour effet de rendre votre programme plus lisible lors de phase de maintenance par exemple. Les Design Patterns sont tout simplement des architectures de classes permettant d'apporter une solution à des problèmes fréquemment rencontrés lors des phases d'analyse et de conception d'applications. Ces solutions sont facilement adaptables (donc réutilisables), elles sont utilisables sans aucun risques dans la grande majorité des langages de programmation orientée objet. Les Design Patterns sont fiables. Ils ont une architecture facilement compréhensible et identifiable pour les développeurs. Cela améliore donc la communication et la compréhension entre développeurs. Ce concept à été introduit par : Erich Gamma, Richard Helm, Ralph Johnson et John Vlissides « Gang Of Four » suivant les travaux publiés en 1995 intitulé « Design Patterns -- Elements of Reusable Object-Oriented Software ».

Page 6: Design+Patterns ESTI 2010 (1)

1.2. Formalisme

Un design pattern est en général composé de 4 parties essentielles à savoir : Le nom du pattern : Point d’accroche qui permettra facilement de référer le problème et sa solution. Ce nom doit être choisit avec soin Le problème lui même : décrivant le plus systématiquement le contexte dans lequel s’appliquent cette design pattern La solution : qui décrit l’ensemble des éléments en jeu, leurs relations et leurs responsabilités. La solution est exprimée d’une façon générique et ne repose sur aucun contexte pour être facilement transportable. La solution doit être suffisamment abstraite pour mieux être concrétisée. Les conséquences qu’entraînent l’application de la solution en matière de développement, cela peut concerné le temps de calcul, la mémoire engagée, les contraintes particulières du langage utilisé. Les diagrammes UML : Les deux points centraux (description du problème et solution) justifient généralement l’usage de UML pour illustrer le propos et le rendre universel.

Class name Attributs Operations

*Nota : Les principes de représentation UML : Interface, héritage, réalisation, dépendance, association, instanciation, composition et aggrégation doivent être acquise.

Page 7: Design+Patterns ESTI 2010 (1)

4.Classification Les design pattern sont généralement classifiés en trois catégories suivant la disposition suivante :

Purpose

Creational Structural Behavioral

Scope Class Factory Method Adapter (class) Interpreter

Template Method

Object Abstract Factory

Builder

Prototype

Singleton

Adapter (object)

Bridge

Composite

Decorator

Façade

Flyweight

Proxy

Chain of Responsibility

Command

Iterator

Medaitor

Momento

Observer

State

Strategy

Visitor

Les “Creational Patterns” s’occupent de la création de classes et d’objets, les “ Structural Patterns » s’occupent de leur composition. Les « Behavioral Patterns” concernent l’interaction des objets et la distribution des responsabilités.

Sous la rubrique « Scope » la classification se fait au niveau des relations qui existent entre les classes à leurs niveaux statiques d’un coté et l’interaction des objets dynamiques entre eux de l’autre coté.

Page 8: Design+Patterns ESTI 2010 (1)

5.Description

1.3. Les pattern de création

Ce genre de pattern gère les différentes formes de création: � Abstraire le processus d'instanciation.

� Rendre indépendant de la façon dont les objets sont créés, composés, assemblés, représentés.

� Encapsuler la connaissance de la classe concrète qui instancie.

� Cacher ce qui est créé, qui crée, comment et quand.

Pattern description

AbstractFactory on passe un paramètre à la création qui définit ce qu'on va créer

Builder on passe en paramètre un objet qui sait construire l'objet à

partir d'une description

FactoryMethod la classe sollicitée appelle des méthodes abstraites ...il suffit

de sous-classer

Prototype des prototypes variés existent qui sont copiés et assemblés

Singleton unique instance

Page 9: Design+Patterns ESTI 2010 (1)

1. Les prototypes Le Design Pattern Prototype est utilisé lorsqu’un système doit être indépendant de la façon dont ses produits sont créés, assemblés, représentés ou quand la classe n'est connue qu'à l'exécution et pour éviter une hiérarchie trop redondante. Prototype permet donc de créer de nouveau objets en copiant une instance. Ex: drag-and-drop de composants inconnus avec touche ctrl enfoncée. Les classes à utiliser pour le Pattern Prototype sont (également appelées : les participants) :

• Prototype (c’est la super classe à partir de laquelle on appellera la méthode clone() qui remplira les fonctionnalités recherchées)

• ConcretePrototype (l’héritage redéfinissant la méthode clone () permettra de retourner la copie de l’instance à cloner)

• Client (c’est à partir de l’Opération qu’une nouvelle instance pourra être crée)

Le langage Java permet, grâce à l’interface Clonable , de pouvoir simplifier ce principe de recopie d’un objet. package prototype; public class Prototype implements Cloneable { String a; String b; public Prototype(String c, String d) { a = c; b = d; }

public Object clone() throws CloneNotSupportedException { return super.clone(); } }

Page 10: Design+Patterns ESTI 2010 (1)

A gérer l’exception « CloneNotSupportedException » qui peut être levée lors de l’appel de la méthode clone() : package prototype; public class Client { public static void main(String[] args) { try { Prototype p1 = new Prototype("Labo", "Sun"); Prototype p2 = (Prototype) p1.clone(); System.out.println(p2.a + ":" + p2.b); } catch (CloneNotSupportedException e) {} } } L’instance p2 est bien une copie de p1, cela signifie que lorsque la copie est faite, on peut modifier p1 sans que cela affecte p2.

Delegation The client replaces all references to the

new operator with calls to prototype

Page 11: Design+Patterns ESTI 2010 (1)

Before // The architect has done an admirable job of // decoupling the client from Stooge concrete // derived classes, and, exercising polymorphism. // But there remains coupling where instances are actually created . class Stooge { public: virtual void slap_stick() = 0; }; class Larry : public Stooge { public: void slap_stick() { cout << "Larry: poke eyes\n"; } }; class Moe : public Stooge { public: void slap_stick() { cout << "Moe: slap head\n"; } }; class Curly : public Stooge { public: void slap_stick() { cout << "Curly: suffer abuse\n"; } }; int main( void ) { vector roles; int choice; while (true) { cout << "Larry(1) Moe(2) Curly(3) Go(0): "; cin >> choice; if (choice == 0) break; else if (choice == 1) roles.push_back( new Larry ); else if (choice == 2) roles.push_back( new Moe ); else roles.push_back( new Curly ); } for (int i=0; i < roles.size(); i++) roles[i]->slap_stick(); for (int i=0; i < roles.size(); i++) delete roles[i]; } // Larry(1) Moe(2) Curly(3) Go(0): 2 // Larry(1) Moe(2) Curly(3) Go(0): 1 // Larry(1) Moe(2) Curly(3) Go(0): 3 // Larry(1) Moe(2) Curly(3) Go(0): 0 // Moe: slap head // Larry: poke eyes // Curly: suffer abuse

After // A clone() method has been added to the Stooge hierarchy. Each derived class implements that // method by returning an instance of itself //A Factory class has been introduced that maintains a suite of "breeder" objects (aka proto types), and knows how to delegate to the correct prototype. class Stooge { public: virtual Stooge* clone() = 0; virtual void slap_stick() = 0; }; class Factory { public: static Stooge* make_stooge( int choice ); private: static Stooge* s_prototypes[4]; }; Stooge* Factory::s_prototypes[] = {6 0, new Larry, new Moe, new Curly }; Stooge* Factory::make_stooge( int choice ) { return s_prototypes[choice]->clone(); } int main( void ) { vector roles; int choice; while (true) { cout << "Larry(1) Moe(2) Curly(3) Go(0):"; cin >> choice; if (choice == 0) break; roles.push_back( Factory::make_stooge( choice ) ); } for (int i=0; i < roles.size(); ++i) roles[i]->slap_stick(); for (int i=0; i < roles.size(); ++i) delete roles[i]; } class Larry : public Stooge { public: Stooge* clone() { return new Larry ; } void slap_stick() { cout << "Larry: poke eyes\n"; } }; class Moe : public Stooge { public: Stooge* clone() { return new Moe ; } void slap_stick() { cout << "Moe: slap head\n"; } }; class Curly : public Stooge { public: Stooge* clone() { return new Curly ; } void slap_stick() { cout << "Curly: suffer abuse\n"; } };

Page 12: Design+Patterns ESTI 2010 (1)

2. Le singleton Le singleton est une classe dont on veut s’assurer qu’une seule et unique instance de celle-ci est créée pendant toute la durée d’exécution de votre application. Ce Design Pattern est très facile à utiliser et souvent utilisé, cependant beaucoup de personnes l’utilisent mal. Exemple :

• système de log pour une application, • pool de connection à la base de données, • classe dérivant de CWinApp dans MFC (afin de s’assurer qu’il n’existe qu’une seule

instance de l’objet Application accessible via la méthode AfxGetApp() ) S’il est facile de concevoir une variable globale pouvant régler le problème de l’accès à l’instance d’un objet dans une application, cette solution n’est malheureusement pas acceptable en termes POO et n’est même pas en mesure de garantir que l’application puisse créer une autre instance quelque part dans son code. Ex : spooler d’impression, … Pour s’assurer de l’unicité de l’instance du singleton, la responsabilité doit être déplacée à la classe elle-même. Il faut tout d’abord penser à limiter les accès aux constructeurs (le plus souvent « private » ou « protected », cependant si on utilise ce deuxième cela ne certifie pas l’unicité de l’instance si on hérite votre singleton). Si on n’a pas de constructeur, il faut donc un autre moyen pour retourner une instance de votre classe. Pour cela on doit implémenter une méthode « static » qui, par convention, s’appellera : getInstance().

1. Add a clone() method to the existing "product" hierarchy. 2. Design a "registry" that maintains a cache of prototypical objects. The

registry could be encapsulated in a new Factory class, or in the base class of the "product" hierarchy.

3. Design a factory method that: may (or may not) accept arguments, finds the correct prototype object, calls clone() on that object, and returns the result.

4. The client replaces all references to the new operator with calls to the factory method.

Le singleton contrôle sa pro propre insance, il peut contrôler l’application quand et comment appelle la méthode exposée

Page 13: Design+Patterns ESTI 2010 (1)

/** * Exemple d'implémentation d'un singleton.<p> * Cet exemple ne fait rien. */ public class Singleton { /** L'instance statique */ private static Singleton instance; /** * Récupère l'instance unique de la class Singleton. * Remarque : le constructeur est rendu inaccessible */ public static Singleton getInstance() { if (null == instance) { // Premier appel instance = new Singleton(); } return instance; } /** * Constructeur redéfini comme étant privé pour interdire son appel et forcer à passer par la méthode getInstance() private Singleton() { } } En C++ plusieurs façons sont disponibles pour en créer des classes singleton : 1- Utilisation d’une déclaration globale d’une variable 2- Utilisation de ‘static’ dans une classe implémentant un message heap pour une application : Dans l’exemple suivant l’objet est crérer au premier appel . donc jamais créer si l’appel n’est pas effectué : class CMsgHndlr { public : static CMsgHndlr * GetMsgHndlr(); protected : CMsgHndlr(){} virtual ~CMsgHndlr(){} static CMsgHndlr m_MsgHndlr; // Static instance of Message handler class. }; CMsgHndlr CMsgHndlr::m_MsgHndlr; CMsgHndlr * CMsgHndlr::GetMsgHndlr() { return & m_MsgHndlr; }

The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance

Page 14: Design+Patterns ESTI 2010 (1)

Before After

// A global variable is default initialized // when it is declared - but it is not ini tialized in earnest until its first use. // This requires that the initialization // code be replicated throughout the application. class GlobalClass { int m_value; public: GlobalClass( int v=0 ) { m_value = v; } int get_value() { return m_value; } void set_value( int v ) { m_value = v; } }; // Default initialization GlobalClass* global_ptr = 0; void foo( void ) { // Initialization on first use if ( ! global_ptr ) global_ptr = new GlobalClass; global_ptr->set_value( 1 ); cout << "foo: global_ptr is " << global_ptr->get_value() << '\n'; } void bar( void ) { if ( ! global_ptr ) global_ptr = new GlobalClass; global_ptr->set_value( 2 ); cout << "bar: global_ptr is " << global_ptr->get_value() << '\n'; } int main( void ) { if ( ! global_ptr ) global_ptr = new GlobalClass; cout << "main: global_ptr is " << global_ptr->get_value() << '\n'; foo(); bar(); } // main: global_ptr is 0 // foo: global_ptr is 1 // bar: global_ptr is 2

// Make the class responsible for its own global pointer and "initialization on first use" (by using a private static pointer and a public static accessor method). The client uses only the public accessor method. class GlobalClass { int m_value; static GlobalClass* s_instance; GlobalClass( int v=0 ) { m_value = v; } public: int get_value() { return m_value; } void set_value( int v ) { m_value = v; } static GlobalClass* instance() { if ( ! s_instance ) s_instance = new GlobalClass; return s_instance; } }; // Allocating and initializing GlobalClass's static data member. The pointer is being // allocated - not the object itself. GlobalClass* GlobalClass::s_instance =0; void foo( void ) { GlobalClass::instance()->set_value( 1 ); cout << "foo: global_ptr is " << GlobalClass::instance()->get_value() << '\n'; } void bar( void ) { GlobalClass::instance()->set_value(2); cout << "bar: global_ptr is " << GlobalClass::instance()->get_value() = << '\n'; } int main( void ) { cout << "main: global_ptr is " << GlobalClass::instance()->get_value() << '\n'; //begin manipulation of the global pointer foo(); bar(); } // main: global_ptr is 0 // foo: global_ptr is 1 // bar: global_ptr is 2

1.4. 1. Define a private static attribute in the "single instance" class. 2. Define a public static accessor function in the class. 3. Do "lazy initialization" (creation on first use) in the accessor function. 4. Define all constructors to be protected or private . 5. Clients may only use the public accessor method to manipulate the Singleton.

Page 15: Design+Patterns ESTI 2010 (1)

Les pattern de structure

Ces patterns gèrent les formes de structure : � Comment les objets sont assemblés � Les patterns sont complémentaires les uns des autres

Pattern Description

Adapter rendre un objet conformant à un autre Bridge pour lier une abstraction à une implantation

Composite basé sur des objets primitifs et composants

Decorator

ajoute des services à un objet

Facade cache une structure complexe

Flyweight petits objets destinés à être partagés

Proxy un objet en masque un autre

1. Le Adapter Ce pattern est très utilisé pour encapsuler un ensemble de fonctions sous la même implémentation (Wrapping). L’utilisation de plusieurs APIs pour une question de portabilité est un très bon exemple d’utilisation de l’adapteur. En effet, comme son nom l’indique il permet d’adapter une implémentation à une autre afin de généraliser les APIs. Il permet de mettre en conformité de composants d’origines diverses. Une autre problématique traité au niveau de ce pattern est quand on veut utiliser plusieurs sous-classes mais il est coûteux de redéfinir l'interface de chaque sous-classe en les sous-classant. Un adapter peut adapter l'interface au niveau du parent. La structure est assez simple, elle redéfinit les accès aux méthodes de l’API. L’interface « Adapteur » permet de définir l’ensemble des méthodes qui seront communes à tous les adaptateurs. La classe « AdapteurImpl » est l’implémentation de l’adaptateur qui permet de faire le lien entre l’API et la normalisation qu’on souhaite mettre en place. La/les classe(s) cible(s) représentent l’ensemble des fonctions de l’API utilisé.

Page 16: Design+Patterns ESTI 2010 (1)

Ce pattern permet donc de rajouter une couche logicielle afin de normaliser des implémentations par exemple. Plusieurs raisons s’offrent à nous pour l’utilisation de ce pattern :

• Le besoin de ne pas dépendre d’une API particulière ou de pouvoir en changer rapidement. • Une portabilité encore plus forte.

Le seul cas où il est difficile de partir dans ce genre de concept est lorsque le temps pour le développement de votre application est très limité. Car ceci demande beaucoup de patience lorsque les APIs à uniformiser sont importantes (exemple API Javamail et Lotus pour l’envoi des mails). Voici un exemple très simple d’utilisation de ce pattern. Dans celui-ci nous « émulons » le fait que nous ayons nos 2 APIs d’envoi de mail (ApiMail1 et ApiMail2). Notre adapter, ici, nous permettra d’utiliser l’API que l’on veut, très facilement (nous spécifions une méthode mail() dans notre interface de normalisation).

• Hiérarchie des packages :

Les packages adapter.apimail1 et adapter.apimail2 représentent nos deux apis de mails complètement différentes l’une de l’autre. Cependant elles ont le point commun de pouvoir envoyer des mails.

• L’API mail1 • L’API mail2

package adapter.apimail1;

public class ApiMailBase { public ApiMailBase() { } public static boolean envoiMail() { System.out.println("Envoi mail avec ApiMail1"); return true ; } }

package adapter.apimail2 ; public class Base { public Base() { } public static boolean sendMail() { System.out.println("Envoi de mail avec ApiMail2"); return true ; } }

• L’interface ApiMailAdapter permet d’indiquer les fonctionnalités communes que les apis sont capables d’exécuter.

L’Adapter est un moyen commode de faire fonctionner un objet avec une interface qu’il ne

possède pas !

Page 17: Design+Patterns ESTI 2010 (1)

package adapter; public interface ApiMailAdapter { // Méthode commune à toutes les apis public boolean mail(); } • Implémentation de l’adapter pour

l’API mail1 : • Implémentation de l’adapter pour l’API mail2 :

package adapter; import adapter.apimail1.ApiMailBase; public class ApiMail1AdapterImpl implements ApiMailAdapter { public boolean mail() { return ApiMailBase.envoiMail(); } }

package adapter; import adapter.apimail2.Base;

public class ApiMail2AdapterImpl implements ApiMailAdapter { public ApiMail2AdapterImpl() { } public boolean mail() { return Base.sendMail(); } }

• Le client (qui permet ici de tester l’application) :

package adapter; public class Client { protected ApiMailAdapter api; //constructeur public Client(ApiMailAdapter api) { this .api = api; } public ApiMailAdapter getApi(){ return api; } public static void main(String []args){ Client clientAvecApiMail1 = new Client(new ApiMail1AdapterImpl()); Client clientAvecApiMail2 = new Client(new ApiMail2AdapterImpl()); // Envoi mail usage d’une méthode commune qq soit l’api clientAvecApiMail1.getApi().mail(); clientAvecApiMail2.getApi().mail(); } }

1. Identify the players: the component(s) that want to be accommodated , and the component that needs to adapt.

2. Identify the interface that the client requires. 3. Design a "wrapper" class that can "impedance match" the adaptee to the client. 4. The adapter/wrapper class "has a" instance of the adaptee class. 5. The adapter/wrapper class "maps" the client interface to the adaptee interface. 6. The client uses (is coupled to) the new interface

Page 18: Design+Patterns ESTI 2010 (1)

2. Le Composite Le design pattern composite compose les objets dans les structures correspondent à une structure récursive ; de type arbre ou arborescence. Il permet alors d’afficher une partie ou l’intégralité d’une hiérarchie. La structure est la même que celle d’un arbre. Celle-ci se compose :

• Classe abstraite « Component » qui définit les attributs et méthodes communes (feuilles/nœuds). • Une classe d’implémentation : « Leaf » qui représente une feuille. • Une classe d’implémentation : « Composite » qui représente le nœud de l’arbre.

Ce pattern est surtout utilisé dans les applications utilisant des hiérarchies (arbres). Pour cela nous allons simplement créer une application qui simule une hiérarchie d’éléments (chaîne de caractère) et les afficher. Voici le résultat que nous allons produire : + Racine + RacineElement1 + SousRoot1 -+ SousSousRoot --+ SSElement1 --+ SSElement2 --+ SSElement3 + RacineElement2 + SousRoot2 -+ SElement1 -+ SElement2 -+ SElement3 Cette application est composée de 4 classes dont 3 principales (qui représentent le pattern) :

Page 19: Design+Patterns ESTI 2010 (1)

• La classe abstract AbstractElement (Component) : permet de définir le type d’élément à hiérarchiser.

package composite; public abstract class AbstractElement { protected String name; public AbstractElement(String name) { this .name = name; } public abstract boolean add(AbstractElement e); public abstract boolean remove(AbstractElement e); public boolean display(int indentation) { StringBuffer strBuf = new StringBuffer(); for (int i = 0; i < indentation; i++) strBuf.append("-"); System.out.println(strBuf.toString() + "+ " + this .name); return true; } }

• La classe Leaf : représente une feuille dans votre arbre. Elle implémente AbstractElement .

package composite; public class Leaf extends AbstractElement { public Leaf(String name) { super (name); } public boolean add(AbstractElement e) { System.err.println("Impossible de rajouter des éléments dans une feuille"); return false ; } public boolean remove(AbstractElement e) { System.err.println("Impossible de supprimer des éléments dans une feuille"); return false ; } }

• La classe Node : représente un nœud dans votre arbre. Cette classe implémente AbstractElement . On peut y ajouter des feuilles ou d’autres nœuds.

package composite; import java.util.ArrayList; public class Node extends AbstractElement { private ArrayList elements; public Node(String name) { super (name);

Page 20: Design+Patterns ESTI 2010 (1)

elements = new ArrayList(); } public boolean add(AbstractElement e) { elements.add(e); return true ; } public boolean remove(AbstractElement e) { elements.remove(e); return true ; } public boolean display(int indentation) { super .display(indentation); for (int i = 0; i < elements.size(); i++) ((AbstractElement) elements.get(i)).display(indentation + 1); return true ; } }

• La classe Client n’est autre que la classe de lancement de l’application :

package composite; public class Client { public static void main(String[] args) { Node root = new Node("Racine"); Node sousRoot1 = new Node("SousRoot1"); Node sousSousRoot = new Node("SousSousRoot"); Node sousRoot2 = new Node("SousRoot2"); // sousSousRoot sousSousRoot.add(new Leaf("SSElement1")); sousSousRoot.add(new Leaf("SSElement2")); sousSousRoot.add(new Leaf("SSElement3")); // sousRoot1 sousRoot1.add(sousSousRoot); // sousRoot2 sousRoot2.add(new Leaf("SElement1")); sousRoot2.add(new Leaf("SElement2")); sousRoot2.add(new Leaf("SElement3")); // Racine root.add(new Leaf("RacineElement1")); root.add(sousRoot1); root.add(new Leaf("RacineElement2")); root.add(sousRoot2); root.display(-1); } }

Page 21: Design+Patterns ESTI 2010 (1)

Before After class File { public File( String name ) { m_name = name; } public void ls() { System.out.println( Composite.g_indent + m_name ); } private String m_name; } class Directory { public Directory( String name ) { m_name = name; } public void add ( Object obj ) { m_files.add( obj ); } public void ls () { System.out.println( Composite.g_indent + m_name ); Composite.g_indent.append( " " ); for (int i=0; i < m_files.size(); ++i) { Object obj = m_files.get(i); // ***** Recover the type of this object *** if (obj.getClass().getName().equals( "Directory" )) ((Directory) obj).ls(); else ((File) obj).ls(); } Composite.g_indent.setLength( CompositeDemo.g_indent.length() - 3 ); } private String m_name; private ArrayList m_files = new ArrayList(); } public class CompositeDemo { public static StringBuffer g_indent = new StringBuffer(); public static void main ( String[] args ) { Directory one = new Directory("dir111"), two = new Directory("dir222"), thr = new Directory("dir333"); File a = new File("a"), b = new File("b"), c = new File("c"), d = new File("d"), e = new File("e"); one.add( a ); one.add(two); one.add(b); two.add(c); two.add(d); two.add(thr ); thr.add(e); one.ls(); } } // dir111 // a // dir222 // c // d // dir333 // e // b

// ***** Define a "lowest common denominator" ***** interface AbstractFile { public void ls(); } // * File implements the "lowest common denominator" class File implements AbstractFile { public File( String name ) { m_name = name; } public void ls () { System.out.println( CompositeDemo.g_indent + m_name ); } private String m_name; } // **Directory implements the "lowest common denominator" class Directory implements AbstractFile { public Directory( String name ) { m_name = name; } public void add ( AbstractFile obj ) { m_files.add( obj ); } public void ls () { System.out.println( CompositeDemo.g_indent + m_name ); CompositeDemo.g_indent.append( " " ); for (int i=0; i < m_files.size(); ++i) { //**Leverage the "lowest common denominator AbstractFile obj = (AbstractFile) m_files.get(i); obj.ls(); } CompositeDemo.g_indent.setLength( CompositeDemo.g_indent.length() - 3 ); } private String m_name; private ArrayList m_files = new ArrayList(); } public class CompositeDemo { public static StringBuffer g_indent = new StringBuffer(); public static void main ( String[] args ) { Directory one = new Directory("dir111"), two = new Directory("dir222"), thr = new Directory("dir333"); File a = new File("a"), b = new File("b"), c = new File("c"), d = new File("d"), e = new File("e"); one.add( a ); one.add( two ); one.add( b ); two.add( c ); two.add( d ); two.add( thr); thr.add( e ); one.ls(); } }

Page 22: Design+Patterns ESTI 2010 (1)

C++ example Before After

class File { public File( String name ) { m_name = name; } public void ls() { System.out.println( Composite.g_indent + m_name ); } private String m_name; } class Directory { public Directory( String name ) { m_name = name; } public void add ( Object obj ) { m_files.add( obj ); } public void ls () { System.out.println( Composite.g_indent + m_name ); Composite.g_indent.append( " " ); for (int i=0; i < m_files.size(); ++i) { Object obj = m_files.get(i); // ***** Recover the type of this object *** if (obj.getClass().getName().equals( "Directory" )) ((Directory) obj).ls(); else ((File) obj).ls(); } Composite.g_indent.setLength( CompositeDemo.g_indent.length() - 3 ); } private String m_name; private ArrayList m_files = new ArrayList(); } public class CompositeDemo { public static StringBuffer g_indent = new StringBuffer(); public static void main ( String[] args ) { Directory one = new Directory("dir111"), two = new Directory("dir222"), thr = new Directory("dir333"); File a = new File("a"), b = new File("b"), c = new File("c"), d = new File("d"), e = new File("e"); one.add( a ); one.add(two); one.add(b); two.add(c); two.add(d); two.add(thr ); thr.add(e); one.ls(); } } // dir111 // a // dir222 // c // d // dir333 // e // b

// ***** Define a "lowest common denominator" ***** interface AbstractFile { public void ls(); } // * File implements the "lowest common denominator" class File implements AbstractFile { public File( String name ) { m_name = name; } public void ls () { System.out.println( CompositeDemo.g_indent + m_name ); } private String m_name; } // **Directory implements the "lowest common denominator" class Directory implements AbstractFile { public Directory( String name ) { m_name = name; } public void add ( AbstractFile obj ) { m_files.add( obj ); } public void ls () { System.out.println( CompositeDemo.g_indent + m_name ); CompositeDemo.g_indent.append( " " ); for (int i=0; i < m_files.size(); ++i) { //**Leverage the "lowest common denominator AbstractFile obj = (AbstractFile) m_files.get(i); obj.ls(); } CompositeDemo.g_indent.setLength( CompositeDemo.g_indent.length() - 3 ); } private String m_name; private ArrayList m_files = new ArrayList(); } public class CompositeDemo { public static StringBuffer g_indent = new StringBuffer(); public static void main ( String[] args ) { Directory one = new Directory("dir111"), two = new Directory("dir222"), thr = new Directory("dir333"); File a = new File("a"), b = new File("b"), c = new File("c"), d = new File("d"), e = new File("e"); one.add( a ); one.add( two ); one.add( b ); two.add( c ); two.add( d ); two.add( thr); thr.add( e ); one.ls(); } }

Page 23: Design+Patterns ESTI 2010 (1)

« Indépendamment du Design Pattern Iterator qui peu t être utilisé dans ce contexte. Le présent Design Pattern est utilisé chaque fois qu’un client veut exécuter des commandes sur des objets abstraction faite si ces objets contiennent eux-mêm es d’autres objets. »

Don Robert

1. Ensure that your problem is about representing "whole-part" hierarchical relationships.

2. Consider the heuristic, "Containers that contain containees, each of which could be a container." For example, "Assemblies that contain components, each of which could be an assembly." Divide your domain concepts into container classes, and containee classes.

3. Create a "lowest common denominator" interface that makes your containers and containees interchangeable. It should specify the behavior that needs to be exercised uniformly across all containee and container objects.

4. All container and containee classes declare an "is a" relationship to the interface. 5. All container classes declare a one-to-many "has a" relationship to the interface. 6. Container classes leverage polymorphism to delegate to their containee objects. 7. Child management methods [e.g. addChild(), removeChild()] should normally be

defined in the Composite class. Unfortunately, the desire to treat Leaf and Composite objects uniformly may require that these methods be promoted to the abstract Component class.

Page 24: Design+Patterns ESTI 2010 (1)

3. Le Décorateur ‘Decorator’ Le pattern Décorateur (Decorator) attache dynamiquement des responsabilités supplémentaires à un objet. Il fournit une alternative souple à l’héritage, pour étendre des fonctionnalités à l’exécution.

Le souci de l’héritage est l’ajout de fonctionnalités de façon statique. En effet, l’héritage de classe se définit lors de l’écriture du programme et ne peut être modifié après la compilation. Or, dans certains cas, on peut vouloir rajouter des fonctionnalités de façon dynamique.

Exemple :

Dans le cas où on est demandé de fournir une interface graphique supportant des fenêtres avec ou sans bordure, avec ou sans scroll bar. Nous pouvons définir une hiérarchie de classe comme suit :

.

Cette structure s’avère figé et difficile »ment maintenable quan’aux évolution d’ergonomie. Le décorateur permet une composition dynamique de possibilités : Widget* aWidget = new BorderDecorator( new Horizon talScrollBarDecorator(

New VerticalScrollBarDecorator( new Window( 80, 2 4 ))));

aWidget->Dessiner();

Cette flexibilité est assurée par une encapsulation récursive. Suivant le schéma suivant :

Fenêtre Dessiner ()

Fenêtre avec bordure Fenêtre avec scroll bar vertical

<<LCD Interface >> Dessiner ()

Fenêtre Dessiner ()

Décorateur Dessiner ()

bordure Scroll bar horizentale Scroll bar verticale

1

Fenêtre avec scroll bar vertical & horizontal

Fenêtre avec scroll bar horizental

Fenêtre avec scroll bar vertical & horizontal avec bordure

Page 25: Design+Patterns ESTI 2010 (1)

La solution consiste à encapsuler l’objet d’origine (fenêtre) derrière une interface abstraite. Le décorateur ainsi que l’objet d’origine héritent de cette interface. L’interface utilise une composition récursive pour permettre un nombre illimité de couche de décoration rajouté à l’objet d’origine.

Before After

// Inheritance run amok class A { public: virtual void do_it() { cout << 'A'; } }; class AwithX : public A { public: /*virtual*/ void do_it () { A::do_it(); do_X(); }; private: void do_X() { cout << 'X'; } }; class AwithY : public A { public: /*virtual*/ void do_it () { A::do_it(); do_Y(); } protected: void do_Y() { cout << 'Y'; } }; class AwithZ : public A { public: /*virtual*/ void do_it () { A::do_it(); do_Z(); } protected: void do_Z() { cout << 'Z'; } };

// Replacing inheritance with wrapping-delegation // Discussion. Use aggregation instead of inheritance to implement embellishments to a "core" object. Client can dynamically compose permutations, instead of the architect statically wielding multiple inheritance. class I { public: virtual ~I() { } virtual void do_it() = 0; }; class A : public I { // Core Class public: ~A() { cout << "A dtor" << '\n'; } /*virtual*/ void do_it() { cout << 'A'; } }; class D : public I { // Decorator Class public: D( I* inner ) { m_wrappee = inner; } ~D() { delete m_wrappee; } /*virtual*/void do_it(){ m_wrappee->do_it(); } private: I* m_wrappee; }; class X : public D { public: X( I* core ) : D( core ) { } ~X() { cout << "X dtor" << " "; } /*virtual*/ void do_it() { D::do_it(); cout << 'X' ; }

Page 26: Design+Patterns ESTI 2010 (1)

class AwithXY :public AwithX, public AwithY { public: /*virtual*/ void do_it() { AwithX::do_it(); AwithY::do_Y(); } }; class AwithXYZ:public AwithX, public AwithY,public AwithZ{ public: /*virtual*/ void do_it() { AwithX::do_it(); AwithY::do_Y(); AwithZ::do_Z(); } }; int main( void ) { AwithX anX; AwithXY anXY; AwithXYZ anXYZ; anX.do_it(); cout << '\n'; anXY.do_it(); cout << '\n'; anXYZ.do_it(); cout << '\n'; } // AX // AXY // AXYZ

}; class Y : public D { public: Y( I* core ) : D( core ) { } ~Y() { cout << "Y dtor" << " "; } /*virtual*/ void do_it() { D::do_it(); cout << 'Y' ; } }; class Z : public D { public: Z( I* core ) : D( core ) { } ~Z() { cout << "Z dtor" << " "; } /*virtual*/ void do_it() { D::do_it(); cout << 'Z' ; } }; int main( void ) { I* anX = new X( new A ); I* anXY = new Y( new X( new A ) ); I* anXYZ = new Z(new Y(new X(new A ))); anX->do_it(); cout << '\n'; anXY->do_it(); cout << '\n'; anXYZ->do_it(); cout << '\n'; delete anX; delete anXY; delete anXYZ; } // AX // AXY // AXYZ // X dtor A dtor // Y dtor X dtor A dtor // Z dtor Y dtor X dtor A dtor

1. Ensure the context is: a single core (or non-optional) component, several optional embellishments or wrappers, and an interface that is common to all.

2. Create a "Lowest Common Denominator" interface that makes all classes interchangeable.

3. Create a second level base class (Decorator) to support the optional wrapper classes.

4. The Core class and Decorator class inherit from the LCD interface.

5. The Decorator class declares a composition relationship to the LCD interface, and this data member is initialized in its constructor.

6. The Decorator class delegates to the LCD object.

7. Define a Decorator derived class for each optional embellishment.

8. Decorator derived classes implement their wrapper functionality - and - delegate to the Decorator base class.

9. The client configures the type and ordering of Core and Decorator objects.

Page 27: Design+Patterns ESTI 2010 (1)

4. Le Pont ‘Bridge’ Ce pattern permet d’éviter d’avoir un lien permanent entre l'abstraction et l'implantation. L'implantation est choisie à l'exécution par exemple. L'abstraction et l'implantation sont toutes les deux susceptibles d'être raffinées, étoffées, …. Cependant, toutes les modifications éventuellement subies par l'implantation ou l'abstraction ne doivent pas avoir d'impacts sur le client (pas de recompilation)

Ce diagramme de classe montre à la fois les concepts d’héritage multiple ainsi que le lien de composition entre la classe « Abstraction » et « Implementor ». C’est ce lien qui donne toute la particularité de ce Design Pattern.

Impl1

Impl2

Impl3

Client BBrr iiddggee

Intermediary defines the interface but not the implementation.

E.g., Motif/Mac/Windows look and feel.

Page 28: Design+Patterns ESTI 2010 (1)

• La classe Client permet d’instancier et d’appeler la méthode operation().

package bridge; public class Client { public static void main(String[] args) {

Abstraction abstraction = new RefinedAbstraction(); //abstraction.implementor = new ConcreteImplementorA(); setImplementor (new ConcreteImplementorA()); abstraction.operation(); //abstraction.implementor = new ConcreteImplementorB(); setImplementor (new ConcreteImplementorB()); abstraction.operation(); } }

• La classe Implementor est la super classe qui s’occupe de la partie implémentation, on retrouvera la méthode operation() qui sera redéfinie dans les sous classes.

package bridge; abstract class Implementor { abstract public void operation(); }

• La classe ConcreteImplementorA qui hérite de la classe Implementor et redéfini la méthode operation().

public class ConcreteImplementorA extends Implementor { public void operation() { System.out.println("ConcreteImplementorA Operation"); } }

Bridge Pattern is all about decoupling an abstraction (interface) from its implementation so

that the two can vary independently. In MFC, the process of storing/retrieving an object

to/from a persistence mechanism (like a file) is called Serialization. MFC uses the Bridge

Pattern to implement Serialization. CArchive and CFile classes implement object

Serialization. CArchive class provides the interface for writing/reading an object to/from a

persistence mechanism whereas the CFile and its sub classes provides implementation for

different persistence mechanisms such as memory, disk file, sockets etc.

A CArchive object is configured with an object of class CFile (or a derived class) during its

construction, from which it obtains the necessary information for serialization, including the

filename and type of the requested operation (a read or write). Client performing the

Serialization operation can use CArchive object without regarding the persistence

mechanism implemented by CFile classes.

Page 29: Design+Patterns ESTI 2010 (1)

• La classe ConcreteImplementorB qui hérite de la classe Implementor et redéfini la méthode operation(). La méthode operation() n’affichera pas la même ligne que la classe précédante.

public class ConcreteImplementorB extends Implementor { public void operation() { System.out.println("ConcreteImplementorB Operation"); } }

• La classe Abstraction permet de simuler la présence de la méthode operation() au niveau abstrait.

package bridge; public class Abstraction { protected Implementor implementor; public void operation() { implementor.operation(); } public void setImplementor(Implementor impl) { //pour le choix du client this .implementor = impl; } }

• La classe RefinedAbstraction hérite de la classe ci–dessus et n’a que pour but de lier la partie d’abstraction d’implémentation.

package bridge; public class RefinedAbstraction extends Abstraction { public void operation() { implementor.operation(); } }

1. Decide if two orthogonal dimensions exist in the domain. These independent

concepts could be: abstraction<>platform, or domain<>infrastructure , or front-end<>back-end, or interface<>implementation.

2. Design the separation of concerns: what does the client want, and what do the platforms provide.

3. Design a platform-oriented interface that is minimal, necessary, and sufficient. Its goal is to decouple the abstraction from the platform.

4. Define a derived class of that interface for each platform. 5. Create the abstraction base class that "has a" platform object and delegates

the platform-oriented functionality to it. 6. Define specializations of the abstraction class if desired.

Page 30: Design+Patterns ESTI 2010 (1)

Exemple: Thread scheduling Il y’a deux types de threads et deux types de système d’exploitation (platformes). Suivant l’approche de spécialisation, nous avons à définir une permutation pour chaque diemnsion. Sinous rajoutons une nouvelle platformes la hierarchie devient : Suivant cette approche d’héritage, le nombre de classe qui resulte sera le produit du nombre de scheduling avec le nombre de platforme Ce pattern propose une factorisation de cette explosion de hiérarchie en deux hiérarchies orthogonales : � Abstarction de la plateforme (indépendance de la plateforme) � Implementation dépendante de la plateforme

Le client pourra choir dynamiquement (au runtime) l’implémentation qu’il utilise (via le membre Impl)

Thread scheduler

Time sliced thread scheduler Premptive thread schedluer

Unix PTS Windows PTS Unix TSTS Windows TSTS

Thread scheduler

Time sliced thread scheduler Premptive thread schedluer

Unix PTS Windows PTS Unix TSTS Windows TSTS

JVM PTS JVM TSTS

Thread scheduler

Thread scheduler Implmentor

Premptive TS Time sliced TS Unix JVM Win

Impl

Page 31: Design+Patterns ESTI 2010 (1)

5. Le Proxy Dans ce cas le client interagit avec un intermédiaire. La requête effective sera emballée et redirigée vers le serveur. C’est le cas de la gestion de connexion, de persistance, contrôle d’accès, de comptage de références… Un cas courant d’utilisation est lorsqu’on veut pousser la construction de l’objet que lors de la première utilisation. Le proxy se place alors entre le client et l’objet réel, et qui n’effectue la construction ‘coûteuse’ que lorsque nécessaire.

Adapter provides a different interface to its subject. Proxy provides the same interface. [GoF. p216]

Page 32: Design+Patterns ESTI 2010 (1)

Before After

// Direct coupling, lots of start-up and shut-down overhead class Image { int m_id; static int s_next; public: Image() { m_id = s_next++; cout << " $$ ctor: "<< m_id << '\n'; } ~Image() { cout << " dtor: " << m_id << '\n'; } void draw() { cout << " drawing image"<< m_id <<'\n'; } }; int Image::s_next = 1; int main( void ) { Image images[5]; for (int i; true; ) { cout << "Exit [0], Image [1-5] : "; cin >> i; if (i == 0) break; images[i-1].draw(); } } // $$ ctor: 1 // $$ ctor: 2 // $$ ctor: 3 // $$ ctor: 4 // $$ ctor: 5 // Exit [0], Image [1-5] : 2 // drawing image 2 // Exit [0], Image [1-5] : 4 // drawing image 4 // Exit [0], Image [1-5] : 2 // drawing image 2 // Exit [0], Image [1-5] : 0 // dtor: 5 // dtor: 4 // dtor: 3 // dtor: 2 // dtor: 1

/ /create on first Use class RealImage { int m_id; public: RealImage( int i ) { m_id = i; cout << " $$ ctor: "<< m_id << '\n'; } ~RealImage(){cout <<"dtor: "<< m_id << '\n';} void draw() { cout << "drawing image " << m_id << '\n'; } }; // 1. Design an "extra level of indirection" wrapper class class Image { // 2. The wrapper class holds a pointer to the real class RealImage* m_the_real_thing; int m_id; static int s_next; public: Image() { m_id = s_next++; // 3. Initialized to null m_the_real_thing = 0; } ~Image() { delete m_the_real_thing; }

void draw () { // 4. When a request comes in, the real object is created "on first use" if ( ! m_the_real_thing) m_the_real_thing = new RealImage( m_id ); // 5. The request is always delegated m_the_real_thing->draw(); } }; int Image::s_next = 1; int main( void ) { Image images[5]; for (int i; true; ) { cout << "Exit[0], Image[1-5]: "; cin >> i; if (i == 0) break; images[i-1].draw(); } } // Exit [0], Image [1-5] : 2 // $$ ctor: 2 // drawing image 2 // Exit [0], Image [1-5] : 4 // $$ ctor: 4 // drawing image 4 // Exit [0], Image [1-5] : 2 // drawing image 2 // Exit [0], Image [1-5] : 0 // dtor: 4 // dtor: 2

Page 33: Design+Patterns ESTI 2010 (1)

Exemple C# using System; namespace ConsoleApplicationTest.FundamentalPatterns.Protecti onProxyPattern { public interface IClient { string GetAccountNo(); } public class RealClient : IClient { private string accountNo = "12345"; public RealClient() { Console.WriteLine("RealClient: Initiali zed"); } public string GetAccountNo() { Console.WriteLine("RealClient's Account No: " + accountNo); return accountNo; } } public class ProtectionProxy : IClient { private string password; //password to get secret RealClient client; public ProtectionProxy(string pwd) { Console.WriteLine("ProtectionProxy: Ini tialized"); password = pwd; client = new RealClient(); } // Authenticate the user and return the Acc ount Number public String GetAccountNo() { Console.Write("Password: "); string tmpPwd = Console.ReadLine(); if (tmpPwd == password) { return client.GetAccountNo(); } else { Console.WriteLine("ProtectionProxy: Illegal password!"); return ""; } } } class ProtectionProxyExample { [STAThread] public static void Main(string[] args) { IClient client = new ProtectionProxy("t hePassword"); Console.WriteLine(); Console.WriteLine("main received: " + c lient.GetAccountNo()); Console.WriteLine("\nPress any key to c ontinue . . ."); Console.Read(); } }

}

1. Identify the leverage or “aspect” that is best implemented as a wrapper or surrogate. 2. Define an interface that will make the proxy and the original component interchangeable. 3. Consider defining a Factory that can encapsulate the decision of whether a proxy or original

object is desirable. 4. The wrapper class holds a pointer to the real class and implements the interface. 5. The pointer may be initialized at construction, or on first use. 6. Each wrapper method contributes its leverage, and delegates to the wrappee object

Page 34: Design+Patterns ESTI 2010 (1)

1.5. Les patterns de comportement

Ces pattern décrivent les Formes de comportement des : � algorithmes � comportements entre objets � formes de communication entre objet

Pattern Description

Command • spécifier, stocker et exécuter des actions à des moments différents. • on veut pouvoir "défaire". Les commandes exécutées peuvent être stockées ainsi que les états des objets affectés... • on veut implanter des transactions ; actions de "hautniveau".

Chain of responsability

• plus d'un objet peut traiter une requète, et il n'est pas connu a priori • l'ensemble des objets pouvant traiter une requète est construit dynamiquement

State • Le comportement d'un objet dépend de son état, qui change à l'exécution • Les opérations sont constituées de partie conditionnelles de grande taille (case)

Iterator • pour accéder à un objet composé dont on ne veut pas exposer la structure interne • pour offrir plusieurs manières de parcourir une structure composée • pour offrir une interface uniforme pour parcourir différentes structures

Observer • Une abstraction a plusieurs aspects, dépendant l’un de l’autre. Encapsuler ces aspects indépendament permet de les réutiliser séparément. • Quand le changement d’un objet se répercute vers d’autres. • Quand un objet doit prévenir d’autres objets sans pour autant les connaitre.

Memento • on veut sauvegarder tout ou partie de l’état d’un objet pour éventuellement pouvoir le restaurer, et • une interface directe pour obtenir l’état de l’objet briserait l’encapsulation

Visitor • une structure d'objets contient de nombreuses classes avec des interfaces différentes et on veut appliquer des operations diverses sur ces objets. • les structures sont assez stables, et les opération sur leurs objets évolutives.

Page 35: Design+Patterns ESTI 2010 (1)

1. Le ‘Command ‘ Le principal objectif de ce Design Pattern est d’encapsuler une requête sous forme d’objet. Ce pattern est utilisé lorsque :

• on veut paramétrer des objets pour réaliser certaines actions avec différentes requêtes • pour spécifier, mettre en file, et exécuter des requêtes à des moments différents • pour implémenter des opérations réversibles • pour structurer un système à l’aide d’opérations de haut niveau construites à partir

d’opérations primitives.

Ce design pattern est représenté par :

• une interface CallbackInterface qui définit les méthodes de la commande qui devront être implémentées.

• Plusieurs classes CallbackOne et CallbackTwo qui implémente l’interface CallbackInterface servant à définir la jonction entre l’objet « Receiver » et les actions à effectuer.

• une classe « Client » qui génère les commandes voulues « CallbackInterface ». Ces commandes passent à une autre classe qui possède les ressources pour les exécuter

Ce design pattern est très utilisé dans la programmation objet. En effet il se retrouve dans les applications utilisant les files d’attentes, les logs, les supports d’opérations réversibles (“undo”)…

Page 36: Design+Patterns ESTI 2010 (1)

Before After

//the client has to query the "type" of each object, and manually invoke the desired method. class Giant { public: enum Type { Fee, Phi, Pheaux }; Giant() { m_id = s_next++; m_type = (Type) (m_id % 3); } Type get_type() { return m_type; } void fee() { cout << m_id << "-fee "; } void phi() { cout << m_id << "-phi "; } void pheaux() { cout << m_id << "-pheaux "; } private: Type m_type; int m_id; static int s_next; }; int Giant::s_next = 0; template <typename T> class Queue { public: Queue() { m_add = m_remove = 0; } void enque( T* c ) { m_array[m_add] = c; m_add = (m_add + 1) % SIZE; } T* deque() { int temp = m_remove; m_remove = (m_remove + 1) % SIZE; return m_array[temp]; } private: enum { SIZE = 8 }; T* m_array[SIZE]; int m_add, m_remove; }; int main( void ) { Queue<Giant> que; Giant input[6], *bad_guy; for (int i=0; i < 6; i++) que.enque( &input[i] ); for (int i=0; i < 6; i++) { bad_guy = que.deque(); if (bad_guy->get_type() == Giant::Fee) bad_guy->fee(); else if (bad_guy->get_type() == Giant::Phi) bad_guy->phi(); else if(bad_guy->get_type()==Giant::Pheaux) bad_guy->pheaux(); } cout << '\n'; } // 0-fee 1-phi 2-pheaux 3-fee 4-phi 5-pheaux

//the desired method is encapsulated in each Command object. class Giant { //receiver object public : Giant() { m_id = s_next++; } void fee(){ cout << m_id << "-fee ";} void phi(){ cout << m_id << "-phi ";} void pheaux(){cout << m_id <<"-pheaux";} private : int m_id; static int s_next; }; int Giant::s_next = 0; typedef void (Giant::*Action)(); class Command { public : Command( Giant* object, Action method ){ m_object = object; m_method = method; } void execute() { (m_object->*m_method)(); } private : Giant* m_object; Action m_method; }; template <typename T> class Queue { public: Queue() { m_add = m_remove = 0; } void enque( T* c ) { m_array[m_add] = c; m_add = (m_add + 1) % SIZE; } T* deque() { int temp = m_remove; m_remove = (m_remove + 1) % SIZE; return m_array[temp]; } private: enum { SIZE = 8 }; T* m_array[SIZE]; int m_add, m_remove; }; int main( void ) { Queue<Giant> que; Command* input[] = { new Command( new Giant, &Giant::fee ), new Command( new Giant, &Giant::phi ), new Command( new Giant,&Giant::pheaux), new Command( new Giant, &Giant::fee ), new Command( new Giant, &Giant::phi ), new Command( new Giant, &Giant::pheaux)

No arguments to pass

Page 37: Design+Patterns ESTI 2010 (1)

}; for (int i=0; i < 6; i++) que.enque( input[i] ); for (int i=0; i < 6; i++) que.deque()->execute(); cout << '\n'; } // 0-fee 1-phi 2-pheaux 3-fee 4-phi 5-pheaux

1. Define a Command interface with a method signature like execute(). 2. Create one or more derived classes that encapsulate some subset of the

following: a "receiver" object, the method to invoke, the arguments to pass. 3. Instantiate a Command object for each deferred execution request. 4. Pass the Command object from the creator (sender) to the invoker (receiver). 5. The invoker decides when to execute().

Page 38: Design+Patterns ESTI 2010 (1)

2. Le Iterator Ce pattern est très utilisé et permet d’accéder à des éléments d’un agrégat séquentiellement. Il a pour but premier de donner un moyen de parcourir et d’accéder aux éléments sans pour autant exposer sa structure interne. Il peut également avoir différents moyens de lister le contenu (ordre croissant/décroissant, insertion…) et permettre (si on le souhaite) d’accéder simultanément à la liste avec différents moyens (croissant et décroissant en même temps par exemple).

L’ iterator peut être utilisé pour accéder à une liste d’éléments suivant un moyen précis :

• Croissant • Décroissant • Insertion • …

Il ser t aussi à appliquer une condition de parcours permettant d’apliquer un filtre sur la liste

Visit every element in an aggregate structure in a well-defined order and perform an action on each element.

=> Defines a mechanism for enumerating elements of an aggregate without exposing the representation (by supporting first(), next(), item(), isDone(), etc.) E.g., pre/in/post-order traversals of tree.

Page 39: Design+Patterns ESTI 2010 (1)

Before After

//The class SomeClassWithData provides access to its internal data structure. Clients can accidently or maliciously trash that data structure. class SomeClassWithData { private TreeSet<Integer> m_data = new TreeSet<Integer>(); public void add( int in ) { m_data.add( in ); } public Collection get_data() { return m_data; } } class IteratorDemo { public static void main( String[] args ) { SomeClassWithData some_object = new SomeClassWithData(); for (int i=9; i > 0; --i) some_object.add( i ); Collection data=some_object.get_data(); for(java.util.Iterator it= data.iterator(); it.hasNext(); ) System.out.print( it.next() + " "); System.out.println(); // Do we really want a client to be able to // trash encapsulated state? data.clear(); data = some_object.get_data(); System.out.println( "size of data is " + data.size() ); } } // 1 2 3 4 5 6 7 8 9 // size of data is 0

// Take traversal-of-a-collection functionality out of the collection and promote it to "full object status". This simplifies the collection, allows many traversals to be active simultaneously, and decouples collection algorithms from collection data structures. class SomeClassWithData { private TreeSet<Integer> m_data = new TreeSet<Integer>(); public class Iterator { private SomeClassWithData m_collection; private java.util.Iterator m_it; private int m_current; public Iterator( SomeClassWithData in){ m_collection = in; } public void first () { m_it =m_collection.m_data.iterator(); next(); } public void next () { try { m_current = m_it.next(); } catch (NoSuchElementException ex) { m_current = -999; } } public boolean is_done () { return m_current == -999; } public int current_item () { return m_current; } } public void add ( int in ) { m_data.add( in ); } } //end of Iterator inner class public Iterator create_iterator() { return new Iterator( this ); } }//end of SomeClassWithData class class IteratorDemo { public static void main(String[] args){ SomeClassWithData some_object = new SomeClassWithData(); for (int i=9; i > 0; --i) some_object.add( i ); // get_data() has been removed. // Client has to use Iterator. SomeClassWithData.Iterator it1 = some_object.create_iterator(); SomeClassWithData.Iterator it2 =

SomeClassWithData

Page 40: Design+Patterns ESTI 2010 (1)

some_object.create_iterator(); for(it1.first();!it1.is_done(); it1.next()) System.out.print(it1.current_item() + " " ); System.out.println(); // Two simultaneous iterations for (it1.first(), it2.first(); !it1.is_done(); it1.next(), it2.next()) System.out.print(it1.current_item() + " " + it2.current_item() + " " ); System.out.println(); } } // 1 2 3 4 5 6 7 8 9 // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

1. Add a create_iterator() method to the "collection" class, and grant the "iterator" class privileged access.

2. Design an "iterator" class that can encapsulate traversal of the "collection" class. 3. Clients ask the collection object to create an iterator object. 4. Clients use the first () , is_done () , next () , and current_item () protocol to

access the elements of the collection class.

Page 41: Design+Patterns ESTI 2010 (1)

3. Le ‘Observer ‘

Dans le cas générale, un Objet possède un état interne et fournit un ensemble de services en tant que des méthodes. L’état de l’objet est décrit par des données membres.

Ce design pattern met en jeu deux Objets en intéractions : un objet ‘A’ possède une référence à un Objet B pour utiliser ses services, ‘A’ doit être linker à l’objet ‘B’ (incude B.h) et rester coupler à cet objet. La réponse du ‘Observer’ est effectivement comment découpler les deux classes ‘A’ et ‘B’ et surtout pouvoir réutiliser ‘A’ sans ramener l’objet ‘B’,

Sometimes, the state of the object 'A' may depend on the state of object 'B'. Whenever 'B' changes, 'A' should recompute its state to remain in Sync with 'B'. The situation becomes more complicated when a new Object 'C', which is dependent on the state of 'B' is brought into the application. In short, the dependency and the coupling between the objects are increased and the reusability is reduced. How can we make two or more independent objects work together without knowing each other’s existence? The answer is The Observer Pattern.

• Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. [GoF, p293]

• Encapsulate the core (or common or engine) components in a Subject abstraction, and the variable (or optional or user interface) components in an Observer hierarchy.

• The "View" part of Model-View-Controller.

Page 42: Design+Patterns ESTI 2010 (1)

Before After // The number and type of "user interface" (or dependent) objects is hard-wired in the Subject class . The user has no ability to Affect this configuration. class DivObserver { int m_div; public: DivObserver( int div ) { m_div = div; } void update( int val ) { cout << val << " div " << m_div << " is " << val / m_div << '\n'; } }; class ModObserver { int m_mod; public: ModObserver( int mod ) { m_mod = mod; } void update( int val ) { cout << val << " mod " << m_mod << " is " << val % m_mod << '\n'; } }; class Subject { int m_value; DivObserver m_div_obj; ModObserver m_mod_obj; public: Subject() : m_div_obj(4), m_mod_obj(3) { } void set_value( int value ) { m_value = value; notify(); } void notify() { m_div_obj.update( m_value ); m_mod_obj.update( m_value ); } }; int main( void ) { Subject subj; subj.set_value( 14 ); } // 14 div 4 is 3 // 14 mod 3 is 2

// The Subject class is now decoupled from the number and type of Observer objects. The client has asked for two DivObserver delegates (each configured differently) and one ModObserver delegate class Observer { public: virtual void update ( int value ) = 0; }; class Subject { int m_value; vector<Observer*> m_views; public: void attach( Observer* obs ) { m_views.push_back( obs ); } void set_val ( int value ) { m_value = value; notify (); } void notify() { for (int i=0; i < m_views.size();++i) m_views[i]->update( m_value ); } }; class DivObserver : public Observer { int m_div; Subject *m_pDocument; public: DivObserver( Subject* model, int div ) { model->attach( this ); m_pDocument = model; m_div = div; } /* virtual */ void update( int v ) { cout << v << " div " << m_div << " is " << v / m_div << '\n'; } }; class ModObserver : public Observer { int m_mod; Subject *m_pDocument; public: ModObserver( Subject* model, int mod ) {

GoF classifies the Observer Pattern as an Object Behavioral Pattern. The Observer Pattern is intended to "Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically". An object that is subjected to change is called a Subject and an object that depends on the Subject's state is called an Observer. In the above example, 'B' can be a Subject and Objects A and C can be Observers. A Subject can have any number of Observers. When the Subject changes its state, it Notifies all the Observers and the Observers query the Subject to maintain state consistency with the Subject.

Page 43: Design+Patterns ESTI 2010 (1)

model->attach( this ); m_pDocument = model; m_mod = mod; } /* virtual */ void update( int v ) { cout << v << " mod " << m_mod << " is " << v % m_mod << '\n'; } }; int main( void ) { Subject subj; DivObserver divObs1( &subj, 4 ); DivObserver divObs2( &subj, 3 ); ModObserver modObs3( &subj, 3 ); subj.set_val( 14 ); } // 14 div 4 is 3 // 14 div 3 is 4 // 14 mod 3 is 2

MFC uses a Document/View variant of the Observer Pattern. MFC's famous Document/View

architecture uses this variant. A document contains the data object and acts as a Subject. A

view is a window object through which the user updates the document and it acts as an

Observer. A document can have multiple views. Whenever the data in the document is

changed by one of the views, it updates the document by calling UpdateAllViews method,

with optional hint about the modification. To inform about the change to other views, the

document object calls OnUpdate method for each view attached to it (except the view that

called UpdateAllViews ). Derived view classes can override the OnUpdate method and

update themselves by querying the data from the document.

1. Differentiate between the core (or independent) functionality and the optional (or dependent) functionality.

2. Model the independent functionality with a "subject" abstraction. 3. Model the dependent functionality with an "observer" hierarchy. 4. The Subject is coupled only to the Observer base class. 5. The client configures the number and type of Observers. 6. Observers register themselves with the Subject. 7. The Subject broadcasts events to all registered Observers. 8. The Subject may "push" information at the Observers, or, the Observers may

"pull" the information they need from the Subject.

Page 44: Design+Patterns ESTI 2010 (1)

4. Le ‘State ‘ Le Pattern State permet, lorsqu’un objet est altéré de changer son comportement. Le changement d’état peut parfois poser des problèmes dans leurs gestions (interminables if – elseif, switch-case), le Pattern State permet de pallier à ce problème de manière simple et rapide en associant a chaque état élémentaire une classe qui sera responsable du traitement et de la transition. Cependant il n’est pas toujours obligatoire de l’utiliser dans toutes les situations. Il faudra identifier s’il sagit réellement d’un changement d’état qui doit être gérer. State doit comporter des classes précises participantes à ce Pattern :

• Context • State • ConcreteState

On utilise State lorsque : • Le comportement d'un objet dépend de son état, qui change à l'exécution. • Les opérations sont constituées de parties conditionnelles de grande taille (case).

• La classe Client permet d’instancier un objet Context et d’en afficher son état suite à des changements liés à l’appel de la méthode request() :

package state; public class Client { public static void main(String[] args) { Context c = new Context(new ConcreteStateA() ); c.show(); c.request(); c.show();

Page 45: Design+Patterns ESTI 2010 (1)

c.request(); c.show(); } }

• La classe Context comporte un attribut de type State et une méthode Request() qui change l’état.

package state; public class Context { private State state; public Context(State state) { this .state = state; } public void request() { state.handle(this ); } public void show() { System.out.println("State: " + state ); } public void setState(State state) { this .state = state; } }

• La classe State est abstraite afin de forcer les classes qui la dérive de redéfinir la méthode abstraite handle().

package state; abstract class State { abstract public void handle(Context context); }

• La classe ConcreteStateA hérite de la classe State et redéfini la méthode handle().

package state; public class ConcreteStateA extends State { public void handle(Context context) { context.setState(new ConcreteStateB()); } }

• La classe ConcreteStateB procède selon le même procédé que la classe précédente mais effectue le changement d’état dans le sens inverse.

package state; public class ConcreteStateB extends State { public void handle(Context context) { context.setState(new ConcreteStateA()); } }

Page 46: Design+Patterns ESTI 2010 (1)

Exemple d’editeur de texte

Before After // 3-speed ceiling fan state machine // Not good: unwieldy "case" statement class CeilingFanPullChain { private int m_current_state; public m_value; public CeilingFanPullChain() { m_current_state = 0; } public void pull(char c, CeilingFanPullChain pContext) { if (m_current_state == 0) { m_current_state = 1;

//The CeilingFanPullChain class is now a wrapper that delegates to its current_state reference. Each clause from the "before" case statement is now captured in a State derived class. // For this simple domain, the State pattern is probably over-kill. class CeilingFanPullChain { private State m_current_state; public CeilingFanPullChain() { m_current_state = new Off(); }

1. Identify an existing class, or create a new class, that will serve as the "state machine" from the client's perspective. That class is the "wrapper" class.

2. Create a State base class that replicates the methods of the state machine interface. Each method takes one additional parameter: an instance of the wrapper class. The State base class specifies any useful "default" behavior.

3. Create a State derived class for each domain state. These derived classes only override the methods they need to override.

4. The wrapper class maintains a "current" State object. 5. All client requests to the wrapper class are simply delegated to the current State object, and the

wrapper object's this pointer is passed. 6. The State methods change the "current" state in the wrapper object as appropriate.

Page 47: Design+Patterns ESTI 2010 (1)

System.out.println( " low speed ",c); }else if (m_current_state == 1) { m_current_state = 2; System.out.println(" medium speed"); } else if (m_current_state == 2) { m_current_state = 3; System.out.println(" high speed" ); } else { m_current_state = 0; System.out.println(" turning off"); } } } public class StateDemo { public static void main ( String[] args ){ CeilingFanPullChain chain = new CeilingFanPullChain(); while (true) { System.out.print( "Press " ); Char c = get_line(); chain.pull(c); } } static String get_line() { BufferedReader in = new BufferedReader( new InputStreamReader( System.in )); String line = null; try { line = in.readLine(); } catch (IOException ex) { ex.printStackTrace(); } return line; } } // Press // low speed // Press // medium speed // Press // high speed // Press // turning off // Press // low speed // Press // medium speed // Press // high speed // Press // turning off

public void set_state( State s ) { m_current_state = s; } public void pull(char ev) { m_current_state.pull( ev, this ); } } interface State { void pull(char ev, CeilingFanPullChain wrapper); } class Off implements State { public void pull(char ev, CeilingFanPullChain wrapper ) { wrapper.set_state( new Low() ); System.out.println(" low speed",ev ); } } class Low implements State { public void pull(char ev, CeilingFanPullChain wrapper ) { wrapper.set_state( new Medium() ); System.out.println(" medium speed",ev); } } class Medium implements State { public void pull(char ev, CeilingFanPullChain wrapper ) { wrapper.set_state( new High() ); System.out.println( " high speed",ev ); } } class High implements State { public void pull(char ev, CeilingFanPullChain wrapper ) { wrapper.set_state(new Off() ); System.out.println(" turning off",ev); } } public class StateDemo { public static void main( String[] args ) { CeilingFanPullChain chain = new CeilingFanPullChain(); while (true) { System.out.print( "Press " ); Char c = get_line(); chain.pull(c); } } static String get_line()

Page 48: Design+Patterns ESTI 2010 (1)

{ BufferedReader in= new BufferedReader( new InputStreamReader(System.in); String line = null; try { line = in.readLine(); } catch (IOException ex) { ex.printStackTrace(); } return line; } }

Page 49: Design+Patterns ESTI 2010 (1)

5. Le ‘Chain Of Responsability ‘

• Le principe de ce pattern est de découpler celui qui envoie la requête et celui qui la tarite. Les entités de traitement auront la chance de se mettre en chaine pour que chacune soit responsable d’un bout de traitement sur la requête. Le data en transit entre les entités est le seul lien qui existe. Le flus de la requête passera d’un handler ason suivant un ordre séquentiel choisit par l’application dynamiquement.

• L’application peut même choisr plusieurs chaines différentes mettant en jeu les m^mes entités ou des entités différentes

Processing Element

Client Request

Processing Element

Processing Element

Processing Element

Client Request

Processing Element

Processing Element

Processing Element

Page 50: Design+Patterns ESTI 2010 (1)

import java.util.*; abstract class Logger { public static int ERR = 3; public static int NOTICE = 5; public static int DEBUG = 7; protected int mask; // The next element in the chain of responsibil ity protected Logger next; public Logger setNext( Logger l) { next = l; return l; } public void message( String msg, int priority ) { if ( priority <= mask ) { writeMessage( msg ); } if ( next != null ) { next.message( msg, priority ); } } abstract protected void writeMessage( String ms g ); } class StdoutLogger extends Logger { public StdoutLogger( int mask ) { this.mask = m ask; } protected void writeMessage( String msg ) { System.out.println( "Writing to stdout: " + msg ); } } class EmailLogger extends Logger { public EmailLogger( int mask ) { this.mask = ma sk; } protected void writeMessage( String msg ) { System.out.println( "Sending via email: " + msg ); } } class StderrLogger extends Logger { public StderrLogger( int mask ) { this.mask = m ask; } protected void writeMessage( String msg ) { System.err.println( "Sending to stderr: " + msg ); } }

Page 51: Design+Patterns ESTI 2010 (1)

public class ChainOfResponsibilityExample { public static void main ( String[] args ) { // Build the chain of responsibility Logger l,l1; l1 = l = new StdoutLogger( Logger.DEBUG ); l1 = l1.setNext(new EmailLogger( Logger.NOT ICE )); l1 = l1.setNext(new StderrLogger( Logger.ER R )); // Handled by StdoutLogger l.message( "Entering function y.", Logger.D EBUG ); // Handled by StdoutLogger and EmailLogger l.message( "Step1 completed.", Logger.NOTIC E ); // Handled by all three loggers l.message( "An error has occurred.", Logger .ERR ); } }

Page 52: Design+Patterns ESTI 2010 (1)

Before After

// Before - the client is responsible for stepping // through the "list" of Handler objects, and deter- // mining when the request has been handled. // class Handler { private static java.util.Random s_rn = new java.util.Random(); private static int s_next = 1; private int m_id = s_next++; public boolean handle( int num ) { if (s_rn.nextInt(4) != 0) { System.out.print( m_id + "-busy " ); return false; } System.out.println( m_id + "-handled-" + num ); return true; } } public class ChainDemo { public static void main( String[] args ) { Handler[] nodes = { new Handler(), new Handler(),new Handler(), new Handler() }; for (int i=1, j; i < 10; i++) { j = 0; while ( ! nodes[j].handle( i )) j = (j+1) % nodes.length; } } } // 1-busy 2-busy 3-busy 4-busy 1-busy 2-handled-1 // 1-busy 2-busy 3-handled-2 // 1-busy 2-busy 3-busy 4-handled-3 // 1-busy 2-busy 3-busy 4-busy 1-busy 2-busy 3-handled-4 // 1-busy 2-busy 3-handled-5 // 1-handled-6 // 1-busy 2-handled-7 // 1-busy 2-busy 3-busy 4-busy 1-busy 2-busy 3-busy 4-handled-8 // 1-busy 2-handled-9

// After - the client submits each request to the // "chain" abstraction and is decoupled from all sub-sequent processing. class Handler { private static java.util.Random s_rn = new java.util.Random(); private static int s_next = 1; private int m_id = s_next++; private Handler m_next; public void add( Handler next ) { if (m_next == null) m_next = next; else m_next.add( next ); } public void wrap_around( Handler root ) { if (m_next == null) m_next = root; else m_next.wrap_around( root ); } public void handle( int num ) { if (s_rn.nextInt(4) != 0) { System.out.print( m_id + "-busy " ); m_next.handle( num ); } else System.out.println( m_id + "-handled-" + num ); } } public class ChainDemo { public static void main( String[] args ) { Handler chain_root = new Handler(); chain_root.add( new Handler() ); chain_root.add( new Handler() ); chain_root.add( new Handler() ); chain_root.wrap_around( chain_root ); for (int i=1; i < 10; i++) chain_root.handle( i ); } } // 1-busy 2-busy 3-handled-1 // 1-busy 2-busy 3-busy 4-busy 1-handled-2 // 1-busy 2-busy 3-busy 4-busy 1-busy 2-busy // 3-busy 4-busy 1-handled-3 // 1-busy 2-handled-4 // 1-busy 2-busy 3-busy 4-handled-5 // 1-busy 2-busy 3-busy 4-busy 1-busy 2-handled-6 // 1-busy 2-handled-7 // 1-handled-8 // 1-busy 2-busy 3-handled-9

Page 53: Design+Patterns ESTI 2010 (1)

6. Le Visitor Le modèle ‘Visitor’ est utile dans la conception d’une opération à travers une collection hétérogène d'objets disposés en hiérarchie de classe. Le modèle de ‘Visitor’ permet à l'opération d’être défini sans changer la classe de n'importe quel des objets dans la collection. Pour accomplir ceci, le modèle de Visitor suggère la définition de l'opération dans une classe séparée référée comme une classe de visiteur. Ceci sépare l'opération de la collection d'objet sur laquelle il fonctionne. Pour chaque nouvelle opération à définir, une nouvelle classe de visiteur est créée. Puisque l'opération est exécutée à travers une série d'objets, le visiteur a besoin d'une façon d' accéder aux membres publics de ces objets. Visitor's primary purpose is to abstract functionality that can be applied to an aggregate hierarchy of "element" objects. The approach encourages designing lightweight Element classes - because processing functionality is removed from their list of responsibilities. New functionality can easily be added to the original inheritance hierarchy by creating a new Visitor subclass.

Visitor implements "double dispatch". OO messages routinely manifest "single dispatch" - the operation that is executed depends on: the name of the request, and the type of the receiver. In "double dispatch", the operation executed depends on: the name of the request, and the type of TWO receivers (the type of the Visitor and the type of the element it visits).

The implementation proceeds as follows. Create a Visitor class hierarchy that defines a pure virtual visit() method in the abstract base class for each concrete derived class in the aggregate node hierarchy. Each visit() method accepts a single argument - a pointer or reference to an original Element derived class.

Each operation to be supported is modelled with a concrete derived class of the Visitor hierarchy. The visit() methods declared in the Visitor base class are now defined in each derived subclass by allocating the "type query and cast" code in the original implementation to the appropriate overloaded visit() method.

Add a single pure virtual accept() method to the base class of the Element hierarchy. accept() is defined to receive a single argument - a pointer or reference to the abstract base class of the Visitor hierarchy.

Each concrete derived class of the Element hierarchy implements the accept() method by simply calling the visit() method on the concrete derived instance of the Visitor hierarchy that it was passed, passing its "this" pointer as the sole argument.

Everything for "elements" and "visitors" is now set-up. When the client needs an operation to be performed, he creates an instance of the Vistor object, calls the accept() method on each Element object, and passes the Visitor object. The accept() method causes flow of control to find the correct Element subclass. Then when the visit() method is invoked, flow of control is vectored to the correct Visitor subclass. accept() dispatch plus visit() dispatch equals double dispatch.

Page 54: Design+Patterns ESTI 2010 (1)

The Visitor pattern makes adding new operations (or utilities) easy - simply add a new Visitor derived class. But, if the subclasses in the aggregate node hierarchy are not stable, keeping the Visitor subclasses in sync requires a prohibitive amount of effort.

An acknowledged objection to the Visitor pattern is that is represents a regression to functional decomposition - separate the algorithms from the data structures. While this is a legitimate interpretation, perhaps a better perspective/rationale is the goal of promoting non-traditional behavior to full object status.

Client Visitor VisitConcreteElementA(ConcreteElementA) VisitConcreteElementB(ConcreteElementB)

Concrete Visitor 1 VisitConcreteElementA(ConcreteElementA) Visi tConcreteElementB (Con creteElementB )

Concrete Visitor 2 VisitConcreteElementA(ConcreteElementA) VisitConcreteElementB(ConcreteElementB)

ObjectStructure Element

Accept(Visitor )

ConcreteElementA Accept(Visitor V) OperationA()

v- >VisitConcreteElementA(this)

ConcreteElementB Accept(Visitor V) OperationB()

v- >VisitConcreteElementB(this)

Page 55: Design+Patterns ESTI 2010 (1)

Before After // The interface for "operations" are specified in the Color base class and implemented in the Color derived classes. done based on the type of both objects. class Color { public: virtual void count() = 0; virtual void call() = 0; static void report_num() { cout << "Reds " << s_num_red << ", Blus " << s_num_blu << '\n'; } protected: static int s_num_red, s_num_blu; }; int Color::s_num_red = 0; int Color::s_num_blu = 0; class Red : public Color { public: void count() { ++s_num_red; } void call() { eye(); } void eye() { cout << "Red::eye\n"; } };

//The Color hierarchy specifies a single "accept()" method, and then the previous "count()" and "call()" methods are implemented as Visitor derived classes. When accept() is called on a Color object, that is the first dispatch. When visit() is called on a Visitor object, that is the second dispatch; and the "right thing" can be class Color { public: virtual void accept( class Visitor* ) = 0; }; class Red : public Color { public: /*virtual*/ void accept( Visitor* ); void eye() { cout << "Red::eye\n"; } }; class Blue : public Color { public: /*virtual*/ void accept( Visitor* ); void sky() { cout << "Blu::sky\n"; } };

Page 56: Design+Patterns ESTI 2010 (1)

class Blu : public Color { public: void count() { ++s_num_blu; } void call() { sky(); } void sky() { cout << "Blu::sky\n"; } }; int main( void ) { Color* set[] = { new Red, new Blu, new Blu, new Red, new Red, 0 }; for (int i=0; set[i]; ++i) { set[i]->count(); set[i]->call(); } Color::report_num(); } // Red::eye // Blu::sky // Blu::sky // Red::eye // Red::eye // Reds 3, Blus 2

void Red::accept( Visitor* v ) { v->visit( this ); } void Blue::accept( Visitor* v ) { v->visit( this ); } class Visitor { public: virtual void visit( Red* ) = 0; virtual void visit( Blue* ) = 0; }; class CountVisitor : public Visitor { public: CountVisitor() { m_num_red = m_num_blu = 0; } /*virtual*/ void visit( Red* ) { ++m_num_red; } /*virtual*/ void visit( Blu* ) { ++m_num_blu; } void report_num() { cout << "Reds " << m_num_red << ", Blus " << m_num_blu << '\n'; } private: int m_num_red, m_num_blu; }; class CallVisitor : public Visitor { public: /*virtual*/ void visit( Red* r ) { r->eye(); } /*virtual*/ void visit( Blu* b ) { b->sky(); } }; int main( void ) { Color* set[] = {new Red, new Blu, new Blu, new Red, new Red, 0 }; CountVisitor count_operation; CallVisitor call_operation; for (int i=0; set[i]; i++) { set[i]->accept( &count_operation ); set[i]->accept( &call_operation ); } count_operation.report_num(); } // Red::eye // Blu::sky // Blu::sky // Red::eye // Red::eye // Reds 3, Blus 2

Page 57: Design+Patterns ESTI 2010 (1)

1. Confirm that the current hierarchy (known as the Element hierarchy) will be fairly stable and that the public interface of these classes is sufficient for the access the Visitor classes will require.

2. Create a Visitor base class with a visit(ElementXxx) method for each Element derived type.

3. Add an accept(Visitor) method to the Element hierarchy. The implementation in each Element derived class is always the same – accept(

Visitor v ) { v.visit( this ); } . Because of cyclic dependencies, the declaration of the Element and Visitor classes will need to be interleaved.

4. The Element hierarchy is coupled only to the Visitor base class, but the Visitor hierarchy is coupled to each Element derived class. If the stability of the Element hierarchy is low, and the stability of the Visitor hierarchy is high; consider swapping the “roles” of the two hierarchies.

5. Create a Visitor derived class for each "operation" to be performed on Element objects. visit() implementations will rely on the Element's public interface.

6. The client creates Visitor objects and passes each to Element objects by calling accept() .

Page 58: Design+Patterns ESTI 2010 (1)

7. Le Memento

Le patron memento est un patron qui fournit la manière de renvoyer un objet à un état précédent (retour arrière) sans violer le principe d'encapsulation.

Le mémento est utilisé par deux objets: le créateur (originator) et le gardien (care Taker). Le créateur est un objet ayant un état interne (état à sauvegarder). Le gardien agira sur le créateur, tout en conservant la possibilité de revenir en arrière. Le gardien demande alors au créateur l'objet mémento. Il effectue l'opération (ou séquence d'opérations) souhaitée. Afin de permettre le retour arrière avant les opérations, le mémento est retourné au créateur. L'objet mémento est opaque :le gardien ne peut, ou ne devrait pas, le modifier. Lors d'utilisation de ce patron, une attention toute particulière doit être prise de vérifier si le créateur modifie d'autres objets ou ressources - le patron mémento doit opérer sur un seul objet.

The Memento captures and externalizes an object's internal state so that the object can later be restored to that state. This pattern is common

among do-it-yourself mechanics restoring

Page 59: Design+Patterns ESTI 2010 (1)

Exemple Java

import java.util.*; class Originator { private String state; public void set(String state) { System.out.println("Originator: etat affecte a: "+state); this.state = state; } public Object saveToMemento() { System.out.println("Originator: sauvegarde dans le memento."); return new Memento(state); } public void restoreFromMemento(Object m) { if (m instanceof Memento) { Memento memento = (Memento)m; state = memento.getSavedState(); System.out.println("Originator: Etat après restauration: "+state); } } private static class Memento { private String state; public Memento(String stateToSave) { state = stateToSave; } public String getSavedState() { return state; } } }

class Caretaker { private ArrayList savedStates = new ArrayList(); public void addMemento(Object m) { savedStates.add(m); } public Object getMemento(int index) { return savedStates.get(index); } } class MementoExample { public static void main(String[] args) { Caretaker caretaker = new Caretaker(); Originator originator = new Originator(); originator.set("State1"); originator.set("State2"); caretaker.addMemento( originator.saveToMemento() ); originator.set("State3"); caretaker.addMemento( originator.saveToMemento() ); originator.set("State4"); originator.restoreFromMemento( caretaker.getMemento(1) );

Page 60: Design+Patterns ESTI 2010 (1)

Exemple C++

// "check point" and "roll back" a Stack class Memento { friend class Stack; int *m_items, m_num; Memento( int* arr, int num ) { m_items = new int[m_num = num]; for (int i=0; i < m_num; i++) m_items[i] = arr[i]; } public: ~Memento() { delete m_items; } }; class Stack { //originator int m_items[10], m_sp; public: Stack() { m_sp = -1; } void push( int in ) { m_items[++m_sp] = in; } int pop() { return m_items[m_sp--]; } bool is_empty() { return (m_sp == -1); } Memento* check_point() { return new Memento( m_items, m_sp+1 ); } void roll_back( Memento* mem ) { m_sp = mem->m_num-1; for (int i=0; i < mem->m_num; ++i) m_items[i] = mem->m_items[i]; } friend ostream& operator<< ( ostream& os, const Stack& s ) { string buf( "[ " ); for (int i=0; i < s.m_sp+1; i++) { buf += s.m_items[i]+48; buf += ' '; } buf += ']'; return os << buf; } }; int main( void ) { // 1. main() is the "caretaker" Stack s; for (int i=0; i < 5; i++) s.push( i ); cout << "stack is " << s << '\n'; Memento* first = s.check_point(); for (int i=5; i < 10; i++) s.push( i ); cout << "stack is " << s << '\n'; Memento* second = s.check_point(); cout << "popping stack: "; while ( ! s.is_empty()) cout << s.pop() << ' '; cout << '\n'; cout << "stack is " << s << '\n'; s.roll_back( second ); cout << "second is " << s << '\n'; s.roll_back( first ); cout << "first is " << s << '\n'; cout << "popping stack: "; while ( ! s.is_empty()) cout << s.pop() << ' '; cout << '\n'; delete first; delete second; } // stack is [ 0 1 2 3 4 ] // stack is [ 0 1 2 3 4 5 6 7 8 9 ] // popping stack: 9 8 7 6 5 4 3 2 1 0 // stack is [ ] // second is [ 0 1 2 3 4 5 6 7 8 9 ] // first is [ 0 1 2 3 4 ] // popping stack: 4 3 2 1 0

Page 61: Design+Patterns ESTI 2010 (1)

1. Identify the roles of “caretaker” and “originator”. 2. Create a Memento class and declare the originator a friend. 3. Caretaker knows when to "check point" the originator. 4. Originator creates a Memento and copies its state to that Memento. 5. Caretaker holds on to (but cannot peek into) the Memento. 6. Caretaker knows when to "roll back" the originator . 7. Originator reinstates itself using the saved state in the Memento

Command and Memento act as magic tokens to be passed around and invoked at a later time. In Command, the token represents a request; in Memento, it represents the internal state of an object at a particular time. Polymorphism is important to Command, but not to Memento because its interface is so narrow that a memento can only be passed as a value.

[GoF, p346]

Page 62: Design+Patterns ESTI 2010 (1)

A N N E X E

Page 63: Design+Patterns ESTI 2010 (1)

Gestion d’achat dans une structure hiérarchique

Table 21.1 Levels of PR Authorization Management Level Authorization Limit Branch Manager $25,000 Regional Director $100,000 Vice President $200,000 President and COO $400,000 class BranchManager {

static double LIMIT = 25000; … …

}//End of class class RegionalDirector {

static double LIMIT = 100000; … …

}//End of class class VicePresident { static double LIMIT = 200000; … …

}//End of class class PresidentCOO { static double LIMIT = 400000; … …

}//End of class class PurchaseRequest {

private int ID; private String description; private double amount; public PurchaseRequest (int id, String desc, double amt) {

ID = id; description = desc; amount = amt;

} public double getAmount () {

return amount; } public String toString () {

return ID + ":" + description; }

}

PurchaseRequest

ID:int description:String amount:double

PurchaseRequest

ID:int description:String amount:double getAmount():double

Page 64: Design+Patterns ESTI 2010 (1)

public abstract class PRHandler { private PRHandler nextHandler; private String handlerName; public PRHandler (String name) {

handlerName = name; } public String getName () {

return handlerName; } public abstract boolean authorize (PurchaseRequest request); public PRHandler getNextHandler () {

return nextHandler; } public void setNextHandler (PRHandler handler) {

nextHandler = handler; };

}

Page 65: Design+Patterns ESTI 2010 (1)

class BranchManager extends PRHandler { static double LIMIT = 25000; public BranchManager(String name) {

super(name); } public boolean authorize(PurchaseRequest request) {

double amount = request.getAmount(); if (amount <= LIMIT) {

System.out.println(" Branch Manager " + getName() + " has authorized the PR - " + request); return true;

} else { //forward the request to the next handler

return getNextHandler().authorize(request); }

} }//End of class class RegionalDirector extends PRHandler {

static double LIMIT = 100000; public RegionalDirector(String name) {

super(name); } public boolean authorize(PurchaseRequest request) {

double amount = request.getAmount(); if (amount <= LIMIT) {

System.out.println(" Regional Director " + getName( ) + " has authorized the PR - " +request);

return true; } else { //forward the request to the next handler

return getNextHandler().authorize(request); }

} }//End of class class VicePresident extends PRHandler {

static double LIMIT = 200000; public VicePresident(String name) {

super(name); } public boolean authorize(PurchaseRequest request) {

double amount = request.getAmount(); if (amount <= LIMIT) {

System.out.println(" V.P. " + getName() + " has authorized the PR - " + request);

return true; } else { //forward the request to the next handler

return getNextHandler().authorize(request); }

} }//End of class class PresidentCOO extends PRHandler {

static double LIMIT = 400000; public PresidentCOO(String name) {

super(name); } public boolean authorize(PurchaseRequest request) {

double amount = request.getAmount(); if (amount <= LIMIT) {

System.out.println(" President & COO " + getName() + " has authorized the PR - " + request); return true;

} else { System.out.println("PR - " + request +" couldn't be

authorized.\n " + "Executive Board needs to be " + "consulted for approval \n" + "reason: Amount too large");

return false; }

Page 66: Design+Patterns ESTI 2010 (1)

} }//End of class public class PRManager {

private BranchManager branchManager; private RegionalDirector regionalDirector; private VicePresident vicePresident; private PresidentCOO coo;

public static void main (String[] args) {

PRManager manager = new PRManager(); manager. createAuthorizationFlow (); PurchaseRequest request = new PurchaseRequest( 1, "Office

Supplies”,10000); manager.branchManager.authorize(request); request = new PurchaseRequest( 2, "HardWare Procurement”,175000); manager.branchManager.authorize(request); request = new PurchaseRequest( 3, "AD Campaign”,800000); manager.branchManager.authorize(request);

} public void createAuthorizationFlow () {

branchManager = new BranchManager("Robin"); regionalDirector = new RegionalDirector("Oscar"); vicePresident = new VicePresident("Kate"); coo = new PresidentCOO("Drew");

}

}//End of class

branchManager.setNextHandler(regionalDirector); regionalDirector.setNextHandler(vicePresident); vicePresident.setNextHandler(coo);

Page 67: Design+Patterns ESTI 2010 (1)

Output: Branch Manager Robin has authorized the PR - 1:Office Supplies V.P. Kate has authorized the PR - 2:HardWare Procurement PR - 3:AD Campaign couldn't be authorized. Executive Board needs to be consulted for approval reason: Amount too large

Page 68: Design+Patterns ESTI 2010 (1)

Study case

File system with 7 GoF patterns

// Composite --------------- directories that conta in files, each of which could // be a directory // Proxy ------------------- Unix logical link // Chain of Responsibility - pointer back to the pa rent directory to create // "fully qualified path name" // Iterator ---------------- use iterator to contro l the traversal of a Composite // Visitor ----------------- create a recursive Uni x "du" utility without // modifying the original Composite hierarchy // Observer ---------------- register one-to-many " listeners" for File write() // events // Command ----------------- register one-to-one ca llback object for File write() // events #include <iostream> #include <vector> #include <string> #include <sstream> #include <ctime> using namespace std; class AbsFile { string name; class Directory* parent; protected: Directory* getParent() { return parent; } public: AbsFile( string n, Directory* p ) : name( n ) { parent = p; } virtual ~AbsFile() { } string getName() { return name; } virtual string getFullName(); virtual void ls( string = "" ) = 0; virtual void populate( vector<AbsFile*>& list ) { list.push_back( this ); } virtual void streamOut( ostream& = cout ) = 0; virtual void accept( class Visitor& ) = 0; }; class Observer { public: virtual void fileChanged( class File* ) = 0; }; class Command { public: virtual void execute ( File* ) = 0; }; class NullCommand : public Command { public: static Command& inst() { static NullCommand nc; return nc; } /*virtual*/ void execute (File*) { } }; class File : public AbsFile { stringstream contents; static vector<Observer*> observers; Command& cmd;

Page 69: Design+Patterns ESTI 2010 (1)

public: File( string n, Directory* p, Command& c = NullC ommand::inst() ) : AbsFile( n,p ), cmd( c ) { } ~File() { cout << 'X' << getName() << ' '; } /*virtual*/ void ls( string s = "" ) { if (s == "") cout << getName() << ' '; else cout << getFullName() << '\n'; } void write( string str ) { contents << str; notify(); cmd.execute( this ); } /*virtual*/ void streamOut( ostream& os = cout ) { os << contents.str() << '\n'; } int getSize() { return contents.str().size(); } /*virtual*/ void accept ( Visitor& v ); static void registerObserver( class Observer* o ) { observers.push_back( o ); } void notify() { for (int i=0; i < observers.size(); i++) observers[i]->fileChanged( this ); } }; vector<Observer*> File::observers; class Directory : public AbsFile { vector<AbsFile*> children; string name; public: Directory( string n, Directory* p=0 ) : AbsFile( n,p ) { } ~Directory() { cout << 'X' << getName() << ' '; for (int i=0; i < children.size(); i++) delete children[i]; } void add( AbsFile* ele ) { children.push_back( e le ); } /*virtual*/ void ls( string s = "" ) { if (s == "") cout << getName() << "/ "; else cout << getFullName() << '\n'; for (int i=0; i < children.size(); i++) children[i]->ls( s ); } /*virtual*/ string getFullName() { return AbsFil e::getFullName() + '/'; } class Iterator* createIterator(); /*virtual*/ void populate( vector<AbsFile*>& lis t ) { AbsFile::populate( list ); for (int i=0; i < children.size(); i++) children[i]->populate( list ); } /*virtual*/ void streamOut( ostream& os = cout ) { os << "directory - " << getName() << '\n'; } /*virtual*/ void accept ( Visitor& v ); }; /*virtual*/ string AbsFile::getFullName() { string str; Directory* p; if (p = getParent()) str = p->getFullName(); return str + getName();

Page 70: Design+Patterns ESTI 2010 (1)

} class Link : public AbsFile { AbsFile* wrapped; public: Link( string n, AbsFile* w, Directory* p ) : Abs File(n,p) { wrapped = w; } ~Link() { cout << 'X' << getName() << ' '; } /*virtual*/ void ls( string s = "" ) { if (s == "") { cout << getName() << "* "; wrapped->ls(); } else { cout << getFullName() << " => "; cout << wrapped->getFullName() << '\n'; } } /*virtual*/ string getFullName() { return AbsFil e::getFullName() + '*'; } /*virtual*/ void streamOut( ostream& os = cout ) { os << "link - " << getName() << '\n'; } /*virtual*/ void accept ( Visitor& v ); }; class Iterator { Directory* container; vector<AbsFile*> list; int index; public: Iterator( Directory* c ) { container = c; } void first() { list.resize( 0 ); container->populate( list ); index = 0; } void next () { index++; } bool isDone () { return index == list.size(); } AbsFile* currentItem () { return list[index]; } }; Iterator* Directory::createIterator() { return new Iterator( this ); } class Visitor { public: virtual void visit( File* ) = 0; virtual void visit( Directory* ) = 0; virtual void visit( Link* ) = 0; }; /*virtual*/ void File::accept( Visitor& v ) { v.visit( this ); } /*virtual*/ void Directory::accept( Visitor& v ) { v.visit( this ); } /*virtual*/ void Link::accept( Visitor& v ) { v.visit( this ); } class DU : public Visitor { int numDir, numLink, numFile, sizeFile; public: DU() { numDir = numLink = numFile = sizeFile = 0 ; } /*virtual*/ void visit( class File* f ) { num File++; siz eFile += f->getSize(); } /*virtual*/ void visit( class Directory* ) { num Dir++; } /*virtual*/ void visit( class Link* ) { num Link++; } void report() { cout << "du: directories-" << numDir << ", li nks-" << numLink; cout << ", files-" << numFile << ", size-" << sizeFile << '\n';

Page 71: Design+Patterns ESTI 2010 (1)

} }; class NameObserver : public Observer { public: /*virtual*/ void fileChanged( File* f ) { time_t t; time( &t ); string str( asctime(localtime(&t)) ); str.resize( str.size()-1 ); cout << f->getName() << " changed - " << str << '\n'; } }; class ContentObserver : public Observer { public: /*virtual*/ void fileChanged( File* f ) { stringstream ss; f->streamOut( ss ); cout << f->getName() << " content - " << ss.s tr(); } }; class ContentCommand : public Command { public: static Command& inst() { static ContentCommand c c; return cc; } /*virtual*/ void execute ( File* f ) { stringstream ss; f->streamOut( ss ); cout << "ContentCommand - " << f->getName() < < " - " << ss.str(); } }; Directory* initialize() { Directory* root = new Directory( "root" ); root->add( new File( "core", root ) ); Directory* usr = new Directory( "usr", root ); root->add( usr ); File* adm = new File( "adm", usr ); usr->add( adm ); Directory* local = new Directory( "local", usr ) ; usr->add( local ); local->add( new File( "bin", local ) ); usr->add( new File( "var", usr ) ); Directory* home = new Directory( "home", root ); root->add( home ); home->add( new Link( "alias", adm, home ) ); File* sam = new File( "sam", home, ContentComman d::inst() ); home->add( sam ); home->add( new File( "sue", home ) ); root->add( new Link( "aka", local, root ) ); root->add( new File( "end", root ) ); return root; } void main( void ) { ////////// Composite, Proxy ////////// Directory* root = initialize(); root->ls(); cout << '\n'; ////////// Chain ////////// root->ls( "-l" ); cout << '\n'; ////////// Iterator ////////// Iterator* it = root->createIterator();

Page 72: Design+Patterns ESTI 2010 (1)

for (it->first(); ! it->isDone(); it->next()) cout << it->currentItem()->getName() << ' '; delete it; cout << "\n\n"; it = root->createIterator(); for (it->first(); ! it->isDone(); it->next()) cout << it->currentItem()->getFullName() << ' \n'; delete it; cout << '\n'; ////////// Observer ////////// File::registerObserver ( new NameObserver() ); File::registerObserver ( new ContentObserver() ); ////////// Observer ////////// it = root->createIterator(); for (it->first(); ! it->isDone(); it->next()) if (it->currentItem()->getName() == "adm") break; File* f = dynamic_cast<File*>( it->currentItem() ); f->write( "adm line 1\nadm line 2" ); f->streamOut(); cout << '\n'; ////////// Observer, Command ////////// for ( ; ! it->isDone(); it->next()) if (it->currentItem()->getName() == "sam") break; f = dynamic_cast<File*>( it->currentItem() ); f->write( "sam single line" ); f->streamOut(); delete it; cout << '\n'; ////////// Visitor ////////// DU duObject; it = root->createIterator(); for (it->first(); ! it->isDone(); it->next()) it->currentItem()->accept( duObject ); duObject.report(); delete it; cout << '\n'; delete root; cout << '\n'; } // root/ core usr/ adm local/ bin var home/ alias* adm sam sue aka* local/ bin end // // root/ // root/core // root/usr/ // root/usr/adm // root/usr/local/ // root/usr/local/bin // root/usr/var // root/home/ // root/home/alias* => root/usr/adm // root/home/sam // root/home/sue // root/aka* => root/usr/local/ // root/end // // root core usr adm local bin var home alias sam s ue aka end // // root/

Page 73: Design+Patterns ESTI 2010 (1)

// root/core // root/usr/ // root/usr/adm // root/usr/local/ // root/usr/local/bin // root/usr/var // root/home/ // root/home/alias* // root/home/sam // root/home/sue // root/aka* // root/end // // adm changed - Thu Dec 28 11:20:26 2000 // adm content - adm line 1 // adm line 2 // adm line 1 // adm line 2 // // sam changed - Thu Dec 28 11:20:26 2000 // sam content - sam single line // ContentCommand - sam - sam single line // sam single line // // du: directories-4, links-2, files-7, size-36 // // Xroot Xcore Xusr Xadm Xlocal Xbin Xvar Xhome Xal ias Xsam Xsue Xaka Xend