A design pattern is a high-level abstract summary of the code you actually write at work

  • Design patterns are divided into 23 classic patterns, which can be divided into three categories according to their use. These are the creative pattern, the structural pattern, and the behavioral pattern
  • Here are a few design principles that will run through this article:
    • Interface oriented programming, not implementation oriented. This is especially 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.

Creation pattern

  • The creation mode just creates an object, new an object, and then sets its 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 HuangMenChicken();
            chicken.addCondiment("potato");
            return chicken;
        } else {
            returnnull; }}}Copy the code
  • Among them, LanZhouNoodle and HuangMenChicken 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 {
            returnnull; }}}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"); }}Copy the code
  • Although both are called makeFood(“A”) to make class A foods, different factories produce completely different foods.

  • First step, we need to select the right factory, and then the second step is basically the same as the simple factory.

  • The core of ** is that 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 simple, I also drew all the components on a picture, so that it can be seen 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 not compatible, the code is error-prone, because the client does not know they are incompatible, and random combinations will 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() privateSingleton() {}; Private static Singleton instance = new Singleton(); private static Singleton instance = new Singleton(); public static SingletongetInstance() {
        returninstance; } // write a static method. The point here is that if we just want to call singleton.getDate (...) Public static Date getDate(String mode) {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:
Public class Singleton {// First, block new Singleton() privateSingletonPrivate static volatile Singleton instance = null; private static volatile Singleton instance = null; public static SingletongetInstance() {
        if(instance == null) {// synchronized (singleton.class) {// Synchronized (singleton.class)if(instance == null) { instance = new Singleton(); }}}returninstance; }}Copy the code

Double-check, which means that instance is checked twice to see if it is null. Volatile is needed here, hopefully to get the reader’s attention. Many people simply add synchronized to the getInstance() method signature without knowing how to write it.

  • Nested classes are the most classic, so use them in the future:
public class Singleton3 {

    private Singleton3Private static class Holder {private static Singleton3 instance = new; private static Singleton3 instance = new Singleton3(); } public static Singleton3getInstance() {
        returnHolder.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()... Build () doesn't look so goodbuilder() {
        returnnew UserBuilder(); } public static class UserBuilder {// Private String name; private String password; private String nickName; private int age; privateUserBuilderUserBuilder 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;
            returnthis; The build() method is responsible for "copying" the properties set in the UserBuilder to the User. // Of course, you can do some checking before "copying" public Userbuild() {
            if (name == null || password == null) {
                throw new RuntimeException("Username and Password required");
            }
            if (age <= 0 || age >= 150) {
                throw new RuntimeException("Age is illegal."); } // You can also do the "default" functionif (nickName == null) {
                nickName = name;
            }
            returnnew 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 call all setter methods *return this, and then 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");
        returnf; Public class FoodServiceProxy implements FoodService {// There must be a real implementation class that implements FoodService. Private FoodService FoodService = new FoodServiceImpl(); public FoodmakeChicken() {
        System.out.println("We're about to start making the chicken."); // If we define this as core code, then the core code is what the real implementation class does, and the proxy just does "unimportant" things before and after the core code. System.out.println("Chicken is done. Pepper."); / / to enhance food. AddCondiment ("pepper");

        return food;
    }
    public Food makeNoodle() {
        System.out.println("Ready to make ramen noodles.");
        Food food = foodService.makeNoodle();
        System.out.println("It's done.")
        returnfood; }}Copy the code
  • Client call, note that we are instantiating the interface with a proxy:
FoodService = new FoodServiceProxy(); foodService.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 what the simplest Adapter mode ** Default Adapter mode ** looks like.
  • 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 creationdoSomething(); } public void onFileDelete(final File File) {// File deletiondoSomething(); }}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(); Public class WildCock implements Cock {public voidgobble() {
        System.out.println("Cooing");
    }
      public void fly() {
        System.out.println("Chickens can fly, too."); }}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; } // Implement the quack method @override public voidquack() {// Inside is a cock.gobble(); } @Override public voidfly() { 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("Draw in red, radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class GreenPen implements DrawAPI {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("In green, radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class BluePen implements DrawAPI {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("In blue, 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 voiddraw() { 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 voiddraw() { 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.

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:

  • Let’s talk about the starting point of the decoration pattern, as you can see from the diagram, the interfaceComponentActually, there already isConcreteComponentAConcreteComponentBTwo implementation classes, but if we’re going toTo enhanceSo if we implement these two classes, we can use the decorator pattern, and we can use the concrete decoratordecorationImplement classes 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.

  • First, some simple concepts. From the figure, we can see that all concrete decorators

  • Both can be used as Components because they implement all the interfaces in 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 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 "Black tea";
    }
      public double cost() {
        return 10;
    }
}
public class GreenTea extends Beverage {
    public String getDescription() {
        return "Green tea";
    }
      public double cost() {
        return11. }}... // 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 StringgetDescription() {// decoratereturn bevarage.getDescription() + "Add lemon.";
    }
      public double cost() {// decoratereturnbeverage.cost() + 2; }} public extends Condiment {private Beverage bevarage; public Mango(Beverage bevarage) { this.bevarage = bevarage; } public StringgetDescription() {
        return bevarage.getDescription() + "Add mango.";
    }
      public double cost() {
        returnbeverage.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); // Add a mango system.out.println (beverage.getDescription() +"Price: ¥" + beverage.cost());
      //"Green tea, lemon, 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 voiddrawCircle(){
      circle.draw();
   }
   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("Draw in red, radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class GreenPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("In green, radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class BluePen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("In blue, 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){
      returnstrategy.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); } // Notify observers of public voidnotifyAllObservers() {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); } // This method is called by the topic class when data changesupdate() {
        String result = Integer.toBinaryString(subject.getState());
        System.out.println("The subscribed data changes and the new data is processed with binary values of:" + 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("The subscribed data changes and the new data is processed with hexadecimal values of:"+ 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 voidsetSuccessor(RuleHandler successor) {
        this.successor = successor;
    }
    public RuleHandler getSuccessor() {
        returnsuccessor; }}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 ()) {// Pass it on if there are successorsif (this.getSuccessor() != null) {
                this.getSuccessor().apply(context);
            }
        } else {
            throw new RuntimeException("This event 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("We are very sorry that your area cannot 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); // Query the remaining prizesif (remainedTimes > 0) {
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(userInfo);
            }
        } else {
            throw new RuntimeException("You're too late. The prize's 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 {// This is the template method public voidtemplateMethod(){ init(); apply(); End (); // Can be used as hook method} protected voidinit() {
        System.out.println("Init abstraction layer implemented, subclasses can also choose to override."); } // Leave it to subclasses to implement protected abstract void apply(); protected voidend() {}}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("Subclasses implement abstract methods apply");
    }
      public void end() {
        System.out.println("We can use method3 as a hook method and just overwrite it if we need to."); }}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.

The state pattern

  • I’ll cut the crap, but let’s do a simple example. One of the most basic needs of commodity inventory centers is destocking and replenishment. Let’s see how to write it in state mode.

  • And the point is, instead of focusing on the Context is what operations are going to be done, we’re focusing on what operations are going to be done in this Context.

  • Define the status interface:

public interface State {
   public void doAction(Context context);
}

Copy the code
  • Define the state of inventory reduction:
public class DeductState implements State {

   public void doAction(Context context) {
      System.out.println("Merchandise sold, ready to reduce inventory."); context.setState(this); / /... } public StringtoString() {return "Deduct State"; }}Copy the code
  • Define replenishment inventory state:
public class RevertState implements State {
    public void doAction(Context context) {
        System.out.println("Restock this item"); context.setState(this); / /... } public StringtoString() {
        return "Revert State"; }}Copy the code
  • Using context.setState(this), let’s see how we define the context class:
public class Context {
    private State state;
      private String name;
      public Context(String name) {
        this.name = name;
    }

      public void setState(State state) {
        this.state = state;
    }
      public void getState() {
        returnthis.state; }}Copy the code
  • Let’s take a look at client calls to make it crystal clear:
Public static void main(String[] args) {// iPhone X Context Context = new Context("iPhone X"); State revertState = new revertState (); revertState.doAction(context); // Similarly, deductState = new deductState (); deductState.doAction(context); // Context.getState ().toString(); }Copy the code
  • As the reader might have noticed, in the example above, if we didn’t care what state the context was in, the context wouldn’t have to maintain the state property, and the code would have been much simpler.
  • But after all, the inventory of goods example is only one example, and there are many more examples where we need to know what state the context is in.

Summary of behavioral patterns

  • The behavioral pattern section introduces the policy pattern, observer pattern, chain of responsibility pattern, template method pattern, and state pattern. In fact, the classic behavioral pattern also includes memo pattern, 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. 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.