This is the third day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021
Put forward the demand
Below is the price list of a Coffee shop. Customers can freely combine different coffees and add any ingredients. Now we need to design the order system of the Coffee shop.
Coffee | Price |
---|---|
HouseBlend | 0.89 |
DarkRoast | 0.99 |
Decaf | 1.05 |
Espresso | 1.99 |
Condiment | Price |
---|---|
Milk | 0.1 |
Mocha | 0.2 |
Soy | 0.15 |
Whip | 0.1 |
We first thought of the factory class we used before, but the difference is that the core need is not to get a large number of objects, but how to design the order system, that is, to get different results according to the user needs. It was up to us to explore the patterns ourselves, and we came up with the idea of using inheritance
abstract class Beverage { getDescription(); cost(); // } class HouseBlendWithMilkandMocha extends Beverage { cost(); } class HouseBlendWithSoyandMochaandWhip extends Beverage { cost(); } / /Copy the code
By this design, we need to implement 64 classes, but if you allow customers to choose from Double Mocha or Coffee shop has a new product, the increase in subclasses is terrible
Decorator pattern
In the order system in daily use, the shop assistant will add coffee to the price according to the need, and then add the coffee to the previous price according to the ingredients selected by the customer each time, which is similar to the decorator mode.
To put it simply, we need a basic class, and then modify it in this basic class. DoubleMocha and Whip HouseBlend coffee can be expressed as follows
We can see the main idea of decorator mode:By dynamically adding additional responsibilities to an object, the decorator pattern is more flexible in terms of adding functionality to the object than a subclass implementation.
Implement with decorator pattern
We first define an abstract class Beverage, all coffee are subclasses of this class, as the interface for all coffee type matching.
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
Copy the code
Then we implement coffee, which only needs to specify its description and price in terms of the abstract class
public class HouseBlend extends Beverage { HouseBlend() { description = "House Blend Coffee"; } @Override public double cost() { return .89; } } public class Espresso extends Beverage { Espresso() { description = "Espresso"; } @override public double cost() {return 1.99; } } public class Decaf extends Beverage { Decaf() { description = "Decaf"; } @override public double cost() {return 1.05; }} / /...Copy the code
We then implement the interface to the ingredients, whose purpose is simply to constrain the methods of the ingredients class
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();
}
Copy the code
The concrete classes of the ingredients, which are concrete decorators, are implemented below, adding further descriptions to the object and modifying the price
public class Milk extends CondimentDecorator { Beverage beverage; Milk(Beverage beverage) { this.beverage = beverage; } @Override public String getDescription() { return beverage.getDescription() + ", Milk"; } @Override public double cost() { return beverage.cost() + .10; } } public class Mocha extends CondimentDecorator { private Beverage beverage; Mocha(Beverage beverage){ this.beverage = beverage; } @Override public String getDescription() { return beverage.getDescription() + ", Whip"; } @Override public double cost() { return .10 + beverage.cost(); }} / /...Copy the code
Then we tested the order system
public class Starbucks { public static void main(String[] args) { Beverage beverage = new Espresso(); Order(beverage); Beverage beverage1 = new HouseBlend(); beverage1 = new Mocha(beverage1); beverage1 = new Mocha(beverage1); beverage1 = new Whip(beverage1); Order(beverage1); Beverage beverage2 = new Decaf(); beverage2 = new Soy(beverage2); beverage2 = new Milk(beverage2); Order(beverage2); } private static void Order(Beverage beverage1) { System.out.println(beverage1.getDescription() + " $" + beverage1.cost()); }}Copy the code
Composition of decorator pattern
The decorator pattern is a relatively simple pattern, and the logical block diagram is easy to understand. Component is the unified interface to get all objects. Objects that are decorated and decorated are all of the Component type.
Most decorator patterns have this structure.
Decorator pattern creates objects
Java I/O
IO is a common package that makes good use of DecoratorPattern for I/O methods
-
The basic components
- FileInputStream reads files
- ByteArrayInputStream reads an array of bytes
-
decorator
- BufferedInputStream is read into the buffer first
- DataInputStream reads large numbers
- PushbackInputStream can be rolled back in memory
// InputStream InputStream = new FileInputStream(""); // BufferedInputStream BufferedInputStream = new BufferedInputStream(inputStream);Copy the code
conclusion
Instead of inheritance
- Using decorator patterns to implement extensions is more flexible than inheritance, which dynamically attaches more responsibility to an object in a transparent way to the customer. The decorator pattern allows you to extend the functionality of an object without having to create more subclasses.
- The purpose of decoration mode and inheritance relationship is to extend the function of the object, but the decoration mode can provide more flexibility than inheritance. The association relationship it adopts does not destroy the encapsulation of the class, and inheritance is a static relationship with large coupling degree, which cannot be dynamically extended when the program runs. In the software development stage, although association relation does not reduce the amount of coding than inheritance relation, in the software maintenance stage, because association relation makes the system have better loose coupling, so it makes the system easier to maintain. Of course, the disadvantage of association relationships is that more objects are created than inheritance relationships.
- By using different concrete decoration classes and permutations of these decoration classes, you can create many combinations of different behaviors. You can use multiple concrete decorator classes to decorate the same object, resulting in more powerful objects.
- Specific component class and specific decoration class can be changed independently, users can add new specific component class and specific decoration class according to need, and then combine them when using, the original code need not change, in line with the “open and closed principle”
disadvantages
When designing a system with decoration mode, many small objects will be generated. The difference between these objects is the way they are connected to each other, rather than the difference in their class or attribute values. At the same time, many concrete decoration classes will be generated. These decorative classes and small objects add complexity to the system and make learning and understanding difficult.
Application environment
- Add responsibilities to a single object dynamically and transparently without affecting other objects.
- Functions that need to be added to an object dynamically can also be revoked dynamically.
- When the system cannot be extended by inheritance or inheritance is not conducive to system expansion and maintenance. There are two main types of situations in which inheritance cannot be adopted: the first type is that there are a large number of independent extensions in the system, and a large number of subclasses will be generated to support each combination, making the number of subclasses explosive growth; The second is because class definitions cannot be inherited, such as final classes