Décorateur (patron de conception)

Si ce bandeau n'est plus pertinent, retirez-le. Cliquez ici pour en savoir plus.
Si ce bandeau n'est plus pertinent, retirez-le. Cliquez ici pour en savoir plus.

Cet article ne cite pas suffisamment ses sources ().

Si vous disposez d'ouvrages ou d'articles de référence ou si vous connaissez des sites web de qualité traitant du thème abordé ici, merci de compléter l'article en donnant les références utiles à sa vérifiabilité et en les liant à la section « Notes et références ».

En pratique : Quelles sources sont attendues ? Comment ajouter mes sources ?

Page d’aide sur l’homonymie

Pour les articles homonymes, voir Décorateur.

En génie logiciel, un décorateur est le nom d'une des structures de patron de conception.

Un décorateur permet d'attacher dynamiquement de nouvelles responsabilités à un objet. Les décorateurs offrent une alternative assez souple à l'héritage pour composer de nouvelles fonctionnalités.

Aperçu

Le pattern Décorateur est l'un des vingt-trois patterns GOF. Il résout les problématiques suivantes :

  • L'ajout (et la suppression) des responsabilités à un objet dynamiquement au moment de l'exécution.
  • Il constitue une alternative aux sous-classes pour une surcharge flexible des fonctionnalités.

Quand on utilise l'héritage, les différentes sous-classes étendent une classe mère en différentes manières. Mais une extension est attachée à la classe au moment de la compilation, et ne peut pas changer à l'exécution.

Exemple en Java

// ______________________________________________________________________
// Déclarations
public interface IVehicule {
	public String getNom();
	public String getMarque();
	public int getPrix();
	public int getPoids();
}

public abstract class Voiture implements IVehicule {
	private String aNom;
	private String aMarque;

	protected void setNom(String pNom) {
		this.aNom = pNom;
	}

	public String getNom() {
		return this.aNom;
	}

	protected void setMarque(String pMarque) {
		this.aMarque = pMarque;
	}

	public String getMarque() {
		return this.aMarque;
	}
}

public class DS extends Voiture {
	public DS() {
		this.setNom("DS");
		this.setMarque("Citroën");
	}

	public int getPrix() {
		return 30000;
	}

	public int getPoids() {
		return 1500;
	}
}

// ______________________________________________________________________
// Décorateurs
public abstract class VoitureAvecOption extends Voiture {
	private IVehicule aVehicule;

	protected void setVehicule(IVehicule pVehicule) {
		this.aVehicule = pVehicule;
	}

	public IVehicule getVehicule() {
		return this.aVehicule;
	}
}

class VoitureAvecToitOuvrant extends VoitureAvecOption {
	public int getPrix() {
		return this.getVehicule().getPrix() + 10000;
	}

	public int getPoids() {
		return this.getVehicule().getPoids() + 15;
	}
}

//On garde le nom du pattern Decorator pour savoir qu'on wrap un objet 
class DSAvecToitOuvrantDecorator extends VoitureAvecToitOuvrant {
	public DSAvecToitOuvrantDecorator(DS pDS) {
		this.setVehicule(pDS);
	}
}

public class Main {
	// ______________________________________________________________________
	// Implémentation
	public static void main(String[] args) {
		DS vDS = new DS();
		IVehicule vDSOption = new DSAvecToitOuvrantDecorator(vDS);
		System.out.println(vDSOption.getPoids() + " - " + vDSOption.getPrix());
	}
}

Exemple en C#

Ici l'héritage est utilisé.

//______________________________________________________________________
// Déclarations

abstract class Voiture {
    public abstract double Prix { get; }
}
class  AstonMartin : Voiture {
    public override double Prix { get { return 999.99; } }
}

//______________________________________________________________________
// Décorateurs

class Option : Voiture {
    protected Voiture _originale;
    protected double _tarifOption;
    public Option(Voiture originale, double tarif) { 
        _originale = originale;
        _tarifOption = tarif; 
    }
    public override double Prix {
        get { return _originale.Prix + _tarifOption; }
    }
}

class VoitureAvecClimatisation : Option {
    public VoitureAvecClimatisation (Voiture originale) : base(originale, 1.0) { }
}
class VoitureAvecParachute : Option {
    public VoitureAvecParachute (Voiture originale) : base(originale, 10.0) { }
}
class VoitureAmphibie : Option {
    public VoitureAmphibie (Voiture originale) : base(originale, 100.0) { }
}

//______________________________________________________________________
// Implémentation

class Program {
    static void Main() {
        Voiture astonMartin= new AstonMartin();
        astonMartin = new VoitureAvecClimatisation(astonMartin);
        astonMartin = new VoitureAvecParachute(astonMartin);
        astonMartin = new VoitureAmphibie(astonMartin);

        Console.WriteLine(astonMartin.Prix); // affiche 1110.99
    }
}

Exemple en C++

# include <iostream>
# include <memory>

class IVoiture
{
public:
  virtual double prix() = 0;
};

class Aston_martin : public IVoiture
{
public:
  double prix() override { return 999.99l; }
};

class Option_voiture : public IVoiture
{
public:
  Option_voiture(std::unique_ptr<IVoiture> voiture, double prix_option)
    : voiture_(std::move(voiture))
    , prix_option_(prix_option)
  {}
  double prix() override { return voiture_->prix() + prix_option_; }
protected:
  std::unique_ptr<IVoiture> voiture_;
  double prix_option_;
};

class Option_clim : public Option_voiture
{
public:
  Option_clim(std::unique_ptr<IVoiture> voiture) : Option_voiture(std::move(voiture), 1.0) {}
};

class Option_parachute : public Option_voiture
{
public:
  Option_parachute(std::unique_ptr<IVoiture> voiture) : Option_voiture(std::move(voiture), 10.0) {}
};

class Option_amphibie : public Option_voiture
{
public:
  Option_amphibie(std::unique_ptr<IVoiture> voiture) : Option_voiture(std::move(voiture), 100.0) {}
};

int main()
{
  auto voiture = std::unique_ptr<IVoiture>(std::make_unique<Aston_martin>());
  voiture = std::make_unique<Option_clim>(std::move(voiture));
  voiture = std::make_unique<Option_parachute>(std::move(voiture));
  voiture = std::make_unique<Option_amphibie>(std::move(voiture));
  std::cout << voiture->prix() << "\n"; // affiche 1110.99
  return 0;
}

Exemple en PHP

<?php

// Interface pour rendre un objet affichable
interface Affichable {
	public function affiche();
}

// Classe contenant un message affichable
class Message implements Affichable {

	protected $message = '';
	
	public function __construct($message) {
		$this->message = $message;
	}
	
	public function affiche() {
		echo $this->message;
	}

}

// Une classe abstraite de décoration de message affichable
abstract class DecorateurDeMessage implements Affichable {
	
	protected $messageDecore = null;
 
	public function __construct(Affichable $messageDecore) {
		$this->messageDecore = $messageDecore;
	}
}

// Une classe pour "décorer" un message en gras
class MessageEnGras extends DecorateurDeMessage {

	public function affiche() {
		echo '<strong>';
		$this->messageDecore->affiche();
		echo '</strong>';
	}
}
 
// Une classe pour "décorer" un message en italique
class MessageEnItalique extends DecorateurDeMessage {

	public function affiche() {
		echo '<em>';
		$this->messageDecore->affiche();
		echo '</em>';
	}
}

// Création du message
$message = new Message('le message');

// On met le message en gras et en italique
$messageDecore = new MessageEnItalique( new MessageEnGras( $message ) );
// On affiche le message décoré
$messageDecore->affiche();
?>

Exemple en Delphi

En effet, les langages Delphi et Free Pascal supportent les class helpers qui rendent inutile le patron de conception décorateur.

Attention car Embarcadero déconseille l'utilisation des class helpers[1].

program NoMoreDecorators;

type
  TMyObject = class
    procedure WriteHello;
  end;

  TMyObjectHelper = class helper for TMyObject
    procedure WriteHello(const Name: string); overload;
  end;

procedure TMyObject.WriteHello;
begin
  writeln('Hello');
end;

procedure TMyObjectHelper.WriteHello(const Name: string);
begin
  writeln('Hello, ', Name, '!');
end;

var
  o: TMyObject;
begin
  o := TMyObject.Create;
  o.WriteHello;
  o.WriteHello('Jean');
  o.Free;
end.

source : Delphi GOF DesignPatterns (CodePlex)

Exemple en Delphi
unit decorator;

interface
 type
   IComponent = interface
     ['{8021ECE2-0D60-4C96-99AA-C5A6C515DF52}']
     function Operation(): String;
   End;

   TComponent = class (TInterfacedObject, IComponent)
   public
     function Operation(): String;
   end;

   TDecoratorA = class (TInterfacedObject, IComponent)
   private
     FComponent: IComponent;
   public
     function Operation(): String;
     constructor Create(c: IComponent);
   end;

   TDecoratorB = class (TInterfacedObject, IComponent)
   private
     FComponent: IComponent;
   public
     addedState: String;
     function Operation(): String;
     function AddedBehaviour(): String;
     constructor Create(c: IComponent);
   end;

   TClient = class
     class procedure Display(s: String; c: IComponent);
   end;


implementation

{ TComponent }

function TComponent.Operation: String;
begin
 Result := 'I am walking ';
end;

{ TDecoratorA }

constructor TDecoratorA.Create(c: IComponent);
begin
 inherited Create;
 Self.FComponent := c;
end;

function TDecoratorA.Operation: String;
var
 s: String;
begin
 s := Self.FComponent.Operation;
 s := s + 'and listening to Classic FM ';
 Result := s;
end;

{ TDecoratorB }

function TDecoratorB.AddedBehaviour: String;
begin
 Result := 'and I bouth a capuccino ';
end;

constructor TDecoratorB.Create(c: IComponent);
begin
 inherited Create;
 Self.FComponent := c;
 Self.addedState := 'past the coffe shop ';
end;

function TDecoratorB.Operation: String;
var
 s: String;
begin
 s := Self.FComponent.Operation;
 s := s + 'to school ';
 Result := s;
end;

{ TClient }

class procedure TClient.Display(s: String; c: IComponent);
begin
 WriteLn(s + c.Operation);
end;

end.

{ projet }

program Structural.Decorator.Pattern;

{$APPTYPE CONSOLE}

uses
 SysUtils,
 decorator in 'decorator.pas';

var
 component, decoratorA, decoratorB: IComponent;
 decB: TDecoratorB;

begin
 try
   WriteLn('Decorator Pattern' +  #10);
   component := TComponent.Create;
   try
     decoratorA := TDecoratorA.Create(component);
     decoratorB := TDecoratorB.Create(component);

     TClient.Display('1. Basic component : ', component);
     TClient.Display('2. A-Decorated     : ', decoratorA);
     TClient.Display('3. B-Decorated     : ', decoratorB);

     decoratorB := TDecoratorB.Create(decoratorA);
     TClient.Display('4. B-A-Decorated   : ', decoratorB);

     decB := TDecoratorB.Create(component);
     decoratorA := TDecoratorA.Create(decB);
     TClient.Display('5. A-B-Decorated   : ', decoratorA);
     WriteLn(decB.addedState + decB.AddedBehaviour);

   finally
     component.Free;
   end;

   ReadLn;
 except
   on E:Exception do
     Writeln(E.Classname, ': ', E.Message);
 end;
end.
 

Notes et références

  1. (en) « Class and Record Helpers (Delphi) », sur embarcadero.com (consulté le ).
  • Le patron "décorateur" sur le site developpez.com
  • (en) Description par Vince Huston
  • (en) Decorator Pattern
  • (en) C# Conception par James W. Cooper
  • (en) Approche Delphi
  • (en) Article Trois approches java pour "décorer" votre code" par Michael Feldman
  • (en) Article "Utiliser le patron "décorateur"" par Budi Kurniawan

Annexes

Sur les autres projets Wikimedia :

  • Décorateur (patron de conception), sur Wikibooks

Articles connexes

  • Adaptateur (patron de conception)
v · m
Patrons de conception
Création
  • Fabrique abstraite
  • Monteur
  • Fabrique
  • Prototype
  • Singleton
Structure
  • Adaptateur
  • Pont
  • Composite
  • Décorateur
  • Façade
  • Poids-mouche
  • Proxy
Comportement
Fonctionnel
  • Fermeture
  • Curryfication
  • Composition de fonctions
  • Foncteur
  • Monade
  • Générateur
Patron d'architecture
Autres patrons
  • icône décorative Portail de la programmation informatique
  • icône décorative Portail de l’informatique