summary
Decorator Pattern allows adding new functions to an existing object without changing its structure. It is used as a wrapper for an existing class.
To extend the functionality of an object, the decorator pattern provides a more flexible alternative than inheritance. Combination is better than inheritance.
When to use:
- You need to extend the functionality of a class or add additional responsibilities to a class.
- You need to dynamically add functions to an object, and these functions can be dynamically revoked.
- It is necessary to add a very large number of functions generated by the arrangement and combination of some basic functions, so that inheritance becomes unrealistic.
UML class diagram:
Role composition:
- Abstract component role: defines the interface of an object that can dynamically add tasks
- Concrete Component: defines an object to be decorated by the decorator, that is, the concrete implementation of the Component
- Abstract decorator: it holds an instance of a component object and defines an interface consistent with the abstract component. It maintains references to component objects and their subclasses
- Concrete decorator role: add new responsibilities to the component
General code
Abstract component role:
public interface Component { void operation(); }
Specific component roles:
public class ConcreteComponent implements Component { @Override public void operation() { // Specific business code System.out.println("ConcreteComponent General basic logic"); } }
Abstract decorative role:
public abstract class Decorator implements Component { private Component component; public Decorator(Component component) { this.component = component; } @Override public void operation() { // Delegate to component component.operation(); } }
Specific decorative roles:
public class ConcreteDecorationA extends Decorator { public ConcreteDecorationA(Component component) { super(component); } @Override public void operation() { super.operation(); decorateMethod(); } // Define your own modification logic private void decorateMethod() { System.out.println("ConcreteDecorationA Modified logic"); } } public class ConcreteDecorationB extends Decorator { public ConcreteDecorationB(Component component) { super(component); } @Override public void operation() { super.operation(); decorateMethod(); } // Define your own modification logic private void decorateMethod() { System.out.println("ConcreteDecorationB Modified logic"); } }
use:
public class Test { public static void main(String[] args) { Component component = new ConcreteComponent(); Decorator decoratorA = new ConcreteDecorationA(component); decoratorA.operation(); } }
result:
ConcreteComponent general basic logic
Modification logic of concrete decoration a
Seeing this, I believe you know a lot about the general code structure of decorator mode. Let's further explain it through a practical use case.
example
I believe everyone has drunk milk tea. There are many kinds of milk tea, such as coconut milk tea, QQ milk tea, chocolate milk tea, etc. different desserts such as coconut, red beans and pudding can be added to milk tea. Milk tea shops now sell milk tea of various flavors. If the decoration mode is not used, all kinds of different milk tea must produce one class in the sales system. If there are 5 kinds of milk tea and 5 kinds of desserts, at least 25 classes (excluding mixed flavors) will be generated. If the decoration mode is used, several classes can be completed.
Milk tea interface class
public interface MilkTea { // Return milk tea description String getDescription(); // Return price double getPrice(); }
Specific milk tea categories: chocolate milk tea and QQ milk tea
public class ChocolateMT implements MilkTea{ private String description = "Chocolate Milk Tea"; @Override public String getDescription() { return description; } @Override public double getPrice() { return 15; } } public class QQMT implements MilkTea { private String description = "QQ tea with milk"; @Override public String getDescription() { return description; } @Override public double getPrice() { return 10; } }
Abstract decoration class
public abstract class Decorator implements MilkTea { protected MilkTea milkTea; public Decorator(MilkTea milkTea) { this.milkTea = milkTea; } @Override public String getDescription() { return milkTea.getDescription(); } @Override public double getPrice() { return milkTea.getPrice(); // The price depends on the type } }
Specific decoration: add coconut to milk tea
public class Coconut extends Decorator { private String description = "With coconut!"; public Coconut(MilkTea milkTea) { super(milkTea); } @Override public String getDescription() { return milkTea.getDescription() + "\n" + description; } @Override public double getPrice() { return milkTea.getPrice() + 3; // 3 represents the price of coconut } }
Specific decoration: add pudding to milk tea
public class Pudding extends Decorator { private String description = "With pudding!"; public Pudding(MilkTea milkTea) { super(milkTea); } @Override public String getDescription() { return milkTea.getDescription() + "\n" + description; } @Override public double getPrice() { return milkTea.getPrice() + 5; // 5 means the price of pudding } }
Specific decoration: add pearls to milk tea
public class Pearl extends Decorator { private String description = "Add pearls!"; public Pearl(MilkTea milkTea) { super(milkTea); } @Override public String getDescription() { return milkTea.getDescription() + "\n" + description; } @Override public double getPrice() { return milkTea.getPrice() + 10; // 10 represents the price of pearls } }
test
// The first way to write public class Test { public static void main(String[] args) { // Choose chocolate milk tea MilkTea milkTea = new ChocolateMT(); // For the first modification, add pudding to chocolate milk tea milkTea= new Pudding(milkTea); // The second modification is to add coconut to chocolate milk tea milkTea = new Coconut(milkTea); System.out.println(milkTea.getDescription() + "\n Price of chocolate milk tea with pudding:" + milkTea.getPrice()); } } // The second way to write public class Test { public static void main(String[] args) { // Choose chocolate milk tea MilkTea milkTea = new ChocolateMT(); // For the first modification, add pudding to chocolate milk tea Pudding puddingMilkTea = new Pudding(milkTea); // The second modification is to add coconut to chocolate milk tea Coconut coconutMilkTea = new Coconut(puddingMilkTea); System.out.println(coconutMilkTea.getDescription() + "\n Price of chocolate milk tea with pudding:" + coconutMilkTea.getPrice()); } }
Test results:
Chocolate Milk Tea
With pudding!
With coconut!
Price of chocolate milk tea with pudding: 23.0
The results of the two ways of writing test cases are the same
summary
As can be seen from the above case, the code structure of the decorator pattern uses the combination of classes to replace inheritance, but in fact, compared with the simple combination relationship, the decorator pattern has two special places.
The first special point is that the decorator class inherits the same parent class as the original class, so that we can "nest" multiple decorator classes on the original class. For example, the second writing method in the above test class nested two decorator classes for chocolate milk tea, adding coconut and pudding to it.
The second special point is that the decorator class is an enhancement of functions, which is also an important feature of the application scenario of decorator mode. In fact, there are many design patterns that conform to the code structure of "composite relationship", such as agent pattern, bridge pattern and decorator pattern. Although their code structure is very similar, the intention of each design pattern is different. Take the more similar proxy mode and decorator mode. In the proxy mode, the proxy class adds functions unrelated to the original class, while in the decorator mode, the decorator class adds enhancements related to the original class.
The IO class library in Java is a classic scenario for using the decorator pattern.
InputStream in = new FileImputStream("/user/test.txt"); InputStream bin = new BufferedInputStream(in); DataInputStream din = new DataInputStream(bin); int data = din.readInt();
Finally
I've always wanted to sort out a perfect interview dictionary, but I can't spare time. This set of more than 1000 interview questions is sorted out in combination with the interview questions of gold, silver and four major factories this year, as well as the documents with star number exceeding 30K + on GitHub. After I uploaded it, it's no surprise that the praise amount reached 13k in just half an hour. To be honest, it's still a little incredible.
You need a full version of the little partner. You can click three times, click here !
1000 Internet Java Engineer Interview Questions
Content: Java, MyBatis, ZooKeeper, Dubbo, Elasticsearch, Memcached, Redis, MySQL, Spring, SpringBoot, SpringCloud, RabbitMQ, Kafka, Linux and other technology stacks (485 pages)
Collection of Java core knowledge points (page 283)
The content covers: Java foundation, JVM, high concurrency, multithreading, distribution, design pattern, Spring bucket, Java, MyBatis, ZooKeeper, Dubbo, Elasticsearch, Memcached, MongoDB, Redis, MySQL, RabbitMQ, Kafka, Linux, Netty, Tomcat, database, cloud computing, etc
Collection of advanced core knowledge points in Java (page 524)
Sorting out knowledge points of Java advanced architecture
Due to space constraints, the detailed information is too comprehensive and there are too many details, so only the screenshots of some knowledge points are roughly introduced. There are more detailed contents in each small node!
You need a full version of the little partner. You can click three times, click here !