Read headFirst Design Patterns - Decorator Patterns

Keywords: Java JDK less

Inheritance can extend the function of the parent class by reusing the parent code, but at the same time it increases the coupling between objects, so inheritance should be used cautiously. So is there a way to extend the functions of parent classes and decouple objects? The answer is yes. That's the decorator model we're going to learn today. You will see later that I will assemble a computer in Decorator mode. But let's start with the examples in the book.

 

Examples from Learning Books

Starbuzz's system needs to be updated. Their original system is as follows:

 

As you can see, customers buy beverages with specific subcategories offered and returned to the beverage price. When buying coffee, you can add spices such as Steamed Milk, Soy, Mocha or covered with milk. Starbuzz charges different fees based on the ingredients added. So how does this work? Maybe we will think of several solutions like this:

1. List the combinations of all beverages and condiments. Well, I don't think anybody will do that. There are too many combinations. In one way, it's called "class explosion" in the book.

2. Set the boolean value of various condiments in the Beverage class to indicate whether the condiments, such as boolean milk, are needed. Then calculate the price after adding various condiments with cost. Then call the cost method of the parent class in the cost method of the subclass and add the price of the beverage itself.

Analysis of the second situation: Sounds good, but once you add a new condiment, you have to modify the Beverage class. If a new type of beverage is developed, some of the condiments may not be appropriate, which leads to the beverage having the method of adding the inappropriate condiments. What are the consequences, which may have some adverse consequences, we are in the process of developing a new type of beverage. Strategy mode The lesson is learned in one chapter (rubber ducks can fly). And what if I want to double Mocha?

 

Trying to solve problems

Now that the problem has arisen, how can we solve it? The natural state of people buying coffee may be that they buy a cup of coffee first, then whatever seasoning they want, and as much as they want. So we wondered if this would solve the problem: first create a cup of coffee, then create condiments and combine them dynamically with coffee. By dynamically combining objects, new code can be written to add new functions without modifying existing code. Since the existing code has not been changed, the chances of introducing bug s or generating unexpected side effects will be greatly reduced. This requires the use of decorator mode.

 

Principles of Software Design

Open-Close Principle: Classes should be open to extensions and closed to modifications.

 

Define Decorator Patterns

Dynamic attachment of responsibility to the object. To extend functionality, decorators offer more flexible alternatives than inheritance.

 

Make Starbuzz beverages fit the decorator model

It needs to be explained that the Beverage class is an abstract class of beverages, from which all beverages are inherited. It has a getDescription() method and an abstract cost() method for calculating prices. Four specific coffee classes, such as Espresso, inherit the Beverage class and rewrite the cost method. Condiment Decorator class is an abstract decoration class. It also inherits Beverage, Milk and other decoration classes. When calculating the price of the beverage, the price of the condiment is added to the price of the beverage. When describing the beverage, the description of the condiment is added to the description of the beverage. So the decorator adds the behavior to the object of the decorated person.

As mentioned earlier, inheritance should be used with caution. Decorator pattern adds new functions through dynamic composite objects. Why does CondimentDecorator class inherit Beverage class here? In fact, inheritance is not used here for inheritance behavior, but for type matching. That is to say, the decorator type can be used to replace the decorator type when it needs to be decorated. This may not be very clear. Let me give you an example of Condiment Decorator not inheriting Beverage: If a customer orders a espresso and needs to add milk Milk and Mocha, we need to create a glass of Espresso to get espresso object, and then pass the espresso object as a parameter to create Milk object, Condiment Decorator milk = new Milk (espresso). So you add milk to espresso, but you need to add mocha, so you can't add milk and mocha at the same time, so Condiment Decorator inherits Beverage to keep type matching.

 

Start-up

First, you need Abstract drinks and concrete drinks.

Beverage:

/**
 * Abstract beverages
 */
public abstract class Beverage {
    protected String description = "Unknow Beverage";
    //Description of drinks
    public String getDescription() {
        return description;
    }
    
    //Calculated price
    public abstract double cost();
}

 

Espresso:

/**
 * Espresso
 */
public class Espresso extends Beverage {
    public Espresso() {
        description = "Espresso";
    }
    
    @Override
    public double cost() {
        return 1.99;
    }
}

Specific drinks only have their own description and price. For other specific drinks, see Appendix A below.

Then add the abstract decorator and the specific decorator. The specific decorator attaches his price and description to the price and description of the beverage.

 

Condiment Decorator:

/**
 * Condiment decorators, specific condiments must add their own description
 */
public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}

 

Specific condiment decorator Mocha:

/**
 * Mocha, Inherited Self-condiment Decorator
 */
public class Mocha extends CondimentDecorator {
    private Beverage beverage;
    
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }

    @Override
    public double cost() {
        return 0.20 + beverage.cost();
    }
}

See Appendix B below for other specific condiments.

In fact, you can see that every specific condiment class needs the reference of Beverage object. Since this can put the reference of Beverage object into Condiment Decorator class, you can adjust it yourself, I will not adjust it here. I'll put references to decorated classes into abstract decorated classes in the example of assembling computers later.

 

Test Decorator Test:

/**
 * What the decorator should do is to add behavior to the object being packaged.
 */
public class DecoratorTest {
    public static void main(String[] args) throws Exception {
        Beverage darkRoast = new DarkRoast();
        System.out.println(darkRoast.getDescription() + "\t" + darkRoast.cost());
        System.out.println("---------------------");
        
        Beverage espresso = new Espresso();
        espresso = new Mocha(espresso);
        espresso = new Mocha(espresso);
        espresso = new Soy(espresso);
        System.out.println(espresso.getDescription() + "\t" + espresso.cost());
    }
}

 

What should the decorator do?

Through examples, we can see that what the decorator should do is to increase the behavior of the decorator to the object being packaged.

 

Decorators in jdk:

Decorator mode is also used in jdk, which is used in IO stream. I think everyone has a hard time learning IO streams. Maybe it's not only to distinguish between byte streams and character streams, but also to try to confuse our view with some decorative streams. For example, Buffered InputStream is a decoration stream, which can be used to decorate FileInputStream, so the most commonly used form should be new Buffered InputStream (new File InputStream). Similar to what we said above, decoration stream also adds some behavior to the decorated object, such as Buffered InputStream improves performance by buffering arrays. Provides a readLine method to read the entire line for extension. The following illustration will give you a better understanding of the decorators in the IO stream:

This figure lists byte streams, and character streams are similar. But decorative streams make more classes in IO, which sometimes bothers us. If we have to say that, it is also a "shortcoming";

 

Write your own examples

After learning the examples in books, I always want to give myself an example, but it's really difficult to get a suitable example, that is, "when books are used, they hate less"? Haha, let's leave it alone. Let's take a look at my own example: I'm going to assemble a computer. Now there's only one case and I need to add other accessories. Here's an example.

 

Manual assembly of computers

First of all, we need an abstract Computer class, which has two attributes: model type and price price price, and two abstract methods to obtain component part incle () and calculate total price ():

/**
 * Computer Abstract class, decorated class abstract class
 */
public abstract class Computer {
    private String type; //Model
    private Double price; //Price
    
    public abstract String comprise();    //Component
    public abstract Double prices();     //Total price
    
    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }
}

 

Suppose there is only one first horse's chassis as the decorated SAMAChassis:

/**
 * A concrete first horse chassis, decorated class
 */
public class SAMAChassis extends Computer {
    public SAMAChassis() {
        setType("Sama chassis");
        setPrice(136.0);
    }

    @Override
    public Double prices() {
        return getPrice();
    }

    @Override
    public String comprise() {
        return getType();
    }
}

 

Now we need to add CPU, motherboard, memory and other accessories to the net chassis, and decorate the chassis with these accessories as decorators. An Abstract class, DiyDecorator, that requires a decorator:

/**
 * Abstract decoration class, diy accessories, all kinds of accessories have models and prices, so they are treated in the abstract parent class, other attributes are ignored for the time being.
 */
public abstract class DiyDecorator extends Computer {
    private Computer computer;
    
    public String comprise() {
        //There this Represents a specific subclass of decorators, optional
        return computer.comprise() + "---" + this.getType();
    }
    
    public Double prices() {
        return computer.prices() + this.getPrice();
    }

    public DiyDecorator(Computer computer) {
        this.computer = computer;
    }
    
    public DiyDecorator(Computer computer, String type, Double price) {
        this.computer = computer;
        this.setType(type);
        this.setPrice(price);
    }
}

 

Designed to address one of the issues mentioned above, I put a reference to Computer into the abstract class DiyDecorator to increase code reuse. At the same time, the include method and the price method are also implemented, so that the subclass only needs to call the construction method of the parent class.

At this point, another question comes to mind. We know that we can't create objects of abstract classes. So why do abstract classes have constructive methods? In fact, as can be seen from this example, the construction method of abstract classes is used to instantiate member variables.

 

Specific decorative CPU:

/**
 * Specific decoration class: CPU class
 */
public class CPU extends DiyDecorator {
    public CPU(Computer computer, String type, Double price) {
        super(computer, type, price);
    }
    
    public CPU(Computer computer) {
        super(computer);
    }
}

I won't write any other specific decoration classes or appendices. It's very simple.

 

Test Decorator Test:

/**
 * Now let's assemble a computer, starting with one case and nothing else.
 */
public class DecoratorTest {
    public static void main(String[] args) {
        run1();
        System.out.println("------------------");
        run2();
    }

    private static void run1() {
        Computer computer = new SAMAChassis();
        computer.setType("Jinhetian Foresight");
        computer.setPrice(215.0);
        computer = new CPU(computer, "CoRE i5 4590", 999.5);
        System.out.println(computer.comprise() + "\t The total price is:" + computer.prices());
    }

    private static void run2() {
        SAMAChassis chassis = new SAMAChassis();
        CPU cpu = new CPU(chassis);
        cpu.setType("CoRE i5");
        cpu.setPrice(999.0);
        Mainboard mainboard = new Mainboard(cpu);
        mainboard.setType("Gigabyte B150");
        mainboard.setPrice(636.5);
        Memory memory = new Memory(mainboard, "Kingston 8 g", 412.5);
        Power computer = new Power(memory, "Hang Jia 500 w", 435.0);
        System.out.println(computer.comprise() + "\t The total price is:" + computer.prices());
    }
}

 

Summary

Decorator pattern dynamically attaches responsibility to the object. To extend functionality, decorators offer more flexible alternatives than inheritance. The decorator model conforms to the Open-Close Principle: Open to Extension and Close to Modification.

 

 

Appendix A

Dark Roast:

/**
 * Deep baked coffee
 */
public class DarkRoast extends Beverage {
    public DarkRoast() {
        description = "DarkRoast";
    }

    @Override
    public double cost() {
        return 0.99;
    }
}

 

Decaf:

/**
 * Decaf
 */
public class Decaf extends Beverage {
    public Decaf() {
        description = "Decaf";
    }

    @Override
    public double cost() {
        return 1.05;
    }
}

 

Coffee HouseBlend:

/**
 * Comprehensive coffee
 */
public class HouseBlend extends Beverage {
    public HouseBlend() {
        description = "HouseBlend";
    }

    @Override
    public double cost() {
        return 0.89;
    }
}

 

 

Appendix B

Soybean milk Soy:

/**
 * Soybean Milk
 */
public class Soy extends CondimentDecorator {
    private Beverage beverage;
    
    public Soy(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Soy";
    }

    @Override
    public double cost() {
        return 0.15 + beverage.cost();
    }
}

 

Whip Bubble:

/**
 * Specific decorator, foam
 */
public class Whip extends CondimentDecorator {
    private Beverage beverage;
    
    public Whip(Beverage beverage) {
        this.beverage = beverage;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Whip";
    }

    @Override
    public double cost() {
        return 0.10 + beverage.cost();
    }
}

Posted by Robban on Sun, 21 Apr 2019 19:03:34 -0700