I have always wanted to write an article that introduces design patterns so that readers can read them quickly, understand them, and use them without confusing each pattern. I think this article is still written well 😂😂😂, I also spent a lot of thought to write and make diagrams, and strive to make readers really can look simple and gain something at the same time.
Design patterns is to practice writing the summary of the various code for high level of abstraction, one of the most famous is the “Gang of Four (GoF) classification, they will design pattern classification for 23 kinds of classical model, according to the purpose we can be divided into three categories, respectively create mode, structural mode and behavior pattern.
There are a few important design principles that I would like to share with you at the beginning of this article, which will run through the rest of the article:
- Interface oriented programming, not implementation oriented. This is very important and the first step to elegant, extensible code, and I don’t need to say much about it.
- Principle of single responsibility. Each class should have a single function, and that function should be fully encapsulated by the class.
- Closed for modifications, open for extensions. To close the modification means that we have worked hard to write the code, the function and the bug repair have been completed, others can not just change; Being open to extensions is easier to understand, meaning that extensions are easy to implement based on code we’ve written.
directory
- Creation pattern
- Simple Factory model
- The factory pattern
- Abstract Factory pattern
- The singleton pattern
- Builder model
- The prototype pattern
- Create a pattern summary
- Structural mode
- The proxy pattern
- Adapter mode
- Default adapter mode
- Object adapter pattern
- Adapter-like pattern
- Adapter Pattern Summary
- Bridge pattern
- Decorative pattern
- Facade pattern
- Portfolio model
- The flyweight pattern
- Summary of structural patterns
- Behavioral pattern
- The strategy pattern
- Observer model
- Chain of Responsibility model
- Template method pattern
- Summary of behavioral patterns
- conclusion
Creation pattern
The creation mode is used to create objects. When it comes to creating an object, the most familiar is to new an object and set related properties. However, there are many scenarios where we need to give clients a more user-friendly way to create objects, especially if we define classes that need to be made available to other developers.
Simple Factory model
As simple as the name, very simple, directly to the code:
public class FoodFactory { public static Food makeFood(String name) { if (name.equals("noodle")) { Food noodle = new LanZhouNoodle(); noodle.addSpicy("more"); return noodle; } else if (name.equals("chicken")) { Food chicken = new HuangMengChicken(); chicken.addCondiment("potato"); return chicken; } else { return null; }}}Copy the code
Among them, LanZhouNoodle and HuangMengChicken are both inherited from Food.
In a nutshell, the simple factory pattern usually looks like this: a factory class XxxFactory with a static method that returns different instance objects derived from the same parent class (or implementing the same interface), depending on our different arguments.
We emphasize the single responsibility principle, a class only provides one function, the function of FoodFactory is only responsible for the production of various Food.
The factory pattern
The simple factory model is simple, and if it meets our needs, I don’t think we should bother. We need to introduce the factory model because we often need to use two or more factories.
public interface FoodFactory { Food makeFood(String name); } public class ChineseFoodFactory implements FoodFactory { @Override public Food makeFood(String name) { if (name.equals("A")) { return new ChineseFoodA(); } else if (name.equals("B")) { return new ChineseFoodB(); } else { return null; } } } public class AmericanFoodFactory implements FoodFactory { @Override public Food makeFood(String name) { if (name.equals("A")) { return new AmericanFoodA(); } else if (name.equals("B")) { return new AmericanFoodB(); } else { return null; }}}Copy the code
ChineseFoodA, ChineseFoodB, AmericanFoodA and AmericanFoodB are all derived from Food.
Client call:
Public class APP {public static void main(String[] args) {FoodFactory factory = new ChineseFoodFactory(); MakeFood = factory.makefood ("A"); makeFood = factory.makefood ("A"); }}Copy the code
Although both are called makeFood(” A “) to make class A foods, the products produced by different factories are completely different.
First step, we need to select the right factory, and then the second step is basically the same as the simple factory.
The point is, we need to choose the factory we need in the first step. For example, we have the LogFactory interface, and the implementation classes FileLogFactory and KafkaLogFactory correspond to writing logs to files and Kafka, respectively. Obviously, The first step for our client is to decide whether to instantiate FileLogFactory or KafkaLogFactory, which will determine all subsequent operations.
Although it is simple, I have also drawn all the components on a picture so that the reader can see them clearly:
Abstract Factory pattern
When it comes to product families, the abstract factory pattern needs to be introduced.
A classic example is building a computer. Let’s leave out the abstract factory pattern and see how it works.
Since a computer is composed of many components, we abstract the CPU and the motherboard, and then the CPU is produced by CPUFactory, and the motherboard is produced by MainBoardFactory. Then, we combine the CPU and the motherboard together, as shown below:
The client call at this point looks like this:
CPUFactory = new IntelCPUFactory(); CPU cpu = intelCPUFactory.makeCPU(); MainBoardFactory = new AmdMainBoardFactory(); MainBoard mainBoard = mainBoardFactory.make(); // Assemble cpus and motherboards Computer = new Computer(CPU, mainBoard);Copy the code
Looking at the CPU factory and the motherboard factory separately, they are respectively the factory mode we talked about earlier. This approach is also easy to scale, because to add a hard disk to a computer, you just need to add a HardDiskFactory and the corresponding implementation, without modifying the existing factory.
One problem with this approach, however, is that if Intel cpus and AMD motherboards are incompatible, the code is error-prone, because the client doesn’t know they’re incompatible, so random combinations can occur by mistake.
Here is the concept of a product family, which represents a collection of accessories that make up a product:
When it comes to this kind of product family, the abstract factory pattern is needed to support it. Instead of defining a CPU factory, a motherboard factory, a hard disk factory, a display factory, etc., we define a computer factory, where each computer factory produces all the equipment, so that there are no compatibility problems.
At this time, for the client, it is no longer necessary to select CPU manufacturers, motherboard manufacturers, hard disk manufacturers, etc., directly choose a brand factory, the brand factory will be responsible for the production of all things, and can ensure that it is compatible and available.
Public static void main(String[] args) {public static void main(String[] args) {ComputerFactory cf = new AmdFactory(); // makeCPU = cf.makecpu (); // From the MainBoard = cf. MakeMainBoard (); MakeHardDisk () = cf.makeharddisk (); Computer result = new Computer(CPU, board, hardDisk); }Copy the code
Of course, the problem of abstract factories is also obvious. For example, if we want to add a display, we need to modify all factories, and add the method of making the display to all factories. This kind of violates the design principle of closing for changes and opening for extensions.
The singleton pattern
The singleton pattern is the most used and the most wrong.
Hungrier mode is the easiest:
Public class Singleton {// First, block new Singleton() private Singleton() {}; Private static Singleton instance = new Singleton(); private static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } // write a static method. The point here is that if we just want to call singleton.getDate (...) Public static Date getDate(String mode) {return new Date(); }}Copy the code
Many people can point out the weaknesses of the hungry Man pattern, but I think it’s rare in production where you define a singleton class that doesn’t need an instance, and then you cram one or more static methods into that class that you’ll need.
Full Han mode is most prone to error:
Private Singleton() {} public class Singleton() {} public class Singleton() {} public class Singleton() {} public class Singleton() {} public class Singleton() {} public class Singleton() {} public class Singleton() {} Private static volatile Singleton instance = null; Public static Singleton getInstance() {if (instance == null) {synchronized (Singleton. Class) { If (instance == null) {instance = new Singleton(); } } } return instance; }}Copy the code
Double locking, volatile ensures visibility between threads, and synchronized synchronizes for possible concurrency problems
Many people don’t know how to write it and simply add synchronized to the getInstance() method signature.
Nested classes are the most classic, so use them in the future:
Public class Singleton3 {private Singleton3() {} public class Singleton3() {} public class Singleton3() {} public class Singleton3() {} public class Singleton3() {} private static Singleton3 instance = new Singleton3(); } public static Singleton3 getInstance() { return Holder.instance; }}Copy the code
Note that many people refer to this nested class as a static inner class. Strictly speaking, inner classes and nested classes are different, and they have different permissions to access the outer class.
Finally, someone must jump in and say use enumeration to implement singletons. Yes, enumeration classes are special in that they initialize all instances of them when the class is loaded, and the JVM guarantees that they will never be instantiated again, so they are singletons by nature. Don’t say, the reader sees oneself do, do not recommend to use.
Builder model
The XxxBuilder classes that are often encountered are often products of the Builder pattern. There are many variations of the builder pattern, but for clients, we usually use the same pattern:
Food food = new FoodBuilder().a().b().c().build();
Food food = Food.builder().a().b().c().build();Copy the code
The trick is to create a New Builder, call a bunch of methods in chain, and then call build() once again, and you have the object you need.
Here’s a modest builder model:
Class User {// Here is a list of attributes private String name; private String password; private String nickName; private int age; Private User(String name, String password, String nickName, int age) {this.name = name; this.password = password; this.nickName = nickName; this.age = age; } // Create a new user.userBuilder ().a()... Public static UserBuilder Builder() {return new UserBuilder(); } public static class UserBuilder {// Private String name; private String password; private String nickName; private int age; Private UserBuilder() {} public UserBuilder name(String name) {this.name = name; return this; } public UserBuilder password(String password) { this.password = password; return this; } public UserBuilder nickName(String nickName) { this.nickName = nickName; return this; } public UserBuilder age(int age) { this.age = age; return this; The build() method is responsible for "copying" the properties set in the UserBuilder to the User. / / of course. Can do the inspection before the "copy" public User build () {if (name = = null | | password = = null) {throw new RuntimeException (" the User name and password required "); } the if (age < = 0 | | age > = 150) {throw new RuntimeException (" age is not a legal "); } if (nickName == null) {nickName = name; } return new User(name, password, nickName, age); }}}Copy the code
The core is to set all the properties to the Builder and then copy those properties to the actual generated object when the build() method is used.
Look at the client call:
public class APP { public static void main(String[] args) { User d = User.builder() .name("foo") .password("pAss12345") .age(25) .build(); }}Copy the code
To be honest, the chain writing of Builder mode is very attractive, but after writing a lot of “useless” Builder code, it doesn’t feel very useful. However, when there are many attributes, some mandatory and some optional, this pattern makes the code much clearer. We can force the caller to provide required fields in the Builder constructor, and the code is more elegant when checking parameters in the build() method than when checking parameters in the User constructor.
As an aside, lombok is strongly recommended. With Lombok, a bunch of code looks like this:
@Builder
class User {
private String name;
private String password;
private String nickName;
private int age;
}Copy the code
What do you say? Maybe I can do something else with the time I’ve saved.
Of course, if you just want chain notation and don’t want builder mode, there’s an easy way to do this: Leave the getter method for User unchanged, just return this for all setter methods, and you can call it like this:
User user = new User().setName("").setPassword("").setAge(20);Copy the code
The prototype pattern
This is the last design pattern I want to talk about for the creative pattern.
The prototype pattern is simple: there is an instance of a prototype, and from that instance a new instance is created, that is, a clone.
The Object class has a clone() method that generates a new Object. Of course, if we want to call this method, Java requires that our class implement the Cloneable interface, which does not define any methods, but otherwise, at clone(), Will throw CloneNotSupportedException anomalies.
protected native Object clone() throws CloneNotSupportedException;Copy the code
Java clones are shallow clones. When encountering an object reference, the cloned object will point to the same object as the reference in the original object. The usual way to implement procloning is to serialize an object and then deserialize it.
I think that’s enough about prototype patterns, and there’s a lot of variation to say that this code or that code is a prototype pattern, it doesn’t make sense.
Create a pattern summary
In general, the creation patterns are relatively simple. Their purpose is to generate instance objects, which is the first step of all work. Since we are writing object-oriented code, the first step is of course to create an object.
The simple factory model is the simplest; Factory mode adds the dimension of selecting factories on the basis of simple factory mode. The first step is to select suitable factories. Abstract Factory pattern has the concept of product family, if the various products are incompatible, the abstract factory pattern will be used. The singleton mode is not mentioned. In order to ensure that the same object is used globally, on the one hand, it is for security reasons, on the other hand, it is to save resources. The builder mode deals specifically with classes with lots of attributes, in order to make the code more elegant; The prototype pattern is the least used, just know something about the Clone () method in the Object class.
Structural mode
While the creative pattern introduced some design patterns for creating objects, the structural pattern introduced in this section aims to decouple our code by changing its structure, making our code easy to maintain and extend.
The proxy pattern
The first proxy pattern is one of the most commonly used, using a proxy to hide implementation details of a concrete implementation class and often to add some logic before and after the actual implementation.
Since it is a proxy, the real implementation is hidden from the client and the proxy takes care of all requests from the client. Of course, an agent is just an agent, it doesn’t do the actual business logic, it’s just a layer, but to the client, it must appear to be the real implementation that the client needs.
Understand the word agent, and the pattern is simple.
public interface FoodService { Food makeChicken(); Food makeNoodle(); } public class FoodServiceImpl implements FoodService { public Food makeChicken() { Food f = new Chicken() f.setChicken("1kg"); f.setSpicy("1g"); f.setSalt("3g"); return f; } public Food makeNoodle() { Food f = new Noodle(); f.setNoodle("500g"); f.setSalt("5g"); return f; Public class FoodServiceProxy implements FoodService {// There must be a real implementation class that implements FoodService. Private FoodService FoodService = new FoodServiceImpl(); Public Food makeChicken() {system.out.println (" We're about to start making chicken "); Food chicken = foodservice.makechicken (); System.out.println(" Chicken done, pepper "); / / enhance chicken. AddCondiment (" pepper "); return fruit; } public Food makeNoodle() {system.out.println (" prepare to make noodles ~"); Food noodle = foodService.makeNoodle(); System.out.println(" finished ") return fruit; }}Copy the code
Client call, note that we are instantiating the interface with a proxy:
FruitService = new FruitServiceProxy(); fruitService.makeChicken();Copy the code
We found that no, the proxy model is simply doing “method wrapping” or “method enhancement.” In aspect oriented programming, the term should not be touted; in AOP, it is the process of dynamic proxy. For example, in Spring, we don’t define our own proxy class, but Spring dynamically defines the proxy for us, and then dynamically adds the code logic we defined in @before, @After, and @around to the proxy.
Speaking of dynamic proxies, we can expand to say… If our class defines an interface, such as the UserService interface and the UserServiceImpl implementation, then use the JDK dynamic proxy. Interested readers can check out the source code for the java.lang.Reflect. Proxy class; Spring uses CGLIB for dynamic proxy, which is a JAR package with good performance.
Adapter mode
Let’s say the proxy pattern and the adapter pattern, because they are similar, and we can make a comparison here.
What the adapter pattern does is, there is an interface that needs to be implemented, but none of our existing objects meet it, and we need to add another layer of adapters to adapt it.
There are three types of adapter patterns: default adapter pattern, object adapter pattern, and class adapter pattern. Before you try to sort these out, let’s look at some examples.
Default adapter mode
First, let’s look at the simplest Adapter pattern, the Default Adapter.
We use the FileAlterationListener in the Appache Commons-io package as an example. This interface defines a number of methods for monitoring files or folders, and triggers the corresponding methods whenever an operation occurs.
public interface FileAlterationListener {
void onStart(final FileAlterationObserver observer);
void onDirectoryCreate(final File directory);
void onDirectoryChange(final File directory);
void onDirectoryDelete(final File directory);
void onFileCreate(final File file);
void onFileChange(final File file);
void onFileDelete(final File file);
void onStop(final FileAlterationObserver observer);
}Copy the code
This interface is a big problem of abstract methods too much, if we want to use this interface, means that we need to realize each abstract method, if we want to monitor folder files to create and delete events, but we still have to implement all methods, it is clear that this is not what we want.
So, we need the following adapter, which implements the above interface, but all the methods are empty methods, so we can define our own class to inherit the following class instead.
public class FileAlterationListenerAdaptor implements FileAlterationListener {
public void onStart(final FileAlterationObserver observer) {
}
public void onDirectoryCreate(final File directory) {
}
public void onDirectoryChange(final File directory) {
}
public void onDirectoryDelete(final File directory) {
}
public void onFileCreate(final File file) {
}
public void onFileChange(final File file) {
}
public void onFileDelete(final File file) {
}
public void onStop(final FileAlterationObserver observer) {
}
}Copy the code
For example, we can define the following class, and we only need to implement the method we want to implement:
Public class FileMonitor extends FileAlterationListenerAdaptor {public void onFileCreate (final File File) {/ / File creation doSomething(); } public void onFileDelete(final File File) {// doSomething(); }}Copy the code
Of course, this is just one of the adapter patterns, and it’s the simplest one, and it needs no further explanation. Next, the “orthodox” adapter pattern is introduced.
Object adapter pattern
Take a look at an example from Head First Design Patterns, where I modified it a little bit to see how I could adapt a chicken to a duck so that the chicken could also be used as a duck. Because we don’t have a proper implementation class for this interface, we need an adapter.
public interface Duck { public void quack(); Public void fly(); Public void gobble(); Public void fly(); // fly} public class WildCock implements Cock {public void gobble() {system.out.println (" WildCock "); } public void fly() {system.out.println (system.out.println); }}Copy the code
Duck interface has two methods: fly() and quare(). If a chicken wants to pass for a duck, the fly() method is readily available, but a chicken can’t quack like a duck. There is no quack() method. This time you need to adapt:
// Public class CockAdapter implements Duck {Cock Cock; Public CockAdapter(Cock Cock) {this. Cock = Cock; } // Override public void quack() {cock. Gobble (); } @Override public void fly() { cock.fly(); }}Copy the code
The client call is simple:
Public static void main(String[] args) {Cock wildCock = new wildCock (); Duck Duck = new CockAdapter(wildCock); . }Copy the code
At this point, you know what the adapter pattern is. We need a duck, but we only have a chicken. In this case, we need to define an adapter that acts as the duck, but the methods in the adapter are implemented by the chicken.
Let’s use a diagram to illustrate this briefly:
The diagram above should be easy to understand, so I won’t explain it any further. Now, let’s look at how the class fit pattern works.
Adapter-like pattern
Without further ado, get straight to the picture above:
As you can easily understand from this diagram, the adapter automatically acquires most of the methods it needs by inheriting methods. Target t = new SomeAdapter(); That’s it.
Adapter Pattern Summary
-
Similarities and differences between class adaptation and object adaptation
One adopts inheritance and one adopts composition;
Class adaptation belongs to static implementation, while object adaptation belongs to dynamic implementation of composition. Object adaptation requires multiple instantiation of an object.
In general, objects are used more often.
-
Similarities and differences between the adapter pattern and the broker pattern
Comparing the two patterns is really comparing the object adapter pattern and the proxy pattern. In terms of code structure, they are very similar and both require an instance of a concrete implementation class. But their purpose is different. The proxy pattern enhances the original method. The adaptor is designed to provide “packaging a chicken into a duck and then using it as a duck”, and there is no inheritance between the chicken and the duck.
Bridge pattern
To understand the bridge pattern is to understand code abstraction and decoupling.
We first need a bridge, which is an interface that defines the interface methods provided.
public interface DrawAPI {
public void draw(int radius, int x, int y);
}Copy the code
Then there’s a list of implementation classes:
public class RedPen implements DrawAPI { @Override public void draw(int radius, int x, Int y) {system.out.println (" radius:" + radius + ", x:" + x + ", y:" + y); } } public class GreenPen implements DrawAPI { @Override public void draw(int radius, int x, Int y) {system.out.println (" radius:" + radius + ", x:" + x + ", y:" + y); } } public class BluePen implements DrawAPI { @Override public void draw(int radius, int x, Int y) {system.out.println (" radius:" + radius + ", x:" + x + ", y:" + y); }}Copy the code
Define an abstract class whose implementation classes all need to use DrawAPI:
public abstract class Shape {
protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI){
this.drawAPI = drawAPI;
}
public abstract void draw();
}Copy the code
Define a subclass of an abstract class:
Public class extends Shape {private int extends Shape; public Circle(int radius, DrawAPI drawAPI) { super(drawAPI); this.radius = radius; } public void draw() { drawAPI.draw(radius, 0, 0); }} public Rectangle extends Shape {private Rectangle extends Shape; private int y; public Rectangle(int x, int y, DrawAPI drawAPI) { super(drawAPI); this.x = x; this.y = y; } public void draw() { drawAPI.draw(0, x, y); }}Copy the code
Finally, let’s look at the client demo:
public static void main(String[] args) {
Shape greenCircle = new Circle(10, new GreenPen());
Shape redRectangle = new Rectangle(4, 8, new RedPen());
greenCircle.draw();
redRectangle.draw();
}Copy the code
In case it’s not very clear step by step, I put everything together in one picture:
So this should give you an idea of where the abstraction is and how it decouples. The advantage of the bridge pattern is also obvious: it is very easy to extend.
This section takes the example from here and modifies it.
Decorative pattern
Want to decorate pattern clearly understand, it is not an easy thing. The reader may know that several classes in Java IO are typical decorative applications, but the reader may not know the relationship between them, and may forget it after reading this section. Hopefully, the reader will have a deeper understanding of it after reading this section.
First, let’s take a look at a simple diagram. As we look at this diagram, we need to understand the following hierarchy:
ConcreteComponentA and ConcreteComponentB are already implemented in the Component interface. However, if we want to enhance the ConcreteComponentA and ConcreteComponentB implementation classes, we can use the decorator. Decorate implementation classes with concrete decorators for enhancement purposes.
The name explains the decorator briefly. Since it is decoration, it is often to add small functions, and we want to meet the need to add more than one small function. At the simplest, the proxy model can implement enhancements, but proxies are not easy to implement multiple enhancements. Of course, you can use proxies to wrap agents, but then the code is complicated.
To start with a few simple concepts, see that all concrete decorators can be used as Components because they implement all interfaces in the Component. The difference between them and the Component implementation class ConcreteComponent is that they are decorators, which means that even though they look awesome, they are just layers of decorators on the concrete implementation.
Note the mix of Component and Decorator nouns in this sentence.
Let’s take a look at an example to clarify the decorator pattern and then introduce the application of the decorator pattern in Java IO.
Recently, “Happy lemon” has become popular in the street. We divide the drinks of happy lemon into three categories: Black tea, green tea, coffee, on the basis of the three major categories, also increased a lot of taste, what kumquat lemon tea, kumquat lemon green tea, pearl mango, mango, mango pearl tea, black tea, roasted pearl, roasted pearl mango, green tea, black tea and coconut germ, caramel cocoa coffee, etc., each store has a long menu, but look closely, There’s not much in the way of ingredients, but they can make a lot of combinations, and they can make a lot of drinks that aren’t on the menu if they need them.
In this example, black tea, green tea, and coffee are the basic drinks, while others like kumquat lemons, mango, pearl, coconut, caramel, etc., are all used for decoration. Of course, in development, we can develop these classes just like in stores: LemonBlackTea, LemonGreenTea, MangoBlackTea, MangoLemonGreenTea… However, it soon became clear that this was not going to work, which led to the need to combine all the possibilities, and what if the customer needed to double the lemon in the black tea? What about three lemons? In case some pervert wants four lemons, so this is a way to get overtime.
All right, cut to the code.
First, define the beverage abstract base class:
Public abstract class Beverage {public abstract String getDescription(); Public abstract double cost(); }Copy the code
Then there are the three basic beverage implementation categories, black tea, green tea and coffee:
Public class BlackTea extends Beverage {public String getDescription() {return "BlackTea "; } public double cost() { return 10; }} public class extends Beverage {public String getDescription() {return "GreenTea "; } public double cost() { return 11; }}... // Coffee is omittedCopy the code
Define the condiment, the base class of the decorator, which must inherit from Beverage:
Public abstract class Condiment extends Beverage {}Copy the code
Then we define specific condiments such as lemons and mangoes, which belong to the Condiment category, and there is no doubt that they all need to inherit from the Condiment category:
public class Lemon extends Condiment { private Beverage bevarage; // The key here is to pass in a specific beverage, such as black or green tea that has not been decorated, or mango green tea that has been decorated, Public Lemon(Beverage bevarage) {this.bevarage = bevarage; } public String getDescription() {return bevarage.getDescription() + ", add lemon "; } public double cost() {return beverage. Cost () + 2; }} public extends Condiment {private Beverage bevarage; public Mango(Beverage bevarage) { this.bevarage = bevarage; } public String getDescription() {return bevarage.getDescription() + ", "; } public double cost() { return beverage.cost() + 3; // Add mango for 3 yuan}}... // Add a class for each condimentCopy the code
Look at the client call:
Public static void main(String[] args) {// first, we need a base Beverage, black tea, GreenTea or coffee Beverage = new GreenTea(); Beverage = new Lemon(beverage); Beverage = new Mongo(beverage); Println (beverage.getDescription() + "$" + beverage.cost()); //" green tea, add lemon, add mango "}Copy the code
If we need mango pearl double lemon black tea:
Beverage beverage = new Mongo(new Pearl(new Lemon(new Lemon(new BlackTea()))));Copy the code
Is it sick?
Take a look at the image below for clarity:
At this point, you should have a clear decoration pattern.
Next, let’s talk about decoration patterns in Java IO. Look at some of the classes derived from InputStream below:
We know that InputStream represents the InputStream. Specific input sources can be files (FileInputStream), pipes (PipedInputStream), arrays (ByteArrayInputStream), etc. These are just like the black tea and green tea in the previous example of milk tea. Belongs to the base input stream.
FilterInputStream follows the key node of the decorator pattern, and its implementation class is a series of decorators. For example, BufferedInputStream means to decorate with buffering, which makes the input stream buffered. LineNumberInputStream stands for line number decoration, which can be obtained at operation time. DataInputStream decoration, which allows us to convert from an input stream to a Java primitive type value.
Of course, in Java IO, if we use decorators, it is not suitable for interface oriented programming, such as:
InputStream inputStream = new LineNumberInputStream(new BufferedInputStream(new FileInputStream("")));Copy the code
As a result, InputStream still does not have the ability to read line numbers, because the method for reading line numbers is defined in the LineNumberInputStream class.
We should use it like this:
DataInputStream is = new DataInputStream(
new BufferedInputStream(
new FileInputStream("")));Copy the code
So it’s hard to find pure code that strictly conforms to design patterns.
Facade pattern
The Facade Pattern (also known as the Facade Pattern) is used in many source code, such as SLF4J can be understood as the application of the Facade Pattern. This is a simple design pattern, so let’s go straight to the code.
First, we define an interface:
public interface Shape {
void draw();
}Copy the code
Define several implementation classes:
public class Circle implements Shape { @Override public void draw() { System.out.println("Circle::draw()"); } } public class Rectangle implements Shape { @Override public void draw() { System.out.println("Rectangle::draw()"); }}Copy the code
Client call:
Public static void main(String[] args) {// Draw a circle circle = new circle (); circle.draw(); Rectangle = new Rectangle(); rectangle = new rectangle (); rectangle.draw(); }Copy the code
Draw a circle by instantiating a circle, draw a rectangle by instantiating a rectangle, and then call the draw() method.
Now, let’s look at how to use facade mode to make client calls more friendly.
Let’s first define a facade:
public class ShapeMaker { private Shape circle; private Shape rectangle; private Shape square; public ShapeMaker() { circle = new Circle(); rectangle = new Rectangle(); square = new Square(); } public void drawCircle(){circle.draw();} public void drawCircle(){circle. } public void drawRectangle(){ rectangle.draw(); } public void drawSquare(){ square.draw(); }}Copy the code
See how the client now calls:
public static void main(String[] args) { ShapeMaker shapeMaker = new ShapeMaker(); // Client calls are now clearer shapeMaker. DrawCircle (); shapeMaker.drawRectangle(); shapeMaker.drawSquare(); }Copy the code
The advantage of the facade pattern is that the client no longer needs to worry about which implementation class should be used for instantiation, but can simply call the methods provided by the facade, since the method names provided by the facade class are already friendly to the client.
Portfolio model
The composite pattern is used to represent data with a hierarchy, allowing consistent access to individual and composite objects.
As an example, each employee has attributes such as name, department, and salary, as well as a set of subordinate employees (although the set may be empty). The subordinate employees have the same structure as themselves, and also have attributes such as name, department, and their subordinate employees.
public class Employee { private String name; private String dept; private int salary; private List<Employee> subordinates; Public Employee(String name,String dept, int sal) {this.name = name; this.dept = dept; this.salary = sal; subordinates = new ArrayList<Employee>(); } public void add(Employee e) { subordinates.add(e); } public void remove(Employee e) { subordinates.remove(e); } public List<Employee> getSubordinates(){ return subordinates; } public String toString(){ return ("Employee :[ Name : " + name + ", dept : " + dept + ", salary :" + salary+" ]"); }}Copy the code
Typically, this class needs to define the add(node), remove(node), and getChildren() methods.
This is actually a combination of patterns, this simple pattern I will not do too much introduction, I believe that readers do not like to see me write nonsense.
The flyweight pattern
English is Flyweight Pattern, I don’t know who first translated this word, I feel this translation is really difficult to understand, let’s try to force the correlation. Flyweight means lightweight, and sharing components separately is sharing components, that is, reusing already generated objects, which is of course lightweight.
The easiest way to reuse objects is to use a HashMap to store each newly generated object. Every time you need an object, you go to the HashMap to see if it exists, and if it doesn’t, you generate a new object, and then you put that object into the HashMap.
I’m not going to show you this simple code.
Summary of structural patterns
Earlier, we talked about the broker pattern, adapter pattern, bridge pattern, decorative pattern, facade pattern, composite pattern and share pattern. Can the reader clarify these patterns separately? Do you have a clear picture or process in mind when talking about these patterns?
Method is enhanced, the proxy pattern do wrap chicken as a duck the adapter pattern is used as interface adapter, do good decoupling bridge model, decorative pattern will see come out from the name, is suitable for decoration or enhance class scene, the advantages of the facade pattern is a client does not need to care about instantiation, as long as the call need to approach, The composite mode is used to describe hierarchical data, while the meta-mode is used to cache objects that have been created in specific scenarios to improve performance.
Behavioral pattern
Behavioral patterns focus on the interactions between classes, separating responsibilities and making our code clearer.
The strategy pattern
The policy pattern is all too common, so I’ll cover it first. It’s a little bit easier, so I’m going to cut to the chase and go straight to the code.
The following scenario is designed so that we need to draw a graph, and the alternative strategy is to draw with a red pen, a green pen, or a blue pen.
First, define a policy interface:
public interface Strategy {
public void draw(int radius, int x, int y);
}Copy the code
Then we define several specific strategies:
public class RedPen implements Strategy { @Override public void draw(int radius, int x, Int y) {system.out.println (" radius:" + radius + ", x:" + x + ", y:" + y); } } public class GreenPen implements Strategy { @Override public void draw(int radius, int x, Int y) {system.out.println (" radius:" + radius + ", x:" + x + ", y:" + y); } } public class BluePen implements Strategy { @Override public void draw(int radius, int x, Int y) {system.out.println (" radius:" + radius + ", x:" + x + ", y:" + y); }}Copy the code
Classes that use policies:
public class Context { private Strategy strategy; public Context(Strategy strategy){ this.strategy = strategy; } public int executeDraw(int radius, int x, int y){ return strategy.draw(radius, x, y); }}Copy the code
Client demo:
public static void main(String[] args) { Context context = new Context(new BluePen()); // Use green pen to draw context.executeDraw(10, 0, 0); }Copy the code
Put it on a picture so you can see it clearly:
At this time, do you think of the bridge mode in the structural mode, they are very similar, let me bring the diagram of the bridge mode for you to compare:
If YOU ask me, they are very similar, with the bridge mode adding a layer of abstraction on the left. The bridge model has lower coupling and a more complex structure.
Observer model
The observer model is pretty simple for us. There are just two operations: observers subscribe to topics they care about and they are notified of any changes to the topic.
First, we need to define topics, and each topic needs to hold a reference to the list of observers that can be used to notify individual observers when data changes:
public class Subject { private List<Observer> observers = new ArrayList<Observer>(); private int state; public int getState() { return state; } public void setState(int state) { this.state = state; // Data has changed, notifyAllObservers(); } public void attach(Observer observer){ observers.add(observer); } public void notifyAllObservers(){for (Observer Observer: observers) {observer.update(); }}}Copy the code
Define the observer interface:
public abstract class Observer {
protected Subject subject;
public abstract void update();
}Copy the code
In fact, if there is only one observer class, the interface does not need to be defined. However, in most scenarios, since the observer mode is used, we expect an event to come out, and there will be multiple different classes that need to handle the corresponding information. For example, we want the classes that send SMS messages to be notified, the classes that send emails to be notified, the classes that process logistics information to be notified, and so on.
Let’s define specific observer classes:
Public Class BinaryObserver extends Observer {// Subscribe to a topic ina constructor public BinaryObserver(Subject Subject) {this.subject = subject; // Always be careful about this.subject.attach(this); @override public void update() {String result = integer.tobinaryString (subject.getState()); System.out.println(" Subscribed data changes, new data is processed as binary value: "+ result); } } public class HexaObserver extends Observer { public HexaObserver(Subject subject) { this.subject = subject; this.subject.attach(this); } @Override public void update() { String result = Integer.toHexString(subject.getState()).toUpperCase(); System.out.println(" Subscribed data changed, new data processed for hexadecimal value: "+ result); }}Copy the code
The client side is also very simple:
Public static void main(String[] args) {Subject subject1 = new Subject(); // Define the observer new BinaryObserver(Subject1); new HexaObserver(subject1); // Simulate data changes, in which case the observer's update method will be called subject.setstate (11); }Copy the code
output:
The subscribed data is changed and the new data is processed as the binary value: 1011 The subscribed data is changed and the new data is processed as the hexadecimal value: BCopy the code
The JDK provides similar support for java.util.Observable and java.util.Observer classes.
In the actual production process, the observer mode is often implemented by messaging-oriented middleware. In order to realize the stand-alone observer mode, the author suggests readers to use EventBus in Guava, which has synchronous and asynchronous implementations. This article mainly introduces the design mode and will not expand on it.
Chain of Responsibility model
The responsibility chain usually starts with a one-way list, and then the caller only needs to call the header node, which then flows automatically. For example, process approval is a good example. As long as the end user submits the application, a chain of responsibility is automatically established according to the content information of the application, and then the flow can start.
There is a scenario where users can receive prizes for participating in an activity, but the activity requires a lot of rules verification before release, such as checking whether the user is a new user, whether there is a quota for the number of participants today, whether there is a quota for the total number of participants and so on. After setting the rules are passed, the user can claim the prize.
If the product gives you this requirement, I’m sure most people will start with a List of all the rules and then Foreach will execute each rule. But wait, reader. How does the chain of responsibility model differ from this one?
First, we define the base class of the nodes on the process:
Public abstract class RuleHandler {// Protected RuleHandler succeeded; public abstract void apply(Context context); public void setSuccessor(RuleHandler successor) { this.successor = successor; } public RuleHandler getSuccessor() { return successor; }}Copy the code
Next, we need to define each node in detail.
Verify whether the user is a new user:
public class NewUserRuleHandler extends RuleHandler { public void apply(Context context) { if (context.isNewUser()) { // Succeeding, if there are any, if (this.getsucceeded ()! = null) { this.getSuccessor().apply(context); }} else {throw new RuntimeException(" This activity is for new users only "); }}}Copy the code
Verify whether the user’s region can participate:
public class LocationRuleHandler extends RuleHandler { public void apply(Context context) { boolean allowed = activityService.isSupportedLocation(context.getLocation); if (allowed) { if (this.getSuccessor() ! = null) { this.getSuccessor().apply(context); }} else {throw new RuntimeException(" Sorry, your region is unable to participate in this event "); }}}Copy the code
Check whether the prize has been claimed:
public class LimitRuleHandler extends RuleHandler { public void apply(Context context) { int remainedTimes = activityService.queryRemainedTimes(context); If (remainedTimes > 0) {if (this.getsucceeded ()! = null) { this.getSuccessor().apply(userInfo); }} else {throw new RuntimeException(" You're too late, the prize is gone "); }}}Copy the code
Client:
public static void main(String[] args) { RuleHandler newUserHandler = new NewUserRuleHandler(); RuleHandler locationHandler = new LocationRuleHandler(); RuleHandler limitHandler = new LimitRuleHandler(); / / assume the event only calibration area and number of prizes, not check the new and old users locationHandler setSuccessor (limitHandler); locationHandler.apply(context); }Copy the code
The code is actually very simple, is to define a linked list, and then pass through any node, if this node has a successor node, then pass on.
I’ll leave it to the reader to figure out how this differs from our previous example of using a List to store rules that need to be executed.
Template method pattern
The template method pattern is very common in code with inheritance structures, and is used heavily in open source code.
There is usually an abstract class:
Public abstract class AbstractTemplate {public void templateMethod(){init(); apply(); End (); } protected void init() {system.out.println (" Init abstraction is implemented, subclasses can also override "); } // Leave it to subclasses to implement protected abstract void apply(); protected void end() { } }Copy the code
The template method calls three methods, of which apply() is an abstract method and subclasses must implement it. In fact, there are several abstract methods in the template method that are completely free. We can also set all three methods as abstract methods and let subclasses implement them. That is, the template method is only responsible for defining what should be done in step 1, what should be done in step 2, and what should be done in step 3. It is up to the subclasses to do that.
Let’s write an implementation class:
Public class ConcreteTemplate extends AbstractTemplate {public void apply() {system.out.println (" ConcreteTemplate extends AbstractTemplate "); } public void end() {system.out.println (" we can use method3 as a hook method, just overwrite it as needed "); }}Copy the code
Client call demo:
public static void main(String[] args) { AbstractTemplate t = new ConcreteTemplate(); // Call the template method t.templatemethod (); }Copy the code
Code is actually very simple, basically see understand, the key is to learn to use their own code.
Summary of behavioral patterns
The behavioral pattern section introduces the policy pattern, the observer pattern, the chain of responsibility pattern, and the template method pattern. In fact, the classic behavioral pattern also includes the state pattern, the command pattern, etc., but their use scenarios are limited, and this article is too large, I will not introduce them.
conclusion
The goal of learning design patterns is to make our code more elegant, maintainable, and extensible. I used to study design patterns in order to understand open source code, but also in order to write code to install B. Sorting out this article, LET me re-examine each design mode, for my own harvest is quite big. I think, the biggest beneficiary of the article is generally the author himself, in order to write an article, need to consolidate their knowledge, need to find all kinds of information, and, their own writing is the easiest to remember, is also my advice to readers.
(Full text)