Preliminary Exploration of Java Design Patterns 4: This article takes you to grasp the design patterns in JDK

Keywords: Java github Spring JDK

Turn from https://javadoop.com/post/design-pattern

Behavioral patterns

This series of articles will be sorted out into my Java Interview Guide warehouse on GitHub. For more interesting information, please visit my warehouse.

https://github.com/h2pl/Java-Tutorial

If you like, please click Star, fork Ha

The article will also be published in my personal blog, and the reading experience will be better:

www.how2playlife.com

This article is one of a series of blog articles of "Tamping Java Foundation" published by Wechat Public No. [Java Technology Jianghu]. Part of the content of this article comes from the Internet. In order to make the theme of this article clear and thorough, it also integrates a lot of technology blog content that I think is good. Some good blog articles are cited. If there are infringements, please contact the author.
This series of blogs will show you how to learn the basic knowledge of Java step by step from the beginning to the advanced stage, and start the war, then understand the implementation principle behind each Java knowledge point, understand the whole Java technology system more completely, and form its own knowledge framework. In order to better summarize and test your learning results, this series of articles will also provide interview questions and reference answers corresponding to each knowledge point.

If you have any suggestions or questions about this series of articles, you can also pay attention to the Public Number (Java Technology Jianghu) to contact the author. You are welcome to participate in the creation and revision of this series of blogs.

Behavioral patterns

Behavioral patterns focus on the interaction between various classes, which clearly divides responsibilities and makes our code clearer.

Strategy mode

Policy patterns are so common that they are introduced first. It's relatively simple. I don't talk nonsense. I'll just talk about things in code.

The following design scenario is that we need to draw a graph. The optional strategy is to draw with red pen, green pen or blue pen.

First, define a policy interface:

public interface Strategy {
   public void draw(int radius, int x, int y);
}

Then we define specific strategies:

public class RedPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("Draw with red strokes. radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class GreenPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("Draw with green strokes. radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class BluePen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("Draw with blue strokes. radius:" + radius + ", x:" + x + ", y:" + y);
   }
}

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);
   }
}

Client Demo:

public static void main(String[] args) {
    Context context = new Context(new BluePen()); // Use a green pen to draw
      context.executeDraw(10, 0, 0);
}

Put it on a picture to make it clear:

Reupload Cancellation

At this time, do you associate with the bridge mode in the structural mode? They are very similar in fact. I will take the diagram of the bridge mode and compare it with you.

As I say, they are very similar. Bridge mode adds a layer of abstraction to the left. The coupling of bridge modes is lower and the structure is more complex.

Observer model

The observer model is so simple for us. There are no more than two operations. The observer notifies the observer when he subscribes to a topic of interest and the subject has data changes.

First, you need to define topics, each of which needs to hold a reference to the list of observers to notify each observer 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 been changed to inform observers
      notifyAllObservers();
   }

   public void attach(Observer observer){
      observers.add(observer);        
   }

   // Notify observers
   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   }     
}

Define the observer interface:

public abstract class Observer {
   protected Subject subject;
   public abstract void update();
}

In fact, if there is only one observer class, the interface does not need to be defined. However, in normal scenarios, since the observer mode is used, we just want an event to come out, and there will be many different classes that need to process the corresponding information. For example, the successful event of order modification, we hope that the classes that send short messages will be notified, the classes that send mail will be notified, and the classes that process logistics information will be notified.

Let's define specific observer classes:

public class BinaryObserver extends Observer {

      // Subscribe to topics in a constructor
    public BinaryObserver(Subject subject) {
        this.subject = subject;
        // You must be careful when publishing this in a construction method.
        this.subject.attach(this);
    }

      // This method is called by the subject class when the data changes.
    @Override
    public void update() {
        String result = Integer.toBinaryString(subject.getState());
        System.out.println("The subscribed data changes and the new data is processed as a 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 changes and the new data processing is hexadecimal:" + result);
    }
}

Client use is also very simple:

public static void main(String[] args) {
    // Define a topic first
      Subject subject1 = new Subject();
      // Define the observer
      new BinaryObserver(subject1);
      new HexaObserver(subject1);

      // Simulate data changes, at which point the observer's update method will be invoked
      subject.setState(11);
}

output:

Subscribed data changed and the new data was processed as binary: 1011
 Subscribed data changes, and the new data processing is hexadecimal: B

Of course, jdk also provides similar support, you can refer to the two classes of java.util.Observable and java.util.Observer.

In the actual production process, the observer mode is often implemented by message middleware. If we want to realize the single observer mode, the author suggests that the reader use EventBus in Guava, which has synchronous and asynchronous implementation. This paper mainly introduces the design mode, and does not start.

Chain of Responsibility Model

The chain of responsibility usually needs to establish a one-way list first, then the caller needs to call the header node, and the latter will automatically flow down. For example, process approval is a good example, as long as the end user submits the application, according to the content information of the application, automatically establish a chain of responsibility, and then can start to flow.

In such a scenario, users can receive prizes for participating in an activity, but the activity needs a lot of rule checks before release, such as checking whether the user is a new user, whether the number of participants is limited today, whether the number of participants is limited, and so on. Only after the set rules are passed can the user take away the prize.

If the product gives you this requirement, I think most people must have thought at first that all the rules should be stored in a List, and then foreach would execute each rule. But don't worry about it. What's the difference between the responsibility chain model and what we're talking about?

First, we define the base class of the nodes in the process:

public abstract class RuleHandler {

      // Successor node
    protected RuleHandler successor;

    public abstract void apply(Context context);

    public void setSuccessor(RuleHandler successor) {
        this.successor = successor;
    }
    public RuleHandler getSuccessor() {
        return successor;
    }
}

Next, we need to define each specific node.

Verify whether the user is a new user:

public class NewUserRuleHandler extends RuleHandler {

    public void apply(Context context) {
        if (context.isNewUser()) {
              // If there is a successor node, pass it on
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(context);
            }
        } else {
            throw new RuntimeException("This activity is limited to new users");
        }
    }

}

Check whether the user's area 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("I'm sorry that your area is unable to participate in this event.");
        }
    }
}

Check whether the prize has been received:

public class LimitRuleHandler extends RuleHandler {
    public void apply(Context context) {
          int remainedTimes = activityService.queryRemainedTimes(context); // Search for Remaining Prizes
        if (remainedTimes > 0) {
            if (this.getSuccessor() != null) {
                this.getSuccessor().apply(userInfo);
            }
        } else {
            throw new RuntimeException("You came too late and the prize was taken away.");
        }
    }
}

Client:

public static void main(String[] args) {
    RuleHandler newUserHandler = new NewUserRuleHandler();
      RuleHandler locationHandler = new LocationRuleHandler();
      RuleHandler limitHandler = new LimitRuleHandler();

      // Assuming that this activity only checks the area and the number of prizes, it does not check the new and old users.
      locationHandler.setSuccessor(limitHandler);
      locationHandler.apply(context);
}

The code is actually very simple, that is to define a linked list first, and then pass it through any node if it has a successor node.

As for the similarities and differences between it and what we said earlier about using a List to store the rules to be executed, leave it to the reader to ponder.

Template Method Patterns

Template method patterns are very common in code with inheritance structure, which is also widely used in open source code.

Usually there is an abstract class:

public abstract class AbstractTemplate {
    // This is the template method.
      public void templateMethod(){
        init();
        apply(); // This is the point.
        end(); // It can be used as a hook method.
    }
    protected void init() {
        System.out.println("init The abstraction layer has been implemented and subclasses can also be overridden");
    }
      // Leave it to subclass implementations
    protected abstract void apply();
    protected void end() {
    }
}

The template method calls 3 methods, where apply() is an abstract method, and the subclass must implement it. In fact, several abstract methods in the template method are completely free. We can also set the three methods as abstract methods, so that the subclasses can be implemented. That is to say, the template method is only responsible for defining what should be done in the first step, what should be done in the second step, what should be done in the third step, and how to do it is implemented by subclasses.

Let's write an implementation class:

public class ConcreteTemplate extends AbstractTemplate {
    public void apply() {
        System.out.println("Subclass Implementing Abstraction Method apply");
    }
      public void end() {
        System.out.println("We can do this. method3 Use it as a hook method and override it when needed.");
    }
}

Client Call Demo:

public static void main(String[] args) {
    AbstractTemplate t = new ConcreteTemplate();
      // Calling template methods
      t.templateMethod();
}

Code is actually very simple, basically see and understand, the key is to learn to use their own code.

State mode

I won't talk nonsense. Let's give a simple example. The most basic requirement of commodity inventory center is to reduce inventory and replenish inventory. Let's see how to write in state mode.

The core is that our focus is no longer on what the Context should do, but on what the Context will do.

Define the state interface:

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

Define the status of inventory reduction:

public class DeductState implements State {

   public void doAction(Context context) {
      System.out.println("Goods sold, ready to reduce inventory");
      context.setState(this);

      //Execute specific operations to reduce inventory
   }

   public String toString(){
      return "Deduct State";
   }
}

Define replenishment status:

public class RevertState implements State {
    public void doAction(Context context) {
        System.out.println("Replenish the stock of this commodity");
          context.setState(this);

          //Execute the specific operation of stockpiling
    }
      public String toString() {
        return "Revert State";
    }
}

With context.setState(this), let's see how to 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() {
        return this.state;
    }
}

Let's take a look at the client invocation, and you all know:

public static void main(String[] args) {
    // What we need to operate is the iPhone X
    Context context = new Context("iPhone X");

    // See how to replenish inventory
      State revertState = new RevertState();
      revertState.doAction(context);

    // Similarly, inventory reduction operations are very simple.
      State deductState = new DeductState();
      deductState.doAction(context);

      // If necessary, we can get the current status.
    // context.getState().toString();
}

Readers may find that in the above example, if we don't care what the current context is in, then the Context doesn't need to maintain the state attribute, which makes the code much simpler.

However, the example of commodity inventory is only one example. We still have many examples to know what the current context is in.

Summary of Behavioral Model

In the behavioral mode part, the strategy mode, the observer mode, the responsibility chain mode, the template method mode and the state mode are introduced. In fact, the classical behavioral mode also includes the memorandum mode and the command mode, but their use scenarios are relatively limited, and the length of this article is quite large, so I will not introduce them.

summary

The goal of learning design patterns is to make our code more elegant, maintainable and extensible. This collation of this article, let me re-examine the various design patterns, for myself, the harvest is still great. I think that the greatest beneficiaries of an article are usually the author himself. In order to write an article, we need to consolidate our knowledge and search for all kinds of information. What's more, what we have written is the easiest to remember, which is also my advice to readers.

(End of the text)

This series of articles will be sorted out into my Java Interview Guide warehouse on GitHub. For more interesting information, please visit my warehouse.

https://github.com/h2pl/Java-Tutorial

If you like, please click Star, fork Ha

The article will also be published in my personal blog, and the reading experience will be better:

www.how2playlife.com

Structural model

The previous Creative Patterns introduce some design patterns for creating objects. The structural patterns introduced in this section are designed to achieve decoupling by changing the code structure so that our code can be easily maintained and extended.

proxy pattern

The proxy pattern introduced in the first section is one of the most commonly used patterns. A proxy is used to hide the implementation details of a specific implementation class, and it is usually used to add some logic before and after a real implementation.

Since it is a proxy, it is necessary to hide the real implementation from the client, and the proxy is responsible for all the requests of the client. Of course, proxy is only a proxy, it will not complete the actual business logic, but only a skin, but for the client, it must be shown as the real implementation of the client needs.

Understanding the word proxy, this model is actually 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;
    }
}

// Agents need to implement FoodService to behave like real implementation classes
public class FoodServiceProxy implements FoodService {

    // There must be a real implementation class inside, and of course it can also be injected through constructive methods.
    private FoodService foodService = new FoodServiceImpl();

    public Food makeChicken() {
        System.out.println("We're about to start making chicken.");

        // If we define this sentence as the core code, then the core code is made by the real implementation class.
        // Agents just do "insignificant" things around the core code
        Food food = foodService.makeChicken();

        System.out.println("Chicken is finished. Add some pepper."); // Enhance
          food.addCondiment("pepper");

        return food;
    }
    public Food makeNoodle() {
        System.out.println("Preparing to make Ramen~");
        Food food = foodService.makeNoodle();
        System.out.println("The production is finished.")
        return food;
    }
}

Client calls, note that we need to instantiate the interface with proxies:

// Here we use proxy classes to instantiate
FoodService foodService = new FoodServiceProxy();
foodService.makeChicken();


We found that there is no agent model, which means "method packaging" or "method enhancement". In aspect-oriented programming, do not boast about this term, in AOP, in fact, is the process of dynamic proxy. For example, in Spring, we don't define the proxy class ourselves, but Spring will help us define the proxy dynamically, and then add the code logic defined in @ Before, @ After, @ Around to the proxy dynamically.

When it comes to dynamic proxy, it can be expanded to say that... There are two ways to implement dynamic proxy in Spring. One is that if our class defines interfaces, such as UserService interface and UserService Impl implementation, then using JDK dynamic proxy, interested readers can see the source code of java.lang.reflect.Proxy class; the other is that we do not define interfaces ourselves, Spring will use CGLIB for dynamic proxy, which is a JDK dynamic proxy. jar package, good performance.

Adapter mode

After the proxy mode, the adapter mode, because they are very similar, here we can make a comparison.

What the adapter pattern does is that there is an interface that needs to be implemented, but our existing objects are not satisfied, so we need to add a layer of adapters to adapt.

There are three kinds of adapter modes in general: default adapter mode, object adapter mode and class adapter mode. Let's not rush to make a clear distinction between them. Let's look at the examples first.

Default adapter mode

First, let's look at the simplest adapter mode, the default adapter.

We use FileAlterationListener in Appache commons-io package as an example. This interface defines many methods for monitoring files or folders. Once the corresponding operation occurs, the corresponding method will be triggered.

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);
}

A big problem with this interface is that there are too many abstract methods. If we use this interface, it means that we need to implement every Abstract method. If we just want to monitor file creation and deletion events in folders, we still have to implement all the methods. Obviously, this is not what we want.

So, we need the following adapter, which is used to implement the above interface, but all methods are empty methods, so that we can define our own class to inherit the following class.

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) {
    }
}

For example, we can define the following classes. We just need to implement the methods we want to implement.

public class FileMonitor extends FileAlterationListenerAdaptor {
    public void onFileCreate(final File file) {
        // File creation
        doSomething();
    }

    public void onFileDelete(final File file) {
        // File deletion
        doSomething();
    }
}

Of course, what I mentioned above is only one of the adapter modes, and the simplest one, without much to say. Next, we introduce the "orthodox" adapter mode.

Object Adapter Pattern

Let's look at an example in Head First Design Patterns. I've modified it a little to see how chickens can be adapted to ducks, so that chickens can also be used as ducks. Because we don't have a suitable implementation class for the duck interface now, we need an adapter.

public interface Duck {
    public void quack(); // The croaking of ducks
      public void fly(); // fly
}

public interface Cock {
    public void gobble(); // Chicken's grunt
      public void fly(); // fly
}

public class WildCock implements Cock {
    public void gobble() {
        System.out.println("Cooing");
    }
      public void fly() {
        System.out.println("Chickens can fly, too.");
    }
}

There are two methods of duck interface: fly() and quare(). If chicken Cock wants to impersonate duck, fly() method is ready-made, but chicken can't crow duck, there is no quack() method. This is the time to adapt:

// Undoubtedly, first of all, this adapter definitely needs implements Duck to be used as a duck.
public class CockAdapter implements Duck {

    Cock cock;
    // An example of a chicken is needed in the construction method, such as adapting the chicken to a duck.
      public CockAdapter(Cock cock) {
        this.cock = cock;
    }

    // Method of realizing duck croaking
      @Override
      public void quack() {
        // Inside, it's actually a chicken's grunt.
        cock.gobble();
    }

      @Override
      public void fly() {
        cock.fly();
    }
}

Client calls are simple:

public static void main(String[] args) {
    // There's a pheasant
      Cock wildCock = new WildCock();
      // Successfully adapted pheasants to ducks
      Duck duck = new CockAdapter(wildCock);
      ...
}

At this point, you will know what the adapter mode is. It's just that we need a duck, but we only have a chicken. At this time, we need to define an adapter, which acts as a duck, but the method in the adapter is still implemented by the chicken.

Let's illustrate briefly with a graph:

The picture above should be easy to understand, so I won't do any more explanations. Next, let's look at the class adaptation pattern.

Class adapter pattern

Say less nonsense and go straight to the picture above.

Looking at this diagram, it should be easy to understand that the adapter automatically obtains most of the required methods through inheritance. At this point, the client can use it more simply, directly Target = new SomeAdapter (); that's all.

Summary of adapter patterns

  1. Similarities and Differences between Class Adaptation and Object Adaptation

    >One adopts inheritance, one adopts combination;
    > 
    > Class adaptation belongs to static implementation, object adaptation belongs to dynamic implementation of combination, and object adaptation needs multi-instantiation of an object.
    > 
    > Overall, object adaptation is used a lot.
    
  2. Similarities and differences between adapter mode and proxy mode

    Comparing these two patterns is actually comparing the object adapter pattern with the proxy pattern. In terms of code structure, they are very similar and require a concrete instance of the implementation class. But their purposes are different. The agent mode does the enhancement of the original method; the adapter does the appropriate work to provide "packaged chicken into duck, then used as duck", and there is no inheritance relationship between chicken and duck.
    

Bridge mode

Understanding bridge patterns is actually understanding code abstraction and decoupling.

We first need a bridge, which is an interface that defines the interface method provided.

public interface DrawAPI {
   public void draw(int radius, int x, int y);
}

Then there is a series of implementation classes:

public class RedPen implements DrawAPI {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("Draw with red strokes. radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class GreenPen implements DrawAPI {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("Draw with green strokes. radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class BluePen implements DrawAPI {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("Draw with blue strokes. radius:" + radius + ", x:" + x + ", y:" + y);
   }
}

Define an abstract class whose implementation classes all need to use Draw API:

public abstract class Shape {
   protected DrawAPI drawAPI;

   protected Shape(DrawAPI drawAPI){
      this.drawAPI = drawAPI;
   }
   public abstract void draw();    
}

Define subclasses of abstract classes:

// circular
public class Circle extends Shape {
   private int radius;

   public Circle(int radius, DrawAPI drawAPI) {
      super(drawAPI);
      this.radius = radius;
   }

   public void draw() {
      drawAPI.draw(radius, 0, 0);
   }
}
// Rectangle
public class Rectangle extends Shape {
    private int x;
      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);
   }
}

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();
}

Maybe you can see that the steps above are not very clear. I put everything together on a map.

This time you should know where abstraction is and how to decouple it. The advantages of the bridge model are also obvious, that is, it is very easy to expand.

This section Quotes Here An example is given and modified.

Decoration mode

It's not easy to make the decoration pattern clear. Perhaps the reader knows that several classes in Java IO are typical applications of decorative patterns, but the reader does not necessarily know the relationship between them. Perhaps after reading this section, the reader will forget, hoping that after reading this section, the reader can have a deeper understanding of it.

First, let's look at a simple graph. When we look at this graph, we can understand the underlying structure.

Let's talk about the starting point of decoration mode. As can be seen from the figure, interface Component already has two implementation classes: ConcreteComponentA and ConcreteComponentB. However, if we want to enhance these two implementation classes, we can adopt decoration mode to decorate the implementation class with specific decorators in order to achieve the purpose of enhancement.

Briefly explain the decoration from the name. Since it is decoration, it is often adding small functions, and we have to meet the need to add more than one small function. In the simplest way, proxy mode can enhance functions, but it is not easy for proxy to enhance multiple functions. Of course, you can say that proxy is used to wrap proxy, but then the code is complicated.

First of all, let's understand some simple concepts. From the diagram, we can see that all the decorators ConcreteDecorator_ can be used as Components, because they all implement all the interfaces in Components. The difference between them and the ConcreteComponent implementation class Component is that they are just decorators, decorative, that is to say, even though they look like a bombshell, they are only decorated with layers in the concrete implementation.

Note that this passage is mixed up with Component and Decorator in various nouns. Don't get confused.

Let's take a look at an example. Let's first clarify the decoration pattern, and then introduce the application of the decoration pattern in java io.

Recently, "Happy Lemon" has been popular in the streets. We divide the drinks of Happy Lemon into three categories: black tea, green tea and coffee. On the basis of these three categories, we have added a lot of flavors. What kind of black tea are orange and lemon, orange and lemon pearl green tea, mango black tea, Mango Green tea, mango pearl black tea, baked pearl black tea, baked pearl Mango Green Tea and coconut bud coffee? Caramel coffee and so on, each shop has a long menu, but look carefully, in fact, the raw materials are not very different, but can be matched with a lot of combinations, if customers need, many drinks that do not appear in the menu can also be made.

In this case, black tea, green tea and coffee are the most basic beverages, while other beverages such as kumquat, lemon, mango, pearl, coconut, caramel and so on are all used for decoration. Of course, in development, we can really develop these classes like stores: Lemon Black Tea, Lemon Green Tea, Mango Black Tea, Mango Lemon Green Tea... But we soon found that this is definitely not the way to do it, which will lead us to combine all the possibilities, and what if guests need to add two lemons to black tea? What about three lemons? In case a pervert asks for four lemons, this is a way to find overtime for himself.

Don't talk nonsense. Code it.

First, the abstract base class of beverages is defined:

public abstract class Beverage {
      // Return description
      public abstract String getDescription();
      // Return price
      public abstract double cost();
}

Then there are three basic beverage implementations: 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() {
        return 11;
    }
}
...// Coffee omission

Define the condiment, which is the base class of the decorator, which must be inherited from Beverage:

// Seasoning
public abstract class Condiment extends Beverage {

}

Then we will define the specific condiments such as lemon and mango, which belong to the decorator. There is no doubt that these condiments need to inherit Condiment class:

public class Lemon extends Condiment {
    private Beverage bevarage;
      // It's crucial to introduce specific beverages, such as black or green tea, which are not decorated.
      // Of course, it can also be introduced into Mango Green tea, which has been decorated, so as to make mango and lemon green tea.
      public Lemon(Beverage bevarage) {
        this.bevarage = bevarage;
    }
      public String getDescription() {
        // decorate
        return bevarage.getDescription() + ", Add lemon";
    }
      public double cost() {
          // decorate
        return beverage.cost() + 2; // It costs 2 yuan to add lemon.
    }
}
public class Mango extends Condiment {
    private Beverage bevarage;
      public Mango(Beverage bevarage) {
        this.bevarage = bevarage;
    }
      public String getDescription() {
        return bevarage.getDescription() + ", Mango";
    }
      public double cost() {
        return beverage.cost() + 3; // It costs 3 yuan to add mango.
    }
}
...// Add a class to each condiment

Look at the client call:

public static void main(String[] args) {
      // First, we need a basic drink, black tea, green tea or coffee.
    Beverage beverage = new GreenTea();
      // Start decorating
      beverage = new Lemon(beverage); // First add a lemon
      beverage = new Mongo(beverage); // Add one more mango

      System.out.println(beverage.getDescription() + " Price:¥" + beverage.cost());
      //"Green tea, lemon, mango price:16"
}

If we need mango pearl double lemon black tea:

Beverage beverage = new Mongo(new Pearl(new Lemon(new Lemon(new BlackTea()))));

Is it perverted?

Look at the picture below and it may be clearer.

Here, you should have a clear pattern of decoration.

Next, let's talk about decoration patterns in java IO. Look at some of the classes derived from InputStream in the following figure:

We know that InputStream represents the input stream. Specific input sources can be files (FileInputStream), pipes (PipedInputStream), arrays (Byte Array InputStream), and so on. Like black tea and green tea in the previous milk tea example, they belong to the basic input stream.

FilterInputStream inherits the key nodes of decoration mode. Its implementation class is a series of decoration devices, such as Buffered InputStream, which means that the input stream is decorated with buffer. LineNumber InputStream is decorated with line number. Line Number InputStream is decorated with line number. Line number can be obtained when operating. Data InputStream is decorated so that we can decorate from the input stream. Convert to basic type values in java.

Of course, in java IO, if we use decorators, it is not very suitable for Interface-oriented programming, such as:

InputStream inputStream = new LineNumberInputStream(new BufferedInputStream(new FileInputStream("")));

As a result, InputStream still does not have the ability to read line numbers, because the method of reading line numbers is defined in the LineNumberInputStream class.

We should use it as follows:

DataInputStream is = new DataInputStream(
                              new BufferedInputStream(
                                  new FileInputStream("")));

So, it's still hard to find code that strictly conforms to the design pattern.

Facade mode

Facade Pattern is used in many source codes, such as slf4j, which can be understood as the application of Facade Pattern. This is a simple design pattern. Let's go straight to the code.

First, we define an interface:

public interface Shape {
   void draw();
}

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()");
   }
}

Client call:

public static void main(String[] args) {
    // Draw a circle
      Shape circle = new Circle();
      circle.draw();

      // draw a rectangle
      Shape rectangle = new Rectangle();
      rectangle.draw();
}

These are the codes we often write. We need to instantiate the circle first when we draw a circle. We need to instantiate a rectangle first when we draw a rectangle, and then call the corresponding draw() method.

Next, let's see how to use the facade mode to make client calls more friendly.

Let's 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();
   }

  /**
   * Next, we define a bunch of methods, and it's up to this facade to decide what method to call.
   */

   public void drawCircle(){
      circle.draw();
   }
   public void drawRectangle(){
      rectangle.draw();
   }
   public void drawSquare(){
      square.draw();
   }
}

See how the client calls now:

public static void main(String[] args) {
  ShapeMaker shapeMaker = new ShapeMaker();

  // Client calls are now clearer
  shapeMaker.drawCircle();
  shapeMaker.drawRectangle();
  shapeMaker.drawSquare();        
}

The advantage of the facade mode is obvious. The client no longer needs to pay attention to which implementation class should be used when instantiating. It is enough to call the method provided by the facade directly, because the method name of the method provided by the facade class is very friendly to the client.

Combination mode

Composition patterns are used to represent hierarchical data, making our access to individual and composite objects consistent.

Look at an example directly. Every employee has the attributes of name, department and salary. At the same time, there is a set of subordinate employees (although the set may be empty). The structure of subordinate employees is the same as that of their own. There are also attributes of name, department and their subordinate employees.

public class Employee {
   private String name;
   private String dept;
   private int salary;
   private List<Employee> subordinates; // subordinate

   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+" ]");
   }   
}

Typically, this class needs to define add(node), remove(node), getChildren() methods.

In fact, this is the combination model, this simple model I will not introduce too much, I believe that readers do not like to see me write nonsense.

Enjoy yuan mode

English is Flyweight Pattern. I don't know who translated the word first. I feel that the translation is really not easy to understand. Let's try to force the connection. Flyweight means lightweight, which means sharing components separately, that is, reusing the generated objects. This is of course lightweight.

The simplest way to reuse objects is to use a HashMap to store each newly generated object. Every time you need an object, go to HashMap to see if there is one, if not, regenerate it into a new object, and then put it into HashMap.

I won't demonstrate this simple code.

Summary of Structural Models

Previously, we talked about proxy mode, adapter mode, bridge mode, decoration mode, facade mode, combination mode and hedonic mode. Can readers clarify these models separately? When it comes to these patterns, is there a clear picture or process in mind?

The proxy mode is to enhance the method. The adapter mode is to pack chicken into duck, which is used for adapting the interface. The bridge mode achieves a good decoupling. The decoration mode can be seen from the name. It is suitable for the scenario of decoration class or enhancement class. The advantage of the facade mode is that the client does not need to care about the instantiation process, as long as the method needed is called, the combination mode can be used. Used to describe hierarchical data, the hedger pattern is used to cache created objects in specific scenarios for performance improvement.

Reference articles

Turn from https://javadoop.com/post/design-pattern

Posted by nicandre on Sun, 13 Oct 2019 05:35:13 -0700