2. C++ Avancé
2.12. Design Pattern
Un design pattern (ou patron de conception en français) est une solution conceptuelle
à un problème logiciel donné.
On implante parfois sans le savoir des patrons de conception.
2.12.1. Façade
Exemple d'un design pattern qui n'en est pas un :
Façade : il permet de remédier à une situation complexe ou plusieurs classes
ou modules interagissent entre-eux.
2.12.2. Singleton
Singleton : il permet de générer une seule instance de la classe.
Exemple : on peut l'utiliser dans le cas d'un jeux de paramètres qui sera partagé
par plusieurs classes.
#include <iostream>
using namespace std;
/**
* class Parameters that implements Singleton Design Pattern
*/
class Parameters {
protected:
int taxa_count;
int taxa_length;
private:
// *** must declare the instance static ***
static Parameters *instance;
// default constructor
// all constructors must be *** private ***
Parameters() {
taxa_count = 0;
taxa_length = 0;
}
public:
// *** must declare get_instance static ***
static Parameters& get_instance() {
if (instance == NULL) {
// call default constructor
instance = new Parameters;
}
return *instance;
}
// getters
int get_taxa_count() {
return taxa_count;
}
int get_taxa_length() {
return taxa_length;
}
// setters
void set_taxa_count(int n) {
taxa_count = n;
}
void set_taxa_length(int n) {
taxa_length = n;
}
ostream& print(ostream& out) {
out << "taxa count = " << taxa_count << endl;
out << "taxa length = " << taxa_length << endl;
}
friend ostream& operator<<(ostream& out, Parameters& p) {
return p.print(out);
}
};
// don't forget to declare static instance of Parameters
Parameters *Parameters::instance = NULL;
// main
int main() {
Parameters& p = Parameters::get_instance();
p.set_taxa_count(10);
p.set_taxa_length(100);
Parameters& q = Parameters::get_instance();
cout << q << endl;
return 0;
}
2.12.3. Factory
Factory : il permet de créer des instances qui ont quelque chose en commun.
Exemple: créer des personnes, étudiants, enseignants issus d'une hierarchie d'objets.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
/**
* base class Person with name and age
*/
class Person {
protected:
string name;
int age;
public:
Person(string n, int a) : name(n), age(a) {
}
virtual ostream& print(ostream& out) {
out << "person " << name << ", " << age << endl;
}
friend ostream& operator<<(ostream& out, Person& p) {
return p.print(out);
}
};
/**
* Student is a Person that is registered to some Faculty
*/
class Student : public Person {
protected:
string level;
public:
Student(string n, int a, string l) : Person(n, a), level(l) {
}
ostream& print(ostream& out) {
out << "student " << name << ", " << age << " in " << level << endl;
}
};
/**
* Teacher is a Person that gets a salary
*/
class Teacher : public Person {
protected:
float salary;
public:
Teacher(string n, int a, float s) : Person(n, a), salary(s) {
}
ostream& print(ostream& out) {
out << "teacher " << name << ", " << age << " of salary $" << salary << endl;
}
};
/**
* Factory of Person, Student and Teacher
* we use polymorphism and have a 'create' method with different
* arguments
*/
class PersonFactory {
public:
// create a Person
static Person *create(string n, int a) {
return new Person(n, a);
}
// create a Student
static Person *create(string n, int a, string l) {
return dynamic_cast<Person *>(new Student(n, a, l));
}
// create a Teacher
static Person *create(string n, int a, float s) {
return dynamic_cast<Person *>(new Teacher(n, a, s));
}
};
// main
int main() {
vector<Person *> people;
Person *Tim = PersonFactory::create("Timothy", 25);
Person *Joe = PersonFactory::create("Joey", 20, "Computer Science");
Person *Sam = PersonFactory::create("Samantha", 21, "Medicine");
Person *Rod = PersonFactory::create("Rodney", 45, 3000.0);
people.push_back(Tim);
people.push_back(Joe);
people.push_back(Sam);
people.push_back(Rod);
for(Person *p : people) {
cout << *p ;
}
}
person Timothy, 25
student Joey, 20 in Computer Science
student Samantha, 21 in Medicine
teacher Rodney, 45 of salary $3000
2.12.4. Observer
Observer : il permet lors de la modification d'une donnée de mettre à jour d'autres classes.
Exemple: un dispose d'une série de valeurs qui est affichée deux fois sous forme de
graphiques : un histogramme, un camembert. Si on modifie l'une des données (modification,
ajout, suppression), on veut que les graphiques soient mis à jour.
#include <iostream>
#include <list>
#include <string.h>
#include <algorithm>
using namespace std;
/**
* interface for Observer
*/
class Observer {
public:
virtual void notify() = 0;
};
/**
* base class for Subject
*/
class Subject {
protected:
list<Observer *> l;
public:
Subject() {
}
virtual ~Subject() {
}
void add_observer(Observer *o) {
l.push_back(o);
}
void remove_observer(Observer *o) {
list<Observer *>::iterator it = find(l.begin(), l.end(), o);
if (it != l.end()) l.erase(it);
}
void notify_observers() {
for (list<Observer *>::iterator it = l.begin(); it != l.end(); ++it) {
(*it)->notify();
}
}
};
/**
* Concrete subject that contains an array of integers
*
*/
class DataSubject : public Subject {
protected:
int *data;
public:
DataSubject() : Subject() {
data = new int [ 4 ];
memset(data, 0, 4 * sizeof(int));
}
~DataSubject() {
delete [] data;
}
void set(int n, int v) {
data[n] = v;
// notify observers on change
notify_observers();
}
int get(int n) {
return data[n];
}
};
/**
* Concrete observer for Bar graph
*/
class BarGraph : public Observer {
protected:
Subject *subject;
public:
BarGraph(Subject *s) : Observer() {
subject = s;
}
~BarGraph() {
}
void notify() {
cout << "BarGraph : " << endl;
DataSubject *sub = dynamic_cast<DataSubject *>(subject);
for (int i=0; i<4; ++i) {
cout << sub->get(i) << " " ;
}
cout << endl;
}
};
/**
* Concrete observer for Pie graph
*/
class PieGraph : public Observer {
protected:
Subject *subject;
public:
PieGraph(Subject *s) : Observer() {
subject = s;
}
~PieGraph() {
}
void notify() {
cout << "PieGraph : " << endl;
DataSubject *sub = dynamic_cast<DataSubject *>(subject);
for (int i=0; i<4; ++i) {
cout << sub->get(i) << " " ;
}
cout << endl;
}
};
// main
int main() {
// create subject
DataSubject a_subject;
// create observers
BarGraph bar( &a_subject );
PieGraph pie( &a_subject );
// register observers
a_subject.add_observer( &bar );
a_subject.add_observer( &pie );
// apply modification on data that will be notified to observers
a_subject.set(1,1);
return 0;
}
2.12.5. Autres design patterns
Voir sur Wikipedia
2.12.6. L'Inversion de contrôle et l'Injection de dépendance
L'inversion de contrôle (Inversion of Control, IoC) est un patron d'architecture commun aux frameworks.
Voir sur Wikipedia pour un exemple explicatif.
L'injection de dépendances (Dependency Injection) est un mécanisme qui permet d'implémenter le principe de l'inversion de contrôle. Il consiste à créer dynamiquement (injecter) les dépendances entre les différentes classes en s'appuyant sur une description (fichier de configuration ou métadonnées) ou de manière programmatique. Ainsi les dépendances entre composants logiciels ne sont plus exprimées dans le code de manière statique mais déterminées dynamiquement à l'exécution (cf Wikipedia).
De manière plus simpliste, on peut expliquer l'injection de dépendance de la manière suivante : On considère
un véhicule composé de 4 pneus et un moteur. Une fonction dépendant du kilométrage pourra mesurer l'usure du véhicule.
Une implantation pour créer un véhicule est la suivante :
class Pneu { ... };
class Moteur { ... };
class Vehicule {
protected:
Pneu *pneus[4];
Moteur *moteur;
float kilometrage;
public:
/**
* constructeur avec type de véhicule
*/
Vehicule(int type) {
if (type == _4x4) {
pneus[0] = new Goodyear();
pneus[1] = new Goodyear();
pneus[2] = new Goodyear();
pneus[3] = new Goodyear();
moteur = new LandRover();
} else if (type == BERLINE) {
pneus[0] = new Michelin();
pneus[1] = new Michelin();
pneus[2] = new Michelin();
pneus[3] = new Michelin();
moteur = new Renault();
}
...
}
/**
* calcul de l'usure en fonction du kilométrage
*/
float usure() {
...
}
};
Le défault de cette implantation est qu'elle est trop fortement couplée à Pneu et Moteur.
Si on ajoute un nouveau type de véhicule, il faudra modifier le code de la classe.
Deux composants sont dits couplés s'ils échangent de l'information. Il existe différents types et
niveaux de couplages (cf Wikipedia).
Une solution plus adaptée (ou conforme à l'injection de dépendance) consiste à passer en paramètre
Pneus et Moteur, mais au final on ne fait que déplacer le problème.
class Vehicule {
protected:
Pneu *pneus[4];
Moteur *moteur;
float kilometrage;
public:
/**
* constructeur avec pièces du véhicule
*/
Vehicule(Moteur *m, Pneu *p[4]) {
pneus[0] = p[0];
pneus[1] = p[1];
pneus[2] = p[2];
pneus[3] = p[3];
moteur = m;
}
...
};