En informatique, plus précisément 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.
Le pattern Décorateur est l'un des vingt-trois patterns GOF. Il résout les problématiques suivantes :
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.
// ______________________________________________________________________
// 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());
}
}
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
}
}
# 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;
}
<?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();
?>
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)
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.