Version without JavaScript, read-only: workshops, quizzes and running code are not available here. Full version · Catalogue · Sign in
Lecture notes · chapter 10
Design patterns
Three patterns to be able to recognise, eleven others presented for reference, and the question to ask before using one: which problem needs solving?
This chapter extends sequences 10 and 12.
A design pattern describes a design problem that comes up often, the constraints involved, a generic solution and its drawbacks. It is therefore not a piece of code to copy.
The vocabulary comes from the Gang of Four (Gamma, Helm, Johnson, Vlissides, 1994), who sort patterns into three families: creational, structural, behavioural.
The three certified patterns
Strategy: an interchangeable algorithm
The problem. A behaviour has several variants, and you want to be able to change it without modifying the code that uses it, sometimes during execution.
This is the case of VolStrategy, already seen in chapters 4 and 5.
interface VolStrategy { void voler(); }
class VolerAvecAiles implements VolStrategy {
public void voler() { System.out.println("je vole"); }
}
class NePasVoler implements VolStrategy {
public void voler() { System.out.println("je ne vole pas"); }
}
class Canard {
private VolStrategy vol;
Canard(VolStrategy vol) { this.vol = vol; }
void effectuerVol() { vol.voler(); }
void equiper(VolStrategy v) { this.vol = v; } // hot swap
}
When to consider it. A switch or a sequence of ifs on a type or a mode, repeated in several places.
Factory: moving the decision about the concrete type elsewhere
The problem. The class to instantiate depends on some data, and you do not want every caller to know all the concrete classes.
interface Paiement { void debiter(int centimes); }
class PaiementCarte implements Paiement { public void debiter(int c) { } }
class PaiementPaypal implements Paiement { public void debiter(int c) { } }
class FabriquePaiement {
static Paiement pour(String mode) {
if ("carte".equals(mode)) return new PaiementCarte();
if ("paypal".equals(mode)) return new PaiementPaypal();
throw new IllegalArgumentException("mode inconnu : " + mode);
}
}
The switch still exists, but it is grouped in a single place. To add a means of payment, you only modify the factory instead of going through every caller.
When to consider it. The same conditional new copied into several classes.
Decorator: adding a responsibility at run time
The problem. You want to add features to an object without creating a subclass, and to be able to combine these additions in the desired order.
interface Document { byte[] octets(); }
class DocumentSimple implements Document {
public byte[] octets() { return "coin".getBytes(); }
}
class DocumentCompresse implements Document {
private final Document interne;
DocumentCompresse(Document d) { this.interne = d; }
public byte[] octets() { return compresser(interne.octets()); }
}
class DocumentChiffre implements Document {
private final Document interne;
DocumentChiffre(Document d) { this.interne = d; }
public byte[] octets() { return chiffrer(interne.octets()); }
}
// Order matters: we compress, then we encrypt.
Document d = new DocumentChiffre(new DocumentCompresse(new DocumentSimple()));
With inheritance, you would have had to write DocumentCompresseEtChiffre, DocumentChiffreEtCompresse, and a new class for each combination. This is the two-axes argument of chapter 4, applied here to adding features.
When to consider it. Subclasses whose name contains an “and” (“Et”).
The other patterns, for reference
These patterns are not certified. They are presented so that you recognise them when you come across them, for example during a code review.
Creational: Singleton, Abstract Factory, Builder
Singleton: a class that has only one instance, with controlled access. In Java, it is written with an inner class that holds the instance, or with an enum.
public final class Ressources {
private Ressources() {}
private static class Porteur { static final Ressources I = new Ressources(); }
public static Ressources get() { return Porteur.I; }
}
It is better to avoid it as much as possible: a singleton is in fact global state, and it makes testing difficult. Pass the object as a parameter instead.
Abstract Factory: producing a family of objects that go together (a light theme, a dark theme) without exposing the concrete classes.
Builder: constructing step by step an object that has many fields, instead of calling a constructor with eight parameters whose order is easily forgotten.
Rapport r = new Rapport.Monteur()
.titre("Bilan T3").auteur("Équipe BI").filigrane(true).monter();Structural: Adapter, Facade, Composite
Adapter: making a library compatible with the interface your code expects, without modifying the library.
interface VitesseKmH { double kmh(); }
class VoitureTierce { double mph() { return 120.0; } }
class AdaptateurVitesse implements VitesseKmH {
private final VoitureTierce v;
AdaptateurVitesse(VoitureTierce v) { this.v = v; }
public double kmh() { return v.mph() * 1.60934; }
}
Facade: a simple method giving access to a complicated subsystem. For example, rendreFacture(facture) takes care of merging the template, embedding the fonts, signing and compressing.
Composite: treating a tree and a leaf the same way. The size of a folder is the sum of its children's sizes, that of a file is its own size, and the same taille() method serves for both.
Behavioural: Observer, Command, State, Template Method
Observer: notifying subscribers of a change, without the emitter having to know them.
class BusEvenements {
private final List<Abonne> abonnes = new ArrayList<>();
void abonner(Abonne a) { abonnes.add(a); }
void publier(Commande c) { for (Abonne a : abonnes) a.surCommande(c); }
}
Command: representing an action as an object, so that it can be queued, replayed or undone.
State: replacing a long switch on an internal state with one object for each state, which knows what to do and which state to move to.
Template Method: the parent class fixes the skeleton of an algorithm and leaves it to the child classes to complete some of its steps. This pattern relies on inheritance; reread chapter 4 before using it.
Choosing a pattern
Exam question
You are shown a class with a
switchon a calculation mode, repeated in three methods. Which pattern, and why?
It is a Strategy, because the behaviour has interchangeable variants. Each branch of the switch becomes an implementation of an interface, the class receives the one it needs, and the three repetitions disappear. If what were repeated were the choice of the class to instantiate, and not the behaviour, a Factory would be needed.