Decorator

La programmation avec design patterns

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
La décoration n'est plus réservée à des artistes et femmes. Désormais elle est également possible dans le développement. Ceci grâce au design pattern decorator.

Le design pattern decorator (le décorateur) est une sorte d'objet qui permet de rajouter la quantité infinie de nouvelles caractéristiques à un autre objet d'une manière dynamique. Une bonne illustration de ce patron de conception est tout l'objet du monde réel composé de plusieurs autres objets, comme par exemple certains plats et boissons (pizza, café, thé, cocktail). Prenons le cas d'un café. L'objet décoré serait alors le café. Les décorateurs seraient des ingrédients, comme le sucre, le lait ou le chocolat.

Dans le cas mentionné on remarque la présence des trois participants :
- decorated (décoré) : l'objet décoré, en occurrence le café
- abstract decorator (décorateur abstrait) : la définition du décorateur (en occurrence, un ingrédient). C'est lui qui définit les endroits qui vont être décorés dans l'objet décoré (dans notre cas ce sera la couleur).
- concrete decorator (décorateur concret) : l'implémentation concrète du décorateur abstrait (dans notre cas, le sucre, le lait ou le chocolat).

Exemple du decorator

// decorated
abstract class Coffee{
protected int candied=0;
protected double price=2d;

public abstract int makeMoreCandied();

public double getPrice(){
return this.price;
}

public void setPrice(final double price){
this.price+=price;
}
}

class BlackCoffee extends Coffee{
@Override
public int makeMoreCandied(){
return 0;
}

@Override
public double getPrice(){
return this.price;
}
}

Voici l'objet qui sera décoré. On remarque la présence des deux méthodes : getPrice() et makeMoreCandied(). La première permet de retourner le prix du café. La deuxième sera responsable de rajouter du sucre dans le café.


// abstract decorator
abstract class CoffeeDecorator extends Coffee{
protected Coffee coffee;

public CoffeeDecorator(final Coffee coffee){
this.coffee=coffee;
}

@Override
public double getPrice(){
return this.coffee.getPrice();
}

@Override
public int makeMoreCandied(){
return this.coffee.makeMoreCandied();
}
}

Cette classe abstraite définit les méthodes de l'objet décoré qui sont susceptibles d'être décorées. Dans ce cas, on va augmenter le prix et le café deviendra plus sucré. On remarque également que le contrôleur prend en paramètre l'instance de la classe abstraite Coffee. Cela pourra être aussi bien l'objet décoré que le décorateur concret qu'on verra tout de suite.


// concrete decorators
class MilkDecorator extends CoffeeDecorator{
public MilkDecorator(final Coffee coffee){
super(coffee);
}

@Override
public double getPrice(){
return super.getPrice()+1d;
}

@Override
public int makeMoreCandied(){
return super.makeMoreCandied()+1;
}
}

class SugarDecorator extends CoffeeDecorator{
public SugarDecorator(final Coffee coffee){
super(coffee);
}

@Override
public double getPrice(){
return super.getPrice()+3d;
}

@Override
public int makeMoreCandied(){
return super.makeMoreCandied()+1;
}
}

class ChocolateDecorator extends CoffeeDecorator{
public ChocolateDecorator(final Coffee coffee){
super(coffee);
}

@Override
public double getPrice(){
return super.getPrice()+5d;
}

@Override
public int makeMoreCandied(){
return super.makeMoreCandied()+1;
}
}

class BlackChocolateDecorator extends CoffeeDecorator{
public BlackChocolateDecorator(final Coffee coffee){
super(coffee);
}

@Override
public double getPrice(){
return super.getPrice()+5d;
}

@Override
public int makeMoreCandied(){
return super.makeMoreCandied()-3;
}
}

Ces décorateurs concrets sont considérés comme les ingrédients. Chaque ingrédient a un prix qui influencera le prix final du café produit. On le constatera sur l'exemple d'implémentation.


import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class DecoratorSampleTest{
@Test
public void test(){
final Coffee completeCoffee=new ChocolateDecorator(new SugarDecorator(new MilkDecorator(new BlackCoffee())));
System.out.println("Candied level is : "+completeCoffee.makeMoreCandied());
assertEquals(completeCoffee.getPrice(), 11d, 0d);
final Coffee sugarCoffee=new SugarDecorator(new BlackCoffee());
assertEquals(sugarCoffee.getPrice(), 5d, 0d);
final Coffee sugarMilkCoffee=new MilkDecorator(new SugarDecorator(new BlackCoffee()));
assertEquals(sugarMilkCoffee.getPrice(), 6d, 0d);
final Coffee sugarBlackChocCoffee=new MilkDecorator(new SugarDecorator(new BlackChocolateDecorator(new BlackCoffee())));
assertEquals(sugarBlackChocCoffee.makeMoreCandied(), -1);
}
}

Les cafés finaux sont représentés par l'imbrication des "ingrédients". Grâce à cela, chaque nouvel objet implémentant l'interface Coffee appelle la méthode du décorateur qui lui est passé en paramètre dans le constructeur.

On observe que grâce à la puissance des décorateurs, on peut créer les imbrications à l'infini. Ce ne sera pas très lisible, mais sûrement sera plus propre que la création des classes ChocolateSugarCoffee, ChocolateMilkCoffee, ChocolateBlackChocolateCoffee etc.
Bartosz KONIECZNY 06-10-2013 16:19 design patterns
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony2

A quoi peut être lié le problème "Unknown Entity namespace alias" ?

Ce problème peut apparaître dans Symfony2 parce que le bundle de l'entité peut ne pas être défini dans AppKernel.