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

Documents pareils