web-dev-qa-db-fra.com

Comprendre le "motif de décorateur" avec un exemple du monde réel

J'étudiais le motif de décorateur décrit dans GOF .

Aidez-moi à comprendre le motif de décorateur . Quelqu'un pourrait-il donner un exemple d'utilisation où cela est utile dans le monde réel?

160
odiseh

Le motif Decorator atteint un seul objectif: ajouter dynamiquement des responsabilités à un objet.

Prenons l'exemple d'une pizzeria. Dans la pizzeria, ils vendront quelques variétés de pizzas et fourniront également des garnitures au menu. Imaginons maintenant une situation dans laquelle, si la pizzeria doit fournir des prix pour chaque combinaison de pizza et de garniture. Même s'il y a quatre pizzas de base et 8 garnitures différentes, l'application deviendra folle en maintenant toutes ces combinaisons concrètes de pizzas et de garnitures.

Voici le motif de décorateur.

Selon le modèle de décorateur, vous utiliserez des garnitures comme décorateurs et les pizzas seront décorées par les décorateurs de ces garnitures. Pratiquement chaque client voudrait des garnitures de son désir et le montant final de la facture sera composé des pizzas de base et des garnitures commandées en plus. Chaque décorateur en tête connaîtrait les pizzas qu’il décore et son prix. La méthode GetPrice () de l'objet Topping renvoie le prix cumulatif de la pizza et de la garniture.

MODIFIER

Voici un exemple de code d'explication ci-dessus.

public abstract class BasePizza
{
    protected double myPrice;

    public virtual double GetPrice()
    {
        return this.myPrice;
    }
}

public abstract class ToppingsDecorator : BasePizza
{
    protected BasePizza pizza;
    public ToppingsDecorator(BasePizza pizzaToDecorate)
    {
        this.pizza = pizzaToDecorate;
    }

    public override double GetPrice()
    {
        return (this.pizza.GetPrice() + this.myPrice);
    }
}

class Program
{
    [STAThread]
    static void Main()
    {
        //Client-code
        Margherita pizza = new Margherita();
        Console.WriteLine("Plain Margherita: " + pizza.GetPrice().ToString());

        ExtraCheeseTopping moreCheese = new ExtraCheeseTopping(pizza);
        ExtraCheeseTopping someMoreCheese = new ExtraCheeseTopping(moreCheese);
        Console.WriteLine("Plain Margherita with double extra cheese: " + someMoreCheese.GetPrice().ToString());

        MushroomTopping moreMushroom = new MushroomTopping(someMoreCheese);
        Console.WriteLine("Plain Margherita with double extra cheese with mushroom: " + moreMushroom.GetPrice().ToString());

        JalapenoTopping moreJalapeno = new JalapenoTopping(moreMushroom);
        Console.WriteLine("Plain Margherita with double extra cheese with mushroom with Jalapeno: " + moreJalapeno.GetPrice().ToString());

        Console.ReadLine();
    }
}

public class Margherita : BasePizza
{
    public Margherita()
    {
        this.myPrice = 6.99;
    }
}

public class Gourmet : BasePizza
{
    public Gourmet()
    {
        this.myPrice = 7.49;
    }
}

public class ExtraCheeseTopping : ToppingsDecorator
{
    public ExtraCheeseTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 0.99;
    }
}

public class MushroomTopping : ToppingsDecorator
{
    public MushroomTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 1.49;
    }
}

public class JalapenoTopping : ToppingsDecorator
{
    public JalapenoTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 1.49;
    }
}
221

Il s'agit d'un exemple simple d'ajout dynamique d'un nouveau comportement à un objet existant ou au motif Decorator. En raison de la nature des langages dynamiques tels que Javascript, ce modèle devient partie intégrante du langage lui-même.

// Person object that we will be decorating with logging capability
var person = {
  name: "Foo",
  city: "Bar"
};

// Function that serves as a decorator and dynamically adds the log method to a given object
function MakeLoggable(object) {
  object.log = function(property) {
    console.log(this[property]);
  }
}

// Person is given the dynamic responsibility here
MakeLoggable(person);

// Using the newly added functionality
person.log('name');
31
Anurag

Il est à noter que le modèle Java i/o est basé sur le motif de décorateur. La superposition de ce lecteur au-dessus de ce lecteur au-dessus de ... est un exemple vraiment réel de décorateur .

17
frankc

Exemple - Scénario - Supposons que vous écriviez un module de chiffrement. Ce chiffrement peut chiffrer le fichier effacé en utilisant DES - Norme de chiffrement des données. De même, dans un système, vous pouvez utiliser le chiffrement en tant que norme de chiffrement AES - Advance. Vous pouvez également combiner le chiffrement - D'abord, puis AES, ou vous pouvez avoir d'abord AES, puis DES.

Discussion - Comment allez-vous gérer cette situation? Vous ne pouvez pas continuer à créer l'objet de telles combinaisons - par exemple, AES et DES - total de 4 combinaisons. Vous devez donc disposer de 4 objets individuels. Cela deviendra complexe à mesure que le type de cryptage augmentera. .

Solution - Continuez à construire la pile - des combinaisons en fonction des besoins - au moment de l'exécution. Un autre avantage de cette approche de pile est que vous pouvez la dérouler facilement.

Voici la solution - en C++.

Tout d'abord, vous avez besoin d'une classe de base - une unité fondamentale de la pile. Vous pouvez penser comme la base de la pile. Dans cet exemple, c'est un fichier en clair. Suivons toujours le polymorphisme. Faites d’abord une classe d’interface de cette unité fondamentale. De cette façon, vous pouvez le mettre en œuvre à votre guise. De plus, vous n'avez pas besoin de penser à la dépendance en incluant cette unité fondamentale.

Voici la classe d'interface -

class IclearData
{
public:

    virtual std::string getData() = 0;
    virtual ~IclearData() = 0;
};

IclearData::~IclearData()
{
    std::cout<<"Destructor called of IclearData"<<std::endl;
}

Maintenant, implémentez cette classe d'interface -

class clearData:public IclearData
{
private:

    std::string m_data;

    clearData();

    void setData(std::string data)
        {
            m_data = data;
        }

public:

    std::string getData()
    {
        return m_data;
    }

    clearData(std::string data)
    {
        setData(data);
    }

    ~clearData()
    {
        std::cout<<"Destructor of clear Data Invoked"<<std::endl;
    }

};

Faisons maintenant une classe abstraite de décorateur - qui peut être étendue pour créer tout type de saveur - ici la saveur est le type de cryptage. Cette classe abstraite de décorateur est liée à la classe de base. Ainsi, le décorateur "est un" type de classe d'interface. Ainsi, vous devez utiliser l'héritage.

class encryptionDecorator: public IclearData
{

protected:
    IclearData *p_mclearData;

    encryptionDecorator()
    {
      std::cout<<"Encryption Decorator Abstract class called"<<std::endl;
    }

public:

    std::string getData()
    {
        return p_mclearData->getData();
    }

    encryptionDecorator(IclearData *clearData)
    {
        p_mclearData = clearData;
    }

    virtual std::string showDecryptedData() = 0;

    virtual ~encryptionDecorator() = 0;

};

encryptionDecorator::~encryptionDecorator()
{
    std::cout<<"Encryption Decorator Destructor called"<<std::endl;
}

Faisons maintenant une classe de décorateurs concrets - Type de cryptage - AES -

const std::string aesEncrypt = "AES Encrypted ";

class aes: public encryptionDecorator
{

private:

    std::string m_aesData;

    aes();

public:

    aes(IclearData *pClearData): m_aesData(aesEncrypt)
    {
        p_mclearData = pClearData;
        m_aesData.append(p_mclearData->getData());
    }

    std::string getData()
        {
            return m_aesData;
        }

    std::string showDecryptedData(void)
    {
        m_aesData.erase(0,m_aesData.length());
        return m_aesData;
    }

};

Supposons maintenant que le type de décorateur est DES -

const std :: string desEncrypt = "DES crypté";

class des: public encryptionDecorator
{

private:

    std::string m_desData;

    des();

public:

    des(IclearData *pClearData): m_desData(desEncrypt)
    {
        p_mclearData = pClearData;
        m_desData.append(p_mclearData->getData());
    }

    std::string getData(void)
        {
            return m_desData;
        }

    std::string showDecryptedData(void)
    {
        m_desData.erase(0,desEncrypt.length());
        return m_desData;
    }

};

Faisons un code client pour utiliser cette classe de décorateur -

int main()
{
    IclearData *pData = new clearData("HELLO_CLEAR_DATA");

    std::cout<<pData->getData()<<std::endl;


    encryptionDecorator *pAesData = new aes(pData);

    std::cout<<pAesData->getData()<<std::endl;

    encryptionDecorator *pDesData = new des(pAesData);

    std::cout<<pDesData->getData()<<std::endl;

    /** unwind the decorator stack ***/
    std::cout<<pDesData->showDecryptedData()<<std::endl;

    delete pDesData;
    delete pAesData;
    delete pData;

    return 0;
}

Vous verrez les résultats suivants -

HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
DES Encrypted AES Encrypted HELLO_CLEAR_DATA
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Destructor called
Destructor called of IclearData
Encryption Decorator Destructor called
Destructor called of IclearData
Destructor of clear Data Invoked
Destructor called of IclearData

Voici le diagramme UML - Représentation de classe de celui-ci. Dans ce cas, vous souhaitez ignorer le code et vous concentrer sur l'aspect de la conception.

enter image description here

7
dexterous_stranger

Le motif Decorator vous aide à modifier ou à configurer une fonctionnalité de votre objet en le chaînant avec d'autres sous-classes similaires de cet objet.

Le meilleur exemple serait les classes InputStream et OutputStream dans le package Java.io

    File file=new File("target","test.txt");
    FileOutputStream fos=new FileOutputStream(file);
    BufferedOutputStream bos=new BufferedOutputStream(fos);
    ObjectOutputStream oos=new ObjectOutputStream(bos);


    oos.write(5);
    oos.writeBoolean(true);
    oos.writeBytes("decorator pattern was here.");


//... then close the streams of course.
4
huseyin

J'ai beaucoup utilisé le motif Decorator dans mon travail. J'ai fait un post sur mon blog sur la façon de l'utiliser avec la journalisation.

3
Ismael

Qu'est-ce que le motif de conception de décorateur en Java?.

La définition formelle du motif Decorator du livre GoF (Modèles de conception: éléments d'un logiciel orienté objet réutilisable, 1995, Pearson Education, Inc., publié sous le nom Pearson Addison Wesley) indique que vous pouvez,

"Attachez de manière dynamique des responsabilités supplémentaires à un objet. Les décorateurs offrent une alternative flexible aux sous-classes pour étendre les fonctionnalités."

Disons que nous avons une pizza et que nous voulons la décorer avec des garnitures telles que du poulet Masala, de l'oignon et du fromage Mozzarella. Voyons comment l'implémenter dans Java ...

Programme pour montrer comment implémenter un modèle de conception de décorateur en Java.

Pizza.Java:

<!-- language-all: lang-html -->

package com.hubberspot.designpattern.structural.decorator;

public class Pizza {

public Pizza() {

}

public String description(){
    return "Pizza";
}

}



package com.hubberspot.designpattern.structural.decorator;

public abstract class PizzaToppings extends Pizza {

public abstract String description();

}

package com.hubberspot.designpattern.structural.decorator;

public class ChickenMasala extends PizzaToppings {

private Pizza pizza;

public ChickenMasala(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + " with chicken masala, ";
}

}



package com.hubberspot.designpattern.structural.decorator;

public class MozzarellaCheese extends PizzaToppings {

private Pizza pizza;

public MozzarellaCheese(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + "and mozzarella cheese.";
}
}



package com.hubberspot.designpattern.structural.decorator;

public class Onion extends PizzaToppings {

private Pizza pizza;

public Onion(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + "onions, ";
}

}



package com.hubberspot.designpattern.structural.decorator;

public class TestDecorator {

public static void main(String[] args) {

    Pizza pizza = new Pizza();

    pizza = new ChickenMasala(pizza);
    pizza = new Onion(pizza);
    pizza = new MozzarellaCheese(pizza);

    System.out.println("You're getting " + pizza.description());

}

}
3
Jonty

Décorateur:

  1. Ajoute un comportement à un objet lors de l'exécution. L'héritage est la clé pour réaliser cette fonctionnalité, ce qui constitue à la fois un avantage et un inconvénient de ce modèle.
  2. Il améliore le comportement de l'interface.
  3. Decorator peut être considéré comme un dégénéré Composite avec un seul composant. Cependant, un décorateur ajoute des responsabilités supplémentaires - il n'est pas destiné à l'agrégation d'objets.
  4. La classe Decorator déclare une relation de composition à l'interface LCD (plus petit dénominateur de classe)) et ce membre de données est initialisé dans son constructeur.
  5. Decorator est conçu pour vous permettre d'ajouter des responsabilités aux objets sans sous-classement

Reportez-vous à l'article création de source pour plus de détails.

Decorator (Abstract): il s'agit d'une classe/interface abstraite, qui implémente l'interface du composant. Il contient une interface de composant. En l'absence de cette classe, vous avez besoin de nombreuses sous-classes de ConcreteDecorators pour différentes combinaisons. La composition du composant réduit les sous-classes inutiles.

Exemple JDK:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("a.txt")));
while(bis.available()>0)
{
        char c = (char)bis.read();
        System.out.println("Char: "+c);;
}

Consultez ci-dessous la question SE pour des exemples de diagrammes et de codes UML.

motif de décorateur pour IO

Articles utiles:

journal

wikipedia

Exemple avec un vrai mot du motif Decorator: VendingMachineDecorator a été expliqué @

Quand utiliser le motif de décorateur?

Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
beverage.decorateBeverage();

beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
beverage.decorateBeverage();

Dans l'exemple ci-dessus, le thé ou le café (boisson) a été décoré avec du sucre et du citron.

2
Ravindra babu

Le motif de décorateur vous permet d'ajouter dynamiquement un comportement aux objets.

Prenons un exemple où vous devez créer une application qui calcule le prix de différents types de hamburgers. Vous devez gérer différentes variations de hamburgers, tels que "gros" ou "avec du fromage", dont chacun a un prix par rapport au hamburger de base. Par exemple. ajoutez 10 $ pour un hamburger avec du fromage, ajoutez 15 $ de plus pour un gros hamburger, etc.

Dans ce cas, vous pourriez être tenté de créer des sous-classes pour les gérer. Nous pourrions exprimer cela en Ruby comme:

class Burger
  def price
    50
  end
end

class BurgerWithCheese < Burger
  def price
    super + 15
  end
end

Dans l'exemple ci-dessus, la classe BurgerWithCheese hérite de Burger et remplace la méthode price pour ajouter 15 € au prix défini dans la super classe. Vous pouvez également créer une classe LargeBurger et définir le prix relatif à Burger. Mais vous devez également définir une nouvelle classe pour la combinaison "gros" et "avec fromage".

Maintenant que se passe-t-il si nous devons servir "un hamburger avec des frites"? Nous avons déjà 4 classes pour gérer ces combinaisons, et nous aurons besoin d'en ajouter 4 pour gérer toutes les combinaisons des 3 propriétés - "grand", "avec fromage" et "avec frites". Nous avons besoin de 8 cours maintenant. Ajoutez une autre propriété et nous aurons besoin de 16. Cela augmentera à partir de 2 ^ n.

Essayons plutôt de définir un BurgerDecorator qui prend un objet Burger:

class BurgerDecorator
  def initialize(burger)
    self.burger = burger
  end
end

class BurgerWithCheese < BurgerDecorator
  def price
    self.burger.price + 15
  end
end

burger = Burger.new
cheese_burger = BurgerWithCheese.new(burger)
cheese_burger.price   # => 65

Dans l'exemple ci-dessus, nous avons créé une classe BurgerDecorator, à partir de laquelle la classe BurgerWithCheese hérite. Nous pouvons également représenter la "grande" variation en créant la classe LargeBurger. Maintenant, nous pourrions définir un gros burger avec du fromage au moment de l'exécution comme:

b = LargeBurger.new(cheese_burger)
b.price  # => 50 + 15 + 20 = 85

N'oubliez pas que l'utilisation de l'héritage pour ajouter la variante "avec des frites" impliquerait l'ajout de 4 autres sous-classes? Avec les décorateurs, nous créerions simplement une nouvelle classe, BurgerWithFries, pour gérer la nouvelle variante et la gérer au moment de l'exécution. Chaque nouvelle propriété aurait besoin d'un peu plus de décorateur pour couvrir toutes les permutations.

PS Ceci est la version courte d'un article sur lequel j'ai écrit en utilisant le motif de décorateur en Ruby , que vous pouvez lire si vous souhaitez en savoir plus sur des exemples plus détaillés.

2
Nithin

Le motif Decorator réalise un seul objectif consistant à ajouter de manière dynamique des responsabilités à un objet .

Le modèle Java I/O est basé sur un modèle de décorateur.

Java IO as decorator pattern

2
ChandraBhan Singh

Il existe un exemple sur Wikipedia sur la décoration d'une fenêtre avec une barre de défilement:

http://en.wikipedia.org/wiki/Decorator_pattern

Voici un autre exemple très concret de "membre d’équipe, chef d’équipe et gestionnaire", qui illustre le fait que le motif de décorateur est irremplaçable avec un héritage simple:

https://zishanbilal.wordpress.com/2011/04/28/design-patterns-by-examples-decorator-pattern/

1
gm2008

Modèle de conception de décorateur : Ce modèle permet de modifier les caractéristiques d'un objet au moment de l'exécution. Il fournit différentes saveurs à un objet et permet de choisir quels ingrédients nous voulons utiliser dans cette saveur.

Exemple concret: Disons que vous avez un siège de cabine principal dans un vol. Maintenant, vous êtes autorisé à choisir plusieurs équipements avec le siège. Chaque équipement a son propre coût associé. Désormais, si un utilisateur choisit le Wi-Fi et les aliments de qualité supérieure, il sera facturé pour le siège + le wifi + les aliments de qualité supérieure.

enter image description here

Dans ce cas, le modèle de conception de décorateur peut vraiment nous aider. Visitez le lien ci-dessus pour en savoir plus sur le motif de décorateur et la mise en œuvre d'un exemple concret.

0
Ajit Singh

Prenons l'exemple de PubG. Les fusils d’assaut fonctionnent mieux avec un zoom 4x et pendant que nous y sommes, nous aurions également besoin d’un compensateur et d’un suppresseur. Cela réduira le recul et réduira le bruit de tir ainsi que l'écho. Nous devrons mettre en œuvre cette fonctionnalité pour permettre aux joueurs d’acheter leur arme préférée et leurs accessoires. Les joueurs peuvent acheter le pistolet ou une partie de l'accessoire ou tout l'accessoire et ils seront facturés en conséquence.

Voyons comment le motif de décorateur est appliqué ici:

Supposons que quelqu'un veuille acheter SCAR-L avec les trois accessoires mentionnés ci-dessus.

  1. Prenez un objet de SCAR-L
  2. Décorer (ou ajouter) l'objet SCAR-L avec zoom 4x
  3. Décorer le SCAR-L avec un objet suppresseur
  4. Décorer le SCAR-L avec un objet compresseur
  5. Appelez la méthode du coût et laissez chaque objet déléguer ajouter le coût en utilisant la méthode du coût des accessoires

Cela conduira à un diagramme de classes comme celui-ci:

Decorator pattern at work

Maintenant, nous pouvons avoir des cours comme celui-ci:

public abstract class Gun {     
    private Double cost;    
    public Double getCost() {           
        return cost;        
       }    
    }

public abstract class GunAccessories extends Gun {  }

public class Scarl extends Gun {    
    public Scarl() {            
        cost = 100;
        }   
     }

public class Suppressor extends GunAccessories {        
    Gun gun;        
    public Suppressor(Gun gun) {            
    cost = 5;           
    this.gun = gun;     
    }               
    public double getCost(){            
        return cost + gun.getCost();
    }
}

public class GunShop{   
    public static void main(String args[]){         
    Gun scarl = new Scarl();                
    scarl = new Supressor(scarl);
    System.out.println("Price is "+scarl.getCost());
    }      
}

Nous pouvons également ajouter d'autres accessoires et décorer notre arme à feu.

Référence:

https://nulpointerexception.com/2019/05/05/a-beginner-guide-to-decorator-pattern/

0
H-V

Il y a quelque temps, j'avais modifié une base de code en utilisant le motif Decorator. Je vais donc essayer d'expliquer le cas d'utilisation.

Supposons que nous ayons un ensemble de services et que l’utilisateur ait ou non acquis la licence d’un service particulier, nous devons le démarrer.

Tous les services ont une interface commune

interface Service {
  String serviceId();
  void init() throws Exception;
  void start() throws Exception;
  void stop() throws Exception;
}

Pre Refactoring

abstract class ServiceSupport implements Service {
  public ServiceSupport(String serviceId, LicenseManager licenseManager) {
    // assign instance variables
  }

  @Override
  public void init() throws Exception {
    if (!licenseManager.isLicenseValid(serviceId)) {
       throw new Exception("License not valid for service");
    }
    // Service initialization logic
  }
}

Si vous observez attentivement, ServiceSupport dépend de LicenseManager. Mais pourquoi devrait-il dépendre de LicenseManager? Et si nous avions besoin d'un service d'arrière-plan qui n'a pas besoin de vérifier les informations de licence. Dans la situation actuelle, nous devrons en quelque sorte former LicenseManager pour retourner true aux services d'arrière-plan. Cette approche ne me semblait pas bien. Selon moi, le contrôle de licence et une autre logique étaient orthogonaux l'un à l'autre.

Alors motif de décorateur vient à la rescousse et commence ici le refactoring avec TDD.

Post-refactoring

class LicensedService implements Service {
  private Service service;
  public LicensedService(LicenseManager licenseManager, Service service) {
    this.service = service;
  }

  @Override
  public void init() {
    if (!licenseManager.isLicenseValid(service.serviceId())) {
      throw new Exception("License is invalid for service " + service.serviceId());
    }
    // Delegate init to decorated service
    service.init();
  }

  // override other methods according to requirement
}

// Not concerned with licensing any more :)
abstract class ServiceSupport implements Service {
  public ServiceSupport(String serviceId) {
    // assign variables
  }

  @Override
  public void init() {
    // Service initialization logic
  }
}

// The services which need license protection can be decorated with a Licensed service
Service aLicensedService = new LicensedService(new Service1("Service1"), licenseManager);
// Services which don't need license can be created without one and there is no need to pass license related information
Service aBackgroundService = new BackgroundService1("BG-1");

À emporter

  • La cohésion du code s'est améliorée
  • Les tests unitaires sont devenus plus faciles, car vous n'avez pas à vous moquer de licence pour tester ServiceSupport
  • Pas besoin de contourner les licences par des vérifications spéciales pour les services d'arrière-plan
  • Bonne répartition des responsabilités
0
Narendra Pathai