Principes OO, patrons de conception
Transcription
Principes OO, patrons de conception
IFT3912 - Développement et maintenance de logiciels Principes OO Conception orientée-objet Bruno Dufour [email protected] Abstraction • L’abstraction permet de gérer la complexité • L’abstraction permet de • se concentrer sur la vue externe d’un objet • séparer le comportement d’un objet de son implémentation • Tony Hoare: “Abstraction arises from a recognition of similarities between certain objects, situations, or processes in the real world, and the decision to concentrate upon those similarities and to ignore for the time being the differences.” Bruno Dufour - Université de Montréal 3 Encapsulation • Séparation de l’interface contractuelle d’une abstraction de son implémentation • Permet de cacher la structure interne et les détails d’implémentation d’un objet • Les classes devraient être opaques et ne pas exposer leurs détails d’implémentation internes Bruno Dufour - Université de Montréal 4 Encapsulation en Java 5 public class Vehicle { public double speed; } Encapsulation en Java 6 public class Vehicle { private double speed; public double getSpeed() { return speed; } public class Vehicle { private double speed; public void setSpeed(double newSpeed) { if (newSpeed < 0) { throw new IllegalArgumentException(“...”); } speed = newSpeed; } public double getSpeed() { return speed; } public void setSpeed(double newSpeed) { speed = newSpeed; } } } Source: Bob Tarr Source: Bob Tarr Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal Encapsulation en Java 7 Encapsulation en Java public class Vehicle { private double speed; public class Vehicle { private double speedInKPH; public double getSpeed() { return speed; } public double getSpeed() { return KPHtoMPH(speedInKPH); } public void setSpeed(double newSpeed) { if (newSpeed < 0) { throw new IllegalArgumentException(“...”); } speed = newSpeed; notifySpeedChanged(); } public void setSpeed(double newSpeedInMPH) { if (newSpeedInMPH < 0) { throw new IllegalArgumentException(“...”); } speedInKPH = MPHtoKPH(newSpeedInMPH); } } } Source: Bob Tarr Bruno Dufour - Université de Montréal 8 Source: Bob Tarr Bruno Dufour - Université de Montréal Réutilisation • 9 Exemple - Par héritage 10 public class InstrumentedHashSet extends HashSet { // The number of attempted element insertions private int addCount = 0; Deux stratégies • Par héritage : la nouvelle fonctionnalité est ajoutée public boolean add(Object o) { addCount++; return super.add(o); } par extension de l’implémentation d’un objet existant • Par composition / délégation : la nouvelle fonctionnalité est ajoutée en créant un nouvel objet qui est composé d’objets existants, qui agissent comme délégués public boolean addAll(Collection c) { addCount += c.size(); return super.addAll(c); } } Bruno Dufour - Université de Montréal Exemple - Par héritage public void testAdd() { InstrumentedHashSet s = new InstrumentedHashSet(); s.add("Snap"); s.add("Crackle"); s.add("Pop"); assert s.getAddCount() == 3; } public void testAddAll() { InstrumentedHashSet s = new InstrumentedHashSet(); s.addAll(Arrays.asList(new String[] { L’implémentation "Snap", de addAll fait "Crackle", appel à add "Pop"})); assert s.getAddCount() == 3; // 6! } Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 11 Réutilisation par héritage • Avantages • Facile, la plupart du code est hérité • Possibilité de modifier le comportement du code réutilisé (overriding) • Inconvénients • Les détails internes de la classe parente sont importants et souvent visibles à ses enfants • Un changement à la classe parente peut nécessiter des changements à ses enfants • Réutilisation rigide - ne peut être modifiée au cours de l’exécution Bruno Dufour - Université de Montréal 12 Exemple - Par héritage 13 public class InstrumentedHashSet implements Set { private final Set delegate; private int addCount = 0; • Les détails de l’implémentation des objets utilisés sont invisibles • Peut être modifiée au cours de l’exécution • Inconvénients public boolean add(Object o) { addCount++; return delegate.add(o); } • Plus difficile, toutes les méthodes d’une interface devront être implémentées (génération automatique possible) • Les interfaces doivent être définies soigneusement • Peut produire des systèmes qui contiennent plus public boolean addAll(Collection c) { addCount += c.size(); return delegate.addAll(c); } d’objets } Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 15 Exemple - 2 responsibilités 16 Une classe ne devrait avoir qu’une seule raison d’être modifiée. • Responsabilité = raison de changer • Si une classe possède plusieurs responsabilités, celles-ci deviennent couplées • plus de difficulté à assurer toutes les responsabilités suite à des changements • fragilité lors de changements peut causer des effets de bord inattendus Source: Robert C. Martin Bruno Dufour - Université de Montréal 14 • Avantages public InstrumentedHashSet(Set s) { delegate = s; } Single Responsibility Principle (SRP) Réutilisation par composition Bruno Dufour - Université de Montréal Exemple - Responsabilités séparées 17 Qu’est-ce qu’une responsabilité ? 18 • Définir une responsabilité peut être difficile • Nos habitudes nous poussent à grouper plusieurs responsabilités : public interface Modem { public void dial(String phoneNo); public void hangup(); public void send(byte[] data); public byte[] receive(); } Source: Robert C. Martin Gestion des communications Échange des donnés Source: Robert C. Martin Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal Séparation des responsabilités Connection DataChannel + dial(String phoneNo) + hangup() + send(byte[] data) + receive() 19 Causes des changements • Devrait-on séparer les deux responsabilités de Modem ? • Dépend de la façon dont l’application peut changer • La connection peut changer indépendamment ? • Conception trop rigide • Les deux responsabilités ne changent pas de façon indépendante ? Modem • Éviter la complexité inutile Source: Robert C. Martin Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 20 Exemple #2 - Persistence 21 Open-Closed Principle (OCP) 22 Une classe devrait être ouverte pour les extensions, mais fermée pour les modifications. Change fréquemment Logique d’affaires Persistence • Une classes devrait être conçue pour ne pas changer • Les changements devraient être effectués par ajout de nouveau code plutôt que par modification de code existant • Ouvert pour les extensions • Le comportement peut être étendu Change rarement • Fermé pour les modifications • Le code source ne doit pas être modifié Source: Robert C. Martin Bruno Dufour - Université de Montréal OCP et abstraction Bruno Dufour - Université de Montréal 23 Exemple - Shape 24 struct Square { ... } struct Circle { ... } • Comment modifier une classe sans changer son code source ? • En utilisant des abstractions • Les classes d’un système manipulent des abstractions • Elles sont fermées pour modification si elles dépendent d’abstractions fixes • Elles sont ouvertes pour extension par ajout de nouvelles classes dérivées de ces abstractions void drawAllShapes(Shape* list[], int n) { int i; for (i=0; i<n; i++) { struct Shape* s = list[i]; switch (s->type) { case square: drawSquare((struct Square*)s); break; case circle: drawCircle((struct Circle*)s); break; } } } Doit être modifié si on ajoute d’autres types de formes Source: Robert C. Martin Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal Exemple - Shape 25 abstract class Shape { public abstract void draw(); } Fermeture stratégique 26 • Un programme ne peut être 100% fermé au modifications • ex: on veut modifier drawAllShapes pour afficher class Square extends Shape { public void draw() { ... } } les formes dans un certain ordre • Il existe toujours certains changements qui class Circle extends Shape { public void draw() { ... } } nécessitent la modification du code source d’une classe • La classe devrait être fermée pour les changements void drawAllShapes(Collection<Shape> shapes) { for (Shape s: shapes) Conforme au OCP s.draw(); } les plus probables, et non pas tous les changements • Fermeture stratégique Source: Bob Tarr Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal Exemple - Fermeture stratégique abstract class Shape implements Comparable<Shape> { public abstract void draw(); public int compareTo(Shape s) { ... } } class Square extends Shape {...} class Circle extends Shape {...} 27 Conventions qui dérivent du OCP • Utiliser des attributes privés • Permet de fermer les méthodes qui dépendent de ces attributs (encapsulation) • Éviter les variables globales • Un module qui dépend d’une variable globale ne peut pas est fermé contre les modifications dans d’autres modules qui peuvent écrire dans cette variable. void drawAllShapes(List<Shape> shapes) { for (Shape s: Collections.sort(shapes)) s.draw(); } • Éviter les tests de types (instanceof) drawAllShapes est fermée pour les ajouts de nouvelles formes et pour les changements de l’ordre des formes. • Fragile en présence d’ajout de type Source: Bob Tarr Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 28 Liskov Substitution Principle (LSP) 29 Une classe devrait toujours pouvoir être substituée pour une classe parente. Exemple - Rectangle 30 public class Rectangle { private double width; private double height; public Rectangle(double w, double h) { width = w; height = h; } • Une classe qui viole ce principe viole aussi OCP • elle doit connaître certains ou tous les types dérivés d’une classe parente public public public public public • ex: drawAllShapes fonctionne pour toutes les formes double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) { width = w; } void setHeight(double h) { height = h; } double area() { return (width * height); } } Source: Bob Tarr Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal Exemple - Square 31 Rectangle, Shape et LSP 32 public class Square extends Rectangle { public Square(double s) { super(s, s); } public class TestRectangle { public static void testLSP(Rectangle r) { r.setWidth(4.0); r.setHeight(5.0); assert r.area() == 20.0; } } public void setWidth(double w) { super.setWidth(w); super.setHeight(w); } public void setHeight(double h) { super.setHeight(h); super.setWidth(h); } } Source: Bob Tarr Bruno Dufour - Université de Montréal Source: Bob Tarr Bruno Dufour - Université de Montréal Quel est le problème ? 33 plus de contraintes que sa classe parente • Si une sous-classe a des contraintes plus fortes • Un carré est bien aussi un rectangle que sa classe parente, certains cas seront valide pour le parent mais pas pour l’enfant (géométriquement) • Les attentes du concepteur sont raisonnable • Une classe enfant peut par contre relaxer certaines • Il faut considérer les attentes des utilisateurs de ces contraintes de sa classe parente sans violer le LSP classes • Plus formellement, une sous-classe peut • Le comportement d’un rectangle est différent de • affaiblir des préconditions • renforcer des postconditions celui d’un carré Bruno Dufour - Université de Montréal Les clients d’une classe ne devraient pas être forcés de dépendre d’interfaces qu’ils n’utilisent pas. • Chaque interface représente un seul comportement cohésif • Une seule responsabilité par interface • Séparer les interfaces lourdes (fat) qui ont trop de responsabilités en plusieurs interfaces distinctes Bruno Dufour - Université de Montréal 34 • Pour respecter le LSP, une sous-classe ne peut avoir • Les classes Rectangle et Square semblent valides Interface Segregation Principle (ISP) LSP et contraintes Bruno Dufour - Université de Montréal 35 Exemple - ISP public interface Bird { public void eat(); public void chirp(); public void walk(); public void fly(); } public class Ostrich implements Bird { ... public void fly() { throw new UnsupportedOperationException( “An ostrich doesn’t fly”); } } Bruno Dufour - Université de Montréal 36 ISP et confidentialité 37 B - Les abstractions ne doivent pas dépendre des détails. Les détails ne doivent pas dépendre des abstractions. • Sans ce principe, des changements à un module de bas niveau peuvent se propager à un module de haut niveau. Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 39 Exemple 40 Abstractions Propagation des changements Bruno Dufour - Université de Montréal 38 A - Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux devraient dépendre d’abstractions. public class Contact { public String getName { ... } public String getAddress { ... } public String getEmailAddress { ... } public String getTelephone { ... } } public class Emailer { public void SendMessage(Contact contact, String subject, String body) { // Cette méthode a accès à plus d’informations // que nécessaire pour effectuer son travail! } } Exemple Dependency Inversion Principle (DIP) Bruno Dufour - Université de Montréal Exemple - Button ---------- lamp.h ---------class Lamp { public: void TurnOn(); void TurnOff(); }; ---------- button.h ---------class Lamp; class Button { public: Button(Lamp& l) : itsLamp(&l) {} void onToggle(); private: Lamp* itsLamp; }; 41 Fait abstraction du geste posé par l’utilisateur ---------- button.cc ---------#include “button.h” #include “lamp.h” void Button::onToggle() { bool buttonOn = GetState(); if (buttonOn) itsLamp->TurnOn(); else itsLamp->TurnOff(); } Bruno Dufour - Université de Montréal 5 principes de la conception OO • • • • • SRP - Single Responsibility Principle OCP - Open-Closed Principle LSP - Liskov Substitution Principle ISP - Interface Segregation Principle DIP - Dependency Inversion Principle Bruno Dufour - Université de Montréal Exemple - Button 42 Fait abstraction de l’objet cible Bruno Dufour - Université de Montréal 43 Patrons de conception Patrons de conception 45 Patrons et abstraction 46 • Un patron décrit (et nomme) une solution à un problème commun Framework • Offre une solution abstraite pour faciliter la réutilisation Plus abstrait ... • Est formulé en termes de classes et d’objets • Peut être implémenté différemment en fonction du Composant ... langage de programmation utilisé Patron de conception • La solution proposée par un patron peut être modifiée ... ou adaptée selon les besoins du logiciel Classe réutilisable (ex: HashMap) • « Forcer » l’utilisation d’un patron dans un logiciel est une mauvaise pratique de développement Bruno Dufour - Université de Montréal Objectifs des patrons Bruno Dufour - Université de Montréal 47 Types de patrons • Les patrons visent en général à accroître la qualité du code en visant un ou plusieurs des objectifs suivant: • Flexibilité accrue • Meilleure performance • Fiabilité accrue • Attention! L’utilisation des patrons peut aussi augmenter la complexité du code • Par exemple, ajout d’indirections • Il faut donc juger des avantages et inconvénients • Créationnels: font l’abstraction du processus d’instanciation afin de rendre un système indépendant de la façon dont ses objets sont créés et représentés • Structuraux: se concentrent sur la façon dont les classes et les objets sont composés pour obtenir de plus grandes structures • Comportementaux: décrivent les modèles de communication et interaction entre les objets de l’ajout de patrons dans la conception d’un logiciel Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 48 Spécification des patrons 49 • • • • • • • résoudre • Applicabilité: situations pour lesquelles le patron peut être utilisé Structure: représentation graphique (ex: UML) Participants: rôles joués par les objets Collaborations: interactions entre les objets Conséquences: avantages / inconvénients du patron Bruno Dufour - Université de Montréal Exemple - Itérateur Intention: Motivation: Applicabilité: Structure: Participants: Collaborations: Conséquences: Bruno Dufour - Université de Montréal 51 Exemple - Itérateur public interface Iterator { public Object next(); public boolean hasNext(); } public interface Iterator { public Object next(); public boolean hasNext(); } • Motivation: • Une collection telle qu’une liste devrait permettre • Intention: permettre un accès séquentiel aux éléments d’une collection sans en exposer la représentation interne de traverser ses éléments tout en respectant les principes d’encapsulation • Une liste devrait supporter différentes méthodes de traversée • Une liste devrait supporter plusieurs traversées concurrentes • On ne veut pas “polluer” l’interface de la liste Bruno Dufour - Université de Montréal 50 public interface Iterator { public Object next(); public boolean hasNext(); } • Nom: court et descriptif • Intention: ce que le patron fait • Motivation: le(s) problème(s) que le patron permet de • • • • Exemple - Itérateur Bruno Dufour - Université de Montréal 52 Exemple - Itérateur 53 Exemple - Itérateur public interface Iterator { public Object next(); public boolean hasNext(); } 54 public interface Iterator { public Object next(); public boolean hasNext(); } • Structure: • Applicabilité: • Supporter l’accès aux éléments d’une collection sans exposer sa représentation interne • Supporter plusieurs traversées à la fois • Fournir une interface uniforme pour traverser différents types de structures Bruno Dufour - Université de Montréal Exemple - Itérateur Bruno Dufour - Université de Montréal 55 Exemple - Itérateur polymorphique public interface Iterator { public Object next(); public boolean hasNext(); } • Participants: public interface Iterator { public Object next(); public boolean hasNext(); } • Structure: • Aggregate : contient les éléments, permet de créer des itérateurs (factory, à venir) • Iterator : définit une interface pour accéder aux éléments • ConcreteAggregate, ConcreteIterator : implémentent leurs interfaces respectives Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 56 Exemple - FilteredIterator Exemple - FilteredIterator 57 public class FilteredIterator<E> implements Iterator<E> { private final Iterator<E> delegate; private final Filter<? super E> filter; public class private private private private ... private E buffer = null; // always points to the next element to return private boolean bufferHasElement = false; public FilteredIterator(Iterator<E> delegate, Filter<? super E> filter) { this.delegate = delegate; this.filter = filter; this.advance(); } private void advance() { while (this.delegate.hasNext()) { if (this.filter.accepts(this.buffer)) { this.buffer = this.delegate.next(); this.bufferHasElement = true; return; } } this.buffer = null; this.bufferHasElement = false; } 58 FilteredIterator<E> implements Iterator<E> { final Iterator<E> delegate; final Filter<? super E> filter; E buffer = null; // always points to the next element to return boolean bufferHasElement = false; public boolean hasNext() { return this.bufferHasElement; } } public E next() { if (!this.bufferHasElement) { throw new NoSuchElementException(); } E next = this.buffer; this.advance(); return next; } ... Bruno Dufour - Université de Montréal Autres langages • Les patrons sont indépendants du langage de programmation Python: class MyListIter: def __init__(self, seq): self.seq = seq self.pos = 0 Bruno Dufour - Université de Montréal 59 Les patrons peuvent être adaptés • Un patron peut être adapté à une situation particulière public interface Iterator { public Object next(); public boolean hasNext(); public void remove(); } def __iter__(self): return self def next(self): if self.pos < len(self.lst): v = self.seq[self.pos] self.pos += 1 return v else: raise StopIteration() Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 60 Motivation 62 • Les langages de programmation OO permettent tous de créer des objets Patrons créationnels • Ce mécanisme est habituellement rigide • ex : new ArrayList() • ArrayList est le nom d’une classe connue durant la compilation Bruno Dufour - Université de Montréal Patrons créationnels 63 Classes vs objets • Les patrons créationnels visent à abstraire le processus de création d’objets • Rendent le mécanisme de création des objets plus flexible • En pratique : remplacer new par une méthode qui effectue la création d’objet • peut créer différents types d’objets au cours de l’exécution, ou contrôler la création des objets • 2 types de patrons créationnels • Par classe : utilisent l’héritage afin de déterminer le type de l’objet à instancier • Par objet : utilisent la délégation afin de déterminer type de l’objet à instancier • peut être étendue pour modifier son comportement (OCP) Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 64 Singleton (rappel) 66 • Intention : Il est souvent important pour une classe de n’avoir qu’une instance • Motivation : • Parfois, nous ne voulons qu’une seule instance Singleton d’une classe dans le système • ex: Collections.emptyList(), un seul SwingWidgetFactory, etc. • souvent limité à des classes sans état mutable • Cette instance doit être facilement accessible • On veut limiter le nombre d’instances qui peuvent être crées Bruno Dufour - Université de Montréal Singleton - Structure 67 Singleton - Implémentation • Assurer une instance unique en cachant le mécanisme de création • Java : constructeur privé • Garder une référence pour l’instance unique • Java : attribut statique privé • Créer un point d’accès publique • Java : une méthode qui retourne l’instance unique Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 68 Singleton - Implémentation 69 Singleton - C++ 70 class Singleton { public: static Singleton* Instance() { static Singleton myInstance; return &myInstance; } protected: Singleton() { ... } public class LazySingleton { private static LazySingleton instance; private LazySingleton() {} public static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } ~Singleton(){ ... } } } Bruno Dufour - Université de Montréal Singleton - JavaScript var Singleton = (function(){ function Singleton() { // do stuff } var instance; return { getInstance: function(){ if (!instance) { instance = new Singleton(); } return instance; } }; })(); Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 71 Singleton - Variations public class RoundRobinSingleton { private static RoundRobinSingleton[] instances = new RoundRobinSingleton[10]; private static int next = 0; } public static RoundRobinSingleton getInstance() { RoundRobinSingleton instance = instances[next]; if (instance == null) { instance = new RoundRobinSingleton(); instances[next] = instance; } next = (next + 1) % instances.length; return instance; } Bruno Dufour - Université de Montréal 72 Singleton - Variations 73 public class Symbol { private static Map<String,Symbol> symbols = new HashMap<>(); private String key; public Symbol(String key) { this.key = key; } } public static Symbol getSymbol(String key) { Symbol instance = symbols.get(key); if (instance == null) { instance = new Symbol(key); symbols.put(key, instance); } return instance; } Factory public String getKey() { return key; } Bruno Dufour - Université de Montréal Abstraire la création d’objet 75 Exemple - Sans Factory • new crée un objet concret (mécanisme inflexible) • introduit une dépendance vers une classe concrète plutôt qu’une abstraction ou interface (DIP) • lors d’un changement dans le système, ces dépendances sont plus susceptibles de propager le changement • Le patron Factory rend la création d’objet plus abstraite List words = new ArrayList(); words.addAll(...); Iterator i = new ArrayListIterator(words); while (i.hasNext()) { System.out.println(i.next()); } • remplace new par un appel de méthode qui retourne un objet • cette méthode retourne une interface (abstraction) plutôt qu’un type concret Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 76 Exemple - Sans Factory 77 Exemple - Avec Factory 78 La méthode iterator() retourne une abstraction (Iterator) plutôt qu’un type concret. List words = new LinkedList(); words.addAll(...); Iterator i = new LinkedListIterator(words); List words = new LinkedList(); words.addAll(...); Iterator i = words.iterator(); while (i.hasNext()) { System.out.println(i.next()); } while (i.hasNext()) { System.out.println(i.next()); } Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal Factory Method 79 La liste concrète peut maintenant décider du type concret d’itérateur à utiliser. Factory Method pour la réutilisation • Les frameworks nécessitent souvent des interfaces pour les classes d’application qu’ils doivent manipuler • Intention : fournir une interface pour la création d’objets, mais en laissant les sous-classes décider du type concret d’objet à créer • ex : documents dans une application MDI • Motivation : • Une classe est incapable d’anticiper le type d’objets qu’elle doit créer • Une classe désire laisser le choix du type d’objets créés à ses sous-classes Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 80 Factory Method pour la réutilisation 81 Factory method - Structure 82 public interface Document {...} public abstract class Application { public Document newDocument() { ... } public Document openDocument() { ... } protected abstract Document createDocument(); } public class TextEditor extends Application { protected Document createDocument() { return new PlainTextDocument(); } } public class WordProcessor extends Application { protected Document createDocument() { return new RichTextDocument(); } } Bruno Dufour - Université de Montréal Factory Method - Paramètres Bruno Dufour - Université de Montréal 83 Créer des familles d’objets • Le patron Factory Method peut prendre des paramètres pour plus de flexibilité • this est déjà un paramètre implicite public abstract class Application { public Document newDocument() { ... } public Document openDocument() { ... } protected abstract Document createDocument(String title); } public class TextEditor extends Application { protected Document createDocument(String title) { return new PlainTextDocument(title + “.txt”); } } Bruno Dufour - Université de Montréal • Dans certains cas, il faut créer une multitude d’objets différents, mais de la même famille, plutôt qu’un seul objet • Les objets concrets sont reliés, mais on désire pour choisir entre les familles au cours de l’exécution • La famille constitue une abstraction dans le système Bruno Dufour - Université de Montréal 84 Exemple de familles d’objets 85 Factory method et familles 86 • Le patron Factory Method n’offre qu’une solution partielle: public Button newButton(String label) { ... } public Label newLabel(String text) { ... } public TextField newTextField() { ... } • Factory Method n’a aucune notion de famille • On ne veut pas mélanger des widgets Windows, Linux ou Mac OS! • Abstract Factory étend le mécanisme pour inclure les familles d’objets Source: Tyler Burton Bruno Dufour - Université de Montréal Abstract Factory - Exemple } public public public public interface WidgetFactory { Button newButton(String label); Label newLabel(String text); TextField newTextField(); Bruno Dufour - Université de Montréal 87 Abstract Factory - Structure Factory Method public class GTKWidgetFactory implements WidgetFactory { ... } public class MacOSWidgetFactory implements WidgetFactory { ... } public class MSWinWidgetFactory implements WidgetFactory { ... } public class ApplicationWindow { public void create(WidgetFactory factory) { Button okButton = factory.newButton(“OK”); Button cancelButton = factory.newButton(“Cancel”); TextField text = factory.newTextField(); ... } Tous les éléments de l’interface pour } cette fenêtre appartiendront à la même famille Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 88 Dependency Injection Dependency Injection - Guice 89 public class RealBillingService implements BillingService { private final CreditCardProcessor processor; Dépendances abstraites private final TransactionLog transactionLog; (interfaces) • La technique de Dependency Injection est une alternative à Factory dans bien des cas • Vise à remplacer des dépendances statiques par } des dépendances dynamiques • Plusieurs implémentations populaires (Spring, Guice, PicoContainer, etc.) • Utilisations courantes: Guice appelera ce constructeur @Inject RealBillingService(CreditCardProcessor processor, TransactionLog transactionLog) { this.processor = processor; this.transactionLog = transactionLog; } public class BillingModule extends AbstractModule { protected void configure() { bind(TransactionLog.class).to(DatabaseTransactionLog.class); bind(CreditCardProcessor.class).to(VisaCreditCardProcessor.class); } } • Chargement dynamique de plugins • Création d’instances ou de stubs lors de tests automatisés un objet de type concret VisaCreditCardProcessor sera utilisé comme implémentation de l’interface CreditCardProcessor Bruno Dufour - Université de Montréal Dependency Injection - Guice public static void main(String[] args) { Injector injector = Guice.createInjector(new BillingModule()); RealBillingService billingService = injector.getInstance(RealBillingService.class); ... } Crée une instance de VisaCreditCardProcessor, tel que défini par BillingModule (= factory) Bruno Dufour - Université de Montréal 90 Bruno Dufour - Université de Montréal 91 Builder Builder 93 Exemple - Sans Builder 94 • Créer un processus • • • • • • Parfois, les programmes doivent créer des objets complexes • Si on utilise new, il faut passer toute l’information nécessaire au constructeur lors de la création • Difficile, souvent beaucoup variations possibles • • Comme pour Factory, l’algorithme de création peut être indépendant des parties qui composent l’objet Commande à exécuter Arguments (String[], Collection?) Dossier courant (String, File?) 1 2+1 2+1 Environnement (Map) 1+1 Redirection stdin, stdout, stderr (File, Stream?) 3x3 162!! Seule la commande à exécuter est absolument nécessaire; les autres paramètres ont tous des valeurs par défaut raisonnables • Combien de constructeurs différents ? Bruno Dufour - Université de Montréal Exemple - Avec Builder Bruno Dufour - Université de Montréal 95 Exemple - Utilisation public final class ProcessBuilder { public ProcessBuilder(String command) {...} public ProcessBuilder arguments(List<String> args) {...} public ProcessBuilder arguments(String... args) {...} public ProcessBuilder directory(String directory) {...} public ProcessBuilder directory(File directory) {...} public ProcessBuilder (File directory) {...} public ProcessBuilder environment(Map<String,String> env) {...} public ProcessBuilder redirectStdin(File input) {...} public ProcessBuilder redirectStdin(InputStream input) {...} public void exec() { Process proc = new ProcessBuilder(“java”) .arguments(“ift3912.Server”) .directory(serverDir) .redirectStdout(logFile) .build(); } ... public Process build() {...} } Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 96 Builder Builder - Structure 97 98 • Intention : séparer la construction d’un objet de sa représentation • permet la construction de plusieurs représentations différentes à partir du même processus de construction • permet de construire un objet complexe indépendamment des parties qui le composent et de leur agencement Bruno Dufour - Université de Montréal Exemple - Builder Bruno Dufour - Université de Montréal Builder - Performance 99 public String greet(String speaker, String action, Object consequence) { StringBuilder builder = new StringBuilder(); builder.append(“Hello. My name is ”); builder.append(speaker); builder.append(“. ”); builder.append(“You ”); builder.append(action); builder.append(“. ”); builder.append(“Prepare to ”); builder.append(consequence); builder.append(“.”); return builder.toString(); } greet(“Inigo Montoya”, “killed my father”, Consequences.DIE); Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 100 Patrons structuraux Façade Façade 103 Façade 104 • Intention : Fournir une interface unie pour l’ensemble des interfaces d’un sous-système afin de réduire la complexité tout en maintenant la fonctionnalité Façade& Source:(Paul(Mayne Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal Source:(Logitech Façade - Exemple } 105 Façade - Exemple 106 public class HomeTheaterFacade { private Amplifier amp; private Tuner tuner; private DvdPlayer dvd; private CdPlayer cd; private Projector projector; private TheaterLights lights; private Screen screen; public void watchMovie() { System.out.println("Get ready to watch a movie..."); lights.dim(10); screen.down(); projector.on(); projector.wideScreenMode(); amp.on(); amp.setInput(dvd); amp.setSurroundSound(); amp.setVolume(5); dvd.on(); } Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal Decorator - Motivation • Considérons une boîte de texte avec • une bordure • des barres de défilement Decorator Bruno Dufour - Université de Montréal 108 Decorator - Motivation Exemple - Sans Decorator 109 110 • Solution #1: par héritage • 3 responsabilités • Beaucoup de sous-classes nécessaires • affichage du texte • affichage de la bordure • affichage des barres de défilement • HorizPlainTextView, VertPlainTextView, BothPlainTextView, Horiz3DTextView, Vert3DTextView, Both3DTextView • Et si on ajoute un autre type de bordure ? Une • Supposons que la bordure et les barres de défilement autre responsabilité ? sont appelées à changer • Problèmes • ex: 2 types de bordures (Plain, 3D) • ex: barre de défilement horizontale, verticale ou les • Explosion du nombre de classes • Chaque sous-classe correspond à un ensemble de deux à la fois choix fixés au moment de la compilation (rigide) Bruno Dufour - Université de Montréal Exemple - Sans Decorator (#2) public class TextView extends Component { private Border border; private Scrollbar sb; public TextView(Border border, Scrollbar sb) { this.border = border; this.sb = sb; } } public void draw() { border.draw(); sb.draw(); // draw TextView itself } Bruno Dufour - Université de Montréal Exemple - Sans Decorator (Strategy) 111 public class TextView extends Component { La classe TextView a été modifiée, et private Border border; dépend maintenant de Border et private Scrollbar sb; Scrollbar (OCP) public TextView(Border border, Scrollbar sb) { this.border = border; this.sb = sb; } } public void draw() { border.draw(); sb.draw(); // draw TextView itself } Utilise la délégation pour implémenter les différentes responsabilités. Et si on voulait ajouter une autre fonctionnalité ? On devrait modifier TextView (OCP) Bruno Dufour - Université de Montréal 112 Bruno Dufour - Université de Montréal Exemple - Avec Decorator Exemple - TextView 113 • Le patron Decorator renverse la solution précédente } • Nous allons décorer l’objet TextView avec de nouvelles fonctionnalités 114 public class TextView extends Component { public void draw() { // draw TextView itself. TextView n’a plus de dépendance! } public class PlainBorder { private Component component; Bruno Dufour - Université de Montréal Chaîner les décorateurs public PlainBorder(Component component) { this.component = component; } } public void draw() { component.draw(); // Draw the border itself. } Bruno Dufour - Université de Montréal 115 Exemple - Hiérarchie public class TextView extends Component { public void draw() { // draw TextView itself. } } public class VertScrollbar extends Component { private Component component; public PlainBorder(Component component) {...} } ... public class PlainBorder extends Component { private Component component; public PlainBorder(Component component) {...} ... } Bruno Dufour - Université de Montréal Decorator délègue à TextView Bruno Dufour - Université de Montréal 116 Decorator - Chaîne de décorateurs VertScrollbar PlainBorder 117 TextView Decorator 118 • Intention : Ajouter des responsabilité à un objet durant l’exécution • Alternative à l’héritage Component view = new TextView(); view = new FancyBorder(view); view = new VertScrollbar(view); • Motivation : • Permet d’ajouter des responsabilités à des objets ... de façon dynamique et transparente view.draw(); • Permet de retirer des responsabilités • Fournit une alternative à l’héritage dans les cas où plusieurs responsabilités indépendantes causeraient une explosion du nombre de classes Bruno Dufour - Université de Montréal Decorator - Structure Bruno Dufour - Université de Montréal 119 Decorator - Exemple (JDK) InputStream FileInputStream BufferedInputStream Bruno Dufour - Université de Montréal StringBufferInputStream Component ByteArrayInputStream FilterInputStream Decorator DataInputStream LineNumberInputStream Bruno Dufour - Université de Montréal 120 Decorator - Exemple (JDK) LineNumberInputStream Permet de compter le nombre de lignes lues BufferedInputStream Améliore la performance de la lecture à l’aide de mémoire tampon Decorator - Ajout de responsabilité 121 FileInputStream Permet de lire un fichier public class LowerCaseInputStream extends FilterInputStream { public LowerCaseInputStream(InputStream in) { super(in); } public int read() throws IOException { int c = super.read(); return (c == -1 ? c : Character.toLowerCase((char)c)); } 122 public int read(byte[] b, int off, int len) throws IOException { int result = super.read(b, off, len); for (int i = off; i < off+result; i++) { b[i] = (byte) Character.toLowerCase((char)b[i]); } return result; } } Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal Adapter (Structurel) • Problème • Convertir l’interface d’une classe en une autre Adapter interface attendue par le client (interface cible) afin de permettre à des classes incompatibles de travailler de concert • Souvent motivé par la réutilisation de code: le code réutilisé doit se conformer à une interface requise • Solutions • Par classe: héritage multiple / interfaces • Par objet: composition Bruno Dufour - Université de Montréal 124 Adapter – Par classe 125 Bruno Dufour - Université de Montréal Adapter - Exemple Adapter – Par objet Bruno Dufour - Université de Montréal 127 Adapter - Exemple public interface Enumeration<T> { public boolean hasMoreElements(); public T nextElement(); } public interface Iterator<T> { public boolean hasNext(); public T next(); public void remove(); } Bruno Dufour - Université de Montréal 126 Bruno Dufour - Université de Montréal 128 Adapter - Exemple 129 public class EnumerationIterator<T> implements Iterator<T> { private Enumeration<T> e; public EnumerationIterator(Enumeration<T> e) { this.e = e; } public boolean hasNext() { return e.hasMoreElements(); } Patrons comportementaux public T next() { return e.nextElement(); } public void remove() { throw new UnsupportedOperationException(); } } Bruno Dufour - Université de Montréal Strategy • Intention : permettre de choisir dynamiquement un algorithme parmi une famille d’algorithmes interchangeables Strategy Bruno Dufour - Université de Montréal 132 Strategy - Exemple 133 Strategy - Structure 134 Frame f = new Frame(); f.setLayout(new FlowLayout()); f.add(new Button(“Press”)); Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal State • Intention : Permettre à un objet de changer son comportement quand son état interne change State Bruno Dufour - Université de Montréal 136 State - Motivation 137 State - Structure • Permet de partitionner le comportement spécifique à un état pour les états complexes • Moins compact, plus de classes • Rend les transitions d’état explicites • Permet de partager les objets état • Presque identique à Strategy, mais l’intention est différente Bruno Dufour - Université de Montréal Bruno Dufour - Université de Montréal 138