Common design patterns in java

Keywords: Programming Java Spring Struts

Brief Introduction to Design Patterns

Before understanding design patterns, we must know the role of design patterns. The purpose of using design patterns is to reuse code, make it easier for others to understand code, and ensure code reliability. Design patterns make coding truly engineering; design patterns are the cornerstone of software engineering, just like the structure of a building.

Six Principles of Design Patterns

Open Close Principle

When you expand, try not to modify the original code, in order to make the code scalable, easy to maintain and upgrade. java interfaces and abstract classes can help us achieve this effect.

Liskov Substitution Principle

Substitution is a principle in object-oriented programming, which means that if S is a subtype of T in computer programs, objects of type T can be replaced by objects of type S (that is, objects of type T can be replaced by objects of any subtype S), without changing any desired attributes of T (correctness, task execution, etc.). Richter's substitution principle is a supplement to the "open-close" principle. The key step to realize the "open-close" principle is abstraction. The inheritance relationship between base class and subclass is the concrete realization of abstraction, so Richter's substitution principle is the specification of concrete steps to realize abstraction.

Dependence Inversion Principle

Programs depend on abstract interfaces, not on concrete implementations. Simply put, it requires programming abstractions, not implementations, which reduces the coupling between customers and implementation modules.

Interface Segregation Principle

ISP decomposes very large interfaces into smaller and more specific interfaces so that clients only need to know the methods they are interested in. This reduced interface is also called role interface. ISP is designed to keep the system decoupled, making it easier to reconfigure, change and redeploy. -

Demeter Principle

DP can be more precisely called "Demeter's Law of Function/Method" (LoD-F). In this case, object A can request the service (calling method) B of the object instance, but object A should not "access" another object C through object B to request its service. Doing so implies that object A implicitly requires more knowledge of the internal structure of object B. That is to say, when you reuse code through objects, you just use its functions and can't rely on other things of it, because this dependence is unstable.

Composite Reuse Principle

Classes should combine the principles of polymorphic behavior and code reuse (by including instances of other classes that implement the required functionality), rather than inheriting classes from the base or parent.

Thoughts on Six Principles of Design Model

Open Close Principle

Open-close principle is to be able to expand without modifying, so when expanding, it will default that the original writing is "perfect", do not look carefully, and want to expand with all one's heart. For example, the original thing is introduced into the extension method as a parameter, the result parameters do not meet the requirements, and a series of transformations are needed to meet the requirements, which leads to the apprenticeship. In order to expand, a large number of codes are not doing specific operations, so if the original thing will bring a lot of apprenticeship to the expansion, the original code will have to be modified.

Liskov Substitution Principle

Richter's substitution principle feels like it's not too flawed. It says that it should be replaced only when there is no difference between before and after substitution, but it can't be replaced for substitution either.

Dependence Inversion Principle and Composite Reuse Principle

These two principles are complementary. They say that we should rely on abstraction and not on implementation. If we want to reuse code, we should use objects to reduce coupling. However, too many objects will burden recycling and affect the efficiency of code execution.

Interface Segregation Principle

The principle of interface isolation is to make the interface as small as possible and not to make the implemented classes overly dependent on the interface, which will cause problems for future maintenance. However, too many functions of the interface are too detailed to be managed, making programming dazzling and maintaining a degree (which is difficult).

Demeter Principle

This principle is also the encapsulation of the three main features of java object-oriented. You can only use the method it allows you to use. You can't get class members to use the completion function. You can't achieve other purposes by understanding its internal structure, because the internal structure may change.

Singleton pattern

The singleton pattern means that only one class object is generated and only one instance is used in the whole application. The purpose of some applications is to require only one instance or avoid wasting resources by creating multiple objects.

Singleton pattern implementation: Privatization of constructors, creation of objects in this class, and definition of a common method to access class objects.

Now it is more convenient to implement. spring can be used to manage objects and control singletons.

Factory model

The factory model is divided into three types:

Ordinary factories

It is to build a factory class to create instances of some classes that implement the same interface.
Examples are as follows: (Let's give an example of sending e-mails and short messages)

First, create a common interface between the two:

public interface Sender {  
    public void Send();  
}  

Second, create implementation classes:

public class MailSender implements Sender {  
    @Override  
    public void Send() {  
        System.out.println("this is mailsender!");  
    }  
}  
public class SmsSender implements Sender {  

    @Override  
    public void Send() {  
        System.out.println("this is sms sender!");  
    }  
}  

Finally, plant construction class:

public class SendFactory {  

    public Sender produce(String type) {  
        if ("mail".equals(type)) {  
            return new MailSender();  
        } else if ("sms".equals(type)) {  
            return new SmsSender();  
        } else {  
            System.out.println("Please enter the correct type!");  
            return null;  
        }  
    }  
}  

Let's test:

public class FactoryTest {  

    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produce("sms");  
        sender.Send();  
    }  
}  

Output: this is sms sender!

Multiple Factory Method Patterns

It is an improvement of the common factory method mode. In the common factory method mode, if the string passed is wrong, the object can not be created correctly. The multiple factory method mode provides multiple factory methods to create objects separately.

Modify the above code and change the SendFactory class as follows:

public class SendFactory {  
   public Sender produceMail(){  
        return new MailSender();  
    }  

    public Sender produceSms(){  
        return new SmsSender();  
    }  
}  

The test classes are as follows:

public class FactoryTest {  

    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produceMail();  
        sender.Send();  
    }  
}  

Output: this is mail ender!

Static Factory Method Model

Set the methods in the above factory method patterns as static, and call them directly instead of creating instances.

public class SendFactory {  

    public static Sender produceMail(){  
        return new MailSender();  
    }  

    public static Sender produceSms(){  
        return new SmsSender();  
    }  
}  
public class FactoryTest {  

    public static void main(String[] args) {      
        Sender sender = SendFactory.produceMail();  
        sender.Send();  
    }  
}  

Output: this is mail ender!

Generally speaking, factory mode is suitable: when a large number of products need to be created and have a common interface, they can be created through factory method mode. Among the three modes mentioned above, the first one is that if the incoming string is incorrect, the object can not be created correctly. The third one does not need to instantiate the chemical plant class as compared with the second one. Therefore, in most cases, we will choose the third mode, the static factory method mode.

Adapter mode

There are three main types: class adapter mode, object adapter mode and interface adapter mode. It is through inheritance, implementation and use of instances to soften into a class and complete the desired function.

Class adapter pattern

The core idea is: there is a Source class, with a method to be adapted, Targetable when the target interface, through the Adapter class, the Source function is extended to Targetable, see the code:

public class Source {  

    public void method1() {  
        System.out.println("this is original method!");  
    }  
}  
public interface Targetable {  

    /* Same method as in the original class */  
    public void method1();  

    /* Methods of new classes */  
    public void method2();  
}  
public class Adapter extends Source implements Targetable {  

    @Override  
    public void method2() {  
        System.out.println("this is the targetable method!");  
    }  
}  

The Adapter class inherits the Source class and implements the Targetable interface. The following is the test class:

public class AdapterTest {  

    public static void main(String[] args) {  
        Targetable target = new Adapter();  
        target.method1();  
        target.method2();  
    }  
}  

Output:

this is original method!
this is the targetable method!

So the implementation class of Targetable interface has the function of Source class.

Object's adapter pattern

The basic idea is the same as the Adapter pattern of the class. It only modifies the Adapter class. This time, instead of inheriting the Source class, it holds an instance of the Source class to solve the compatibility problem.

Simply modify the source code of the Adapter class:

public class Wrapper implements Targetable {  

    private Source source;  

    public Wrapper(Source source){  
        super();  
        this.source = source;  
    }  
    @Override  
    public void method2() {  
        System.out.println("this is the targetable method!");  
    }  

    @Override  
    public void method1() {  
        source.method1();  
    }  
}  

Test class:

public class AdapterTest {  

    public static void main(String[] args) {  
        Source source = new Source();  
        Targetable target = new Wrapper(source);  
        target.method1();  
        target.method2();  
    }  
}  

The output is the same as the first one, but the method of adaptation is different.

Interface adapter mode

The adapter of an interface is like this: Sometimes there are many abstract methods in an interface we write. When we write the implementation class of the interface, we must implement all the methods of the interface. This is obviously wasteful sometimes, because not all the methods are needed, sometimes only some ones are needed. In order to solve this problem, we introduce the adapter model of the interface. Formula, with the aid of an abstract class, the abstract class implements the interface and implements all the methods. Instead of dealing with the original interface, we only get in touch with the abstract class, so we write a class, inherit the abstract class, and rewrite the methods we need.

This is well understood. In practical development, we often encounter too many methods defined in this interface, so that sometimes we don't need them in some implementation classes. Look at the code:

public interface Sourceable {  

    public void method1();  
    public void method2();  
}  

The abstract class Wrapper2:

public abstract class Wrapper2 implements Sourceable{  

    public void method1(){}  
    public void method2(){}  
}  
public class SourceSub1 extends Wrapper2 {  
    public void method1(){  
        System.out.println("the sourceable interface's first Sub1!");  
    }  
}  
public class SourceSub2 extends Wrapper2 {  
    public void method2(){  
        System.out.println("the sourceable interface's second Sub2!");  
    }  
}  
public class WrapperTest {  

    public static void main(String[] args) {  
        Sourceable source1 = new SourceSub1();  
        Sourceable source2 = new SourceSub2();  

        source1.method1();  
        source1.method2();  
        source2.method1();  
        source2.method2();  
    }  
}  

Test output:

the sourceable interface's first Sub1!
the sourceable interface's second Sub2!

Decoration mode

As the name implies, decoration mode is to add some new functions to an object, and it is dynamic. The decoration object holds an instance of the object being decorated. It's a bit like the object adapter pattern, but the decorator requires that the decorated object and the decorated object implement the same interface. For example: FilterInputStream and FilterOutputStream.

Source class is a decorated class, Decorator class is a decorated class. It can add some functions dynamically for Source class. The code is as follows:

public interface Sourceable {  
    public void method();  
}  
public class Source implements Sourceable {  

    @Override  
    public void method() {  
        System.out.println("the original method!");  
    }  
}  
public class Decorator implements Sourceable {  

    private Sourceable source;  

    public Decorator(Sourceable source){  
        super();  
        this.source = source;  
    }  
    @Override  
    public void method() {  
        System.out.println("before decorator!");  
        source.method();  
        System.out.println("after decorator!");  
    }  
}  

Test class:

public class DecoratorTest {  

    public static void main(String[] args) {  
        Sourceable source = new Source();  
        Sourceable obj = new Decorator(source);  
        obj.method();  
    }  
}  

Output:

before decorator!
the original method!
after decorator!

Appearance pattern

Appearance patterns are designed to solve the dependencies between classes and class homes. Like spring, they can configure the relationships between classes and classes into configuration files. Appearance patterns place their relationships in a Facade class, which reduces the coupling between classes. There is no interface involved in this pattern.

Let's take the startup process of a computer as an example. Let's first look at the implementation class:

public class CPU {  

    public void startup(){  
        System.out.println("cpu startup!");  
    }  

    public void shutdown(){  
        System.out.println("cpu shutdown!");  
    }  
}  
public class Memory {  

    public void startup(){  
        System.out.println("memory startup!");  
    }  

    public void shutdown(){  
        System.out.println("memory shutdown!");  
    }  
}  
public class Disk {  

    public void startup(){  
        System.out.println("disk startup!");  
    }  

    public void shutdown(){  
        System.out.println("disk shutdown!");  
    }  
}  
public class Computer {  
    private CPU cpu;  
    private Memory memory;  
    private Disk disk;  

    public Computer(){  
        cpu = new CPU();  
        memory = new Memory();  
        disk = new Disk();  
    }  

    public void startup(){  
        System.out.println("start the computer!");  
        cpu.startup();  
        memory.startup();  
        disk.startup();  
        System.out.println("start computer finished!");  
    }  

    public void shutdown(){  
        System.out.println("begin to close the computer!");  
        cpu.shutdown();  
        memory.shutdown();  
        disk.shutdown();  
        System.out.println("computer closed!");  
    }  
}  

The User class is as follows:

public class User {  

    public static void main(String[] args) {  
        Computer computer = new Computer();  
        computer.startup();  
        computer.shutdown();  
    }  
}  

Output:

start the computer!
cpu startup!
memory startup!
disk startup!
start computer finished!
begin to close the computer!
cpu shutdown!
memory shutdown!
disk shutdown!
computer closed!

If we don't have Computer classes, CPU, Memory and Disk will hold instances and have relationships with each other, which will result in serious dependencies. Modifying one class may bring changes to other classes. This is not what we want to see. With Computer classes, their relationships are placed in Computer classes, which will play a role of decoupling. It's the appearance mode!

template method

Explain the template method pattern, that is, in an abstract class, there is a main method, and then define 1. n methods can be abstract or practical. Define a class, inherit the abstract class, rewrite the abstract method, and call the subclass by calling the abstract class.

It is in the AbstractCalculator class that we define a main method calc, calculate() calling spilt(), etc. Plus and Minus inherit the AbstractCalculator class respectively. By calling AbstractCalculator, we can call subclasses. See the following example:

public abstract class AbstractCalculator {  

    /*Main method, which implements calls to other methods of this kind*/  
    public final int calculate(String exp,String opt){  
        int array[] = split(exp,opt);  
        return calculate(array[0],array[1]);  
    }  

    /*Method of Rewriting by Subclass*/  
    abstract public int calculate(int num1,int num2);  

    public int[] split(String exp,String opt){  
        String array[] = exp.split(opt);  
        int arrayInt[] = new int[2];  
        arrayInt[0] = Integer.parseInt(array[0]);  
        arrayInt[1] = Integer.parseInt(array[1]);  
        return arrayInt;  
    }  
}  
public class Plus extends AbstractCalculator {  

    @Override  
    public int calculate(int num1,int num2) {  
        return num1 + num2;  
    }  
}  

Test class:

public class StrategyTest {  

    public static void main(String[] args) {  
        String exp = "8+8";  
        AbstractCalculator cal = new Plus();  
        int result = cal.calculate(exp, "\\+");  
        System.out.println(result);  
    }  
}  

I tracked the execution of this small program: first, exp and "+" were taken as parameters, the calculation (String, String) method in the AbstractCalculator class was called, the same split() method was called in the calculation (String, String), and then the calculation (int, int) method was called. From this method into the subclass, after return num1 + num2 was executed, the value was returned to the AbstractCalculator class. Give the result and print it out. It just validates the way we started.

Command mode

The command pattern is well understood. For example, the commander ordered the soldiers to do something. From the point of view of the whole thing, the commander's role is to issue a password, which is passed on to the soldier's ears and carried out by the soldier. The good thing about this process is that the three are decoupled from each other. No one needs to rely on others. All he needs to do is to do his own thing well. The commander wants the result, not to pay attention to how the soldiers achieve it.

Invoker is the caller (commander), Receiver is the callee (soldier), MyCommand is the command, implements the Command interface, holds the receiving object, and looks at the implementation code:

public interface Command {  
    public void exe();  
}  
public class MyCommand implements Command {  

    private Receiver receiver;  

    public MyCommand(Receiver receiver) {  
        this.receiver = receiver;  
    }  

    @Override  
    public void exe() {  
        receiver.action();  
    }  
}  
public class Receiver {  
    public void action(){  
        System.out.println("command received!");  
    }  
}  
public class Invoker {  

    private Command command;  

    public Invoker(Command command) {  
        this.command = command;  
    }  

    public void action(){  
        command.exe();  
    }  
}  
public class Test {  

    public static void main(String[] args) {  
        Receiver receiver = new Receiver();  
        Command cmd = new MyCommand(receiver);  
        Invoker invoker = new Invoker(cmd);  
        invoker.action();  
    }  
}  

Output: command received!

The purpose of command mode is to achieve decoupling between the issuer and the executor of command and to separate requests from execution. Students familiar with Struts should know that Struts is actually a technology to separate requests from presentation, which inevitably involves the idea of command mode.

Observer model

The next four patterns, including this one, are all the relationships between classes and classes, not involving inheritance. When you learn, you should remember induction and the graph at the beginning of this article. The observer model is well understood, similar to email subscriptions and RSS subscriptions. When we browse some blogs or wiki s, we often see RSS icons. This means that when you subscribe to this article, you will be notified in time if there are subsequent updates. In fact, simply put it in one sentence: when an object changes, other objects that depend on it will be notified, and as it changes! Objects are one-to-many relationships.

Let me explain the role of these classes: MySubject classes are our main objects, Observer1 and Observer2 are MySubject-dependent objects, and when MySubject changes, Observer1 and Observer2 will inevitably change. The AbstractSubject class defines a list of objects to be monitored and can be modified by adding or deleting monitored objects and notifying the objects that exist in the list when MySubject changes. Let's look at the implementation code:

An Observer interface:

public interface Observer {  
    public void update();  
} 

Two implementation classes:

public class Observer1 implements Observer {  

    @Override  
    public void update() {  
        System.out.println("observer1 has received!");  
    }  
}  
public class Observer2 implements Observer {  

    @Override  
    public void update() {  
        System.out.println("observer2 has received!");  
    }  

}  

Subject interface and implementation class:

public interface Subject {  

    /*Increase the number of observers*/  
    public void add(Observer observer);  

    /*Delete the observer*/  
    public void del(Observer observer);  

    /*Notify all observers*/  
    public void notifyObservers();  

    /*Self-operation*/  
    public void operation();  
}  
public abstract class AbstractSubject implements Subject {  

    private Vector<Observer> vector = new Vector<Observer>();  
    @Override  
    public void add(Observer observer) {  
        vector.add(observer);  
    }  

    @Override  
    public void del(Observer observer) {  
        vector.remove(observer);  
    }  

    @Override  
    public void notifyObservers() {  
        Enumeration<Observer> enumo = vector.elements();  
        while(enumo.hasMoreElements()){  
            enumo.nextElement().update();  
        }  
    }  
}  
public class MySubject extends AbstractSubject {  

    @Override  
    public void operation() {  
        System.out.println("update self!");  
        notifyObservers();  
    }  

}  

Test class:

public class ObserverTest {  

    public static void main(String[] args) {  
        Subject sub = new MySubject();  
        sub.add(new Observer1());  
        sub.add(new Observer2());  

        sub.operation();  
    }  

}  

Output:

update self!
observer1 has received!
observer2 has received!

These things, in fact, are not difficult, but some abstract, not easy to understand as a whole, it is recommended that readers: according to the diagram, new projects, write their own code (or refer to my code), according to the overall idea once, so as to understand its ideas, easy to understand!

State mode

The core idea is: when the state of the object changes, at the same time change its behavior, very good understanding! Take QQ for example, there are several states, online, invisible, busy, etc. Each state corresponds to different operations, and your friends can see your state, so there are two points in the state mode: 1. Different behaviors can be obtained by changing the state. 2. Your friends can see your changes at the same time.

The State class is a state class, and the Context class can switch. Let's look at the code:

package com.xtfggef.dp.state;  

/** 
 * Core Classes of State Classes 
 * 2012-12-1 
 * @author erqing 
 * 
 */  
public class State {  

    private String value;  

    public String getValue() {  
        return value;  
    }  

    public void setValue(String value) {  
        this.value = value;  
    }  

    public void method1(){  
        System.out.println("execute the first opt!");  
    }  

    public void method2(){  
        System.out.println("execute the second opt!");  
    }  
}  
package com.xtfggef.dp.state;  

/** 
 * Switching Class of State Mode 2012-12-1 
 * @author erqing 
 *  
 */  
public class Context {  

    private State state;  

    public Context(State state) {  
        this.state = state;  
    }  

    public State getState() {  
        return state;  
    }  

    public void setState(State state) {  
        this.state = state;  
    }  

    public void method() {  
        if (state.getValue().equals("state1")) {  
            state.method1();  
        } else if (state.getValue().equals("state2")) {  
            state.method2();  
        }  
    }  
}  

Test class:

public class Test {  

    public static void main(String[] args) {  

        State state = new State();  
        Context context = new Context(state);  

        //Setting the first state  
        state.setValue("state1");  
        context.method();  

        //Setting the second state  
        state.setValue("state2");  
        context.method();  
    }  
}  

Output:

execute the first opt!
execute the second opt!

According to this feature, state mode is widely used in daily development. Especially when we are making websites, we sometimes hope to distinguish some functions of objects according to certain attributes, such as simple privilege control.

Strategic model

The policy pattern defines a series of algorithms and encapsulates each one so that they can replace each other, and the changes of the algorithm will not affect the customers who use the algorithm. It is necessary to design an interface to provide a unified method for a series of implementation classes. Multiple implementation classes implement the interface, and an abstract class (optional, belonging to auxiliary class) is designed to provide auxiliary functions.

ICalculator provides a method of consent.
AbstractCalculator is an auxiliary class that provides an auxiliary method. Next, it implements each of the following classes in turn:

First, Unified Interface:

public interface ICalculator {  
    public int calculate(String exp);  
}  

Auxiliary classes:

public abstract class AbstractCalculator {  

    public int[] split(String exp,String opt){  
        String array[] = exp.split(opt);  
        int arrayInt[] = new int[2];  
        arrayInt[0] = Integer.parseInt(array[0]);  
        arrayInt[1] = Integer.parseInt(array[1]);  
        return arrayInt;  
    }  
}  

Three implementation classes:

public class Plus extends AbstractCalculator implements ICalculator {  

    @Override  
    public int calculate(String exp) {  
        int arrayInt[] = split(exp,"\\+");  
        return arrayInt[0]+arrayInt[1];  
    }  
}  
public class Minus extends AbstractCalculator implements ICalculator {  

    @Override  
    public int calculate(String exp) {  
        int arrayInt[] = split(exp,"-");  
        return arrayInt[0]-arrayInt[1];  
    }  

}  
public class Multiply extends AbstractCalculator implements ICalculator {  

    @Override  
    public int calculate(String exp) {  
        int arrayInt[] = split(exp,"\\*");  
        return arrayInt[0]*arrayInt[1];  
    }  
}  

Simple test classes:

public class StrategyTest {  

    public static void main(String[] args) {  
        String exp = "2+8";  
        ICalculator cal = new Plus();  
        int result = cal.calculate(exp);  
        System.out.println(result);  
    }  
}  

Output: 10

The decision-making power of policy mode is in the user, the system itself provides the implementation of different algorithms, adds or deletes algorithms, and encapsulates various algorithms. Therefore, policy patterns are mostly used in algorithmic decision-making systems, and external users only need to decide which algorithm to use.

Posted by mattyvx on Tue, 18 Jun 2019 14:38:14 -0700