[Big Talk Design Patterns] Chapters 3, 4, 5, and 11 Design Principles; Chapters 1 and 2 Factory Patterns, Strategy Patterns; Chapter 6 Decoration Patterns; Chapter 7 Proxy Patterns

Chapter 3 Design Principles—Single Responsibility Principle

​ As far as a class is concerned, there should be only one reason for it to change.
If a class takes on too many responsibilities, it is equivalent to coupling these responsibilities together, and a change in one responsibility may weaken or inhibit the ability of this class to complete other responsibilities. This coupling can result in a brittle design that can suffer unexpected breakage when changes occur.

​ It is necessary to put the same responsibilities together, and separate different responsibilities into different class implementations. Specifically, for a class, if you can think of more than one motivation to change a class, then the class has more than one responsibility, and you should consider decomposing the class's responsibilities.

​ Personal understanding is to only do one thing!

Chapter 4 Design Principles - The Open-Closed Principle

The Open-Closed Principle, or OCP for short, means that software entities (classes, modules, functions, etc.) should be expandable but not modifiable. That is, it is open for extension and closed for change. In layman's terms, for new functions to be added or changes to be adjusted, try to expand new codes instead of modifying existing codes .

Chapter 5 Design Principles - Dependency Inversion Principle

The Dependence Inversion Principle, Dependence Inversion Principle, or DIP for short, means that programs should not depend on details, and details should depend on abstractions. Simply put, it is to program for the interface, not for the implementation.

The first computers were self-contained. Although they all used the same design architecture and structure, the connections between components were different. If you use the computer of company A, you can only use the hard disk provided by company A after the hard disk is broken. This is an expression of tight coupling, where each component exposes its internal implementation to external interfaces.

Later, several large companies unified the standards and agreed on the standards for the connection between components. The specific steps behind the standards are the responsibility of the corresponding companies themselves. As a result, we can use both the hard disk of company A and the hard disk of company B. Not only that, hard drives of different sizes (such as 500G and 200G) and different structures (such as solid-state drives and mechanical hard drives) are also interchangeable. Realized pluggable and easy pluggable.

In addition to the hard disk, various other equipment components such as CPU, memory, and peripheral equipment have also implemented standardized interfaces. All docking occurs at the interface level, and there is no need to care about specific implementation details.

Chapter 5 Design Principles - The Liskov Substitution Principle

Liskov Substitution Principle, Liskov Substitution Principle, referred to as LSP, if a software entity uses a parent class, it must be applicable to its subclasses, and it cannot detect the difference between the parent class and the subclass. That is to say, in the software, if the parent class is replaced with its subclass, the behavior of the program will not change. In simple terms, subtypes must be able to replace their supertypes.

motivation

The parent class can be truly reused (inherited), and the subclass can also add new behaviors based on the parent class.

how to use

  • Parent classes generally use abstract classes or interfaces.
  • Abstract classes define common objects and state; interfaces define common behavior.
  • Subclasses extend by inheriting parent classes and interfaces.

Example of use

Take penguins and birds for example.

Assuming that bird is the parent class, there are two methods of laying eggs and flying. If the penguin as a bird inherits the parent class, there will be problems, because although the penguin has wings, it cannot fly. Therefore, it is unreasonable to design the parent class and subclass in this way, which violates the above-mentioned principle, and the penguin as a subclass cannot replace the parent class.

A reasonable approach is to abstract the behavior of flying into an interface. The parent class bird describes the state and public methods (such as eating), and then the subclasses that can fly implement the flying interface. Those who can’t fly don’t care.

======================================================

Chapter 1 Factory Pattern

package com.miniai;

import java.util.Scanner;

public class Main {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入数字A:");
            Double numberA = Double.parseDouble(scanner.nextLine());
            System.out.println("请选择运算符号(+、-、*、/):");
            String strOperate = scanner.nextLine();
            System.out.println("请输入数字B:");
            Double numberB = Double.parseDouble(scanner.nextLine());

            double result = 0d;

            switch (strOperate){
    
    
                case "+":
                    result = numberA + numberB;
                    break;
                case "-":
                    result = numberA - numberB;
                    break;
                case "*":
                    result = numberA * numberB;
                    break;
                case "/":
                    result = numberA / numberB;
                    break;
            }
            System.out.println("结果是:" + result);
        }
        catch (Exception e){
    
    
            System.out.println("输入有错:" + e.toString());
        }
    }
}

​ The program of the calculator first requires input of two numbers and operation symbols, and then judges and chooses how to operate according to the operation symbols to get the result. This is not wrong in itself, but this kind of thinking makes our program only meet the current needs. The program is not easy to maintain, not easy to expand, and even less easy to reuse, thus failing to meet the requirements of high-quality code.

1.8 Service Encapsulation

package com.miniai;

import java.util.Scanner;

public class Main {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入数字A:");
            Double numberA = Double.parseDouble(scanner.nextLine());
            System.out.println("请选择运算符号(+、-、*、/):");
            String strOperate = scanner.nextLine();
            System.out.println("请输入数字B:");
            Double numberB = Double.parseDouble(scanner.nextLine());
//            double result = 0d;
//            switch (strOperate){
    
    
//                case "+":
//                    result = numberA + numberB;
//                    break;
//                case "-":
//                    result = numberA - numberB;
//                    break;
//                case "*":
//                    result = numberA * numberB;
//                    break;
//                case "/":
//                    result = numberA / numberB;
//                    break;
//            }
            double result = Operation.getResult(numberA,numberB,strOperate);
            System.out.println("结果是:" + result);
        }
        catch (Exception e){
    
    
            System.out.println("输入有错:" + e.toString());
        }
    }
}
package com.miniai;

public class Operation {
    
    
    public static double getResult(double numberA,double numberB,String operate){
    
    

        double result = 0d;
        switch (operate){
    
    
            case "+":
                result = numberA + numberB;
                break;
            case "-":
                result = numberA - numberB;
                break;
            case "*":
                result = numberA * numberB;
                break;
            case "/":
                result = numberA / numberB;
                break;
        }
        return result;
    }
}

1.9 Loosely coupled vs tightly coupled

​ But the above writing method is easy to make a big change to the original functional code that works well.

Operation needs to be divided into four categories:

package com.miniai;

public abstract class Operation {
    
    
    public static double getResult(double numberA,double numberB){
    
    

        return 0d;
    }
}
package com.miniai;

public class Add extends Operation{
    
    

    public double getResult(double numberA,double numberB){
    
    
        return numberA + numberB;
    }
}

package com.miniai;

public class Sub extends Operation{
    
    

    public double getResult(double numberA,double numberB){
    
    
        return numberA - numberB;
    }
}

package com.miniai;

public class Mul extends Operation{
    
    

    public double getResult(double numberA,double numberB){
    
    
        return numberA * numberB;
    }
}
package com.miniai;

public class Div extends Operation{
    
    

    public double getResult(double numberA,double numberB){
    
    

        if(numberB == 0){
    
    
            System.out.println("除数不能为0!");
            throw new ArithmeticException();
        }

        return numberA / numberB;
    }
}

​ The first is an abstract class of operations, which has a method getResult(numberA, numberB), which is used to obtain results, and then I wrote addition, subtraction, multiplication, and division as subclasses of the operation class. After inheriting it, I rewritten the getResult() method , so that if you want to modify any algorithm, you don't need to provide the code of other algorithms. But here comes the question, how do I let the calculator know which algorithm I want to use?

1.10 Simple factory pattern

​ Create a new operation factory class:

package com.miniai;

public class OperationFactory {
    
    

    public static Operation createOperate(String operate){
    
    

        Operation oper = null;
        switch (operate){
    
    
            case "+":
                oper = new Add();
                break;
            case "-":
                oper = new Sub();
                break;
            case "*":
                oper = new Mul();
                break;
            case "/":
                oper = new Div();
                break;
        }
        return oper;
    }
}

The Main function is changed to:

package com.miniai;

import java.util.Scanner;

public class Main {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入数字A:");
            Double numberA = Double.parseDouble(scanner.nextLine());
            System.out.println("请选择运算符号(+、-、*、/):");
            String strOperate = scanner.nextLine();
            System.out.println("请输入数字B:");
            Double numberB = Double.parseDouble(scanner.nextLine());
			
            Operation oper = OperationFactory.createOperate(strOperate);
            double result = oper.getResult(numberA,numberB);

            System.out.println("结果是:" + result);
        }
        catch (Exception e){
    
    
            System.out.println("输入有错:" + e.toString());
        }
    }
}

1.11 UML class diagram

insert image description here

1.1 Introducing the factory pattern

First of all, based on the following UML class diagram to do the factory pattern:

insert image description here

Main.java

package com.miniai;

import com.miniai.factory.CashFactory;
import com.miniai.factory.OperationFactory;

import java.util.Scanner;

public class Main {
    
    
    public static void main(String[] args) {
    
    
        // 2. 商品 工厂模式
        double price = 0d;
        int num = 0;
        double totalPrice = 0d;
        double total = 0d;
        int discount = 0;

        Scanner scanner = new Scanner(System.in);

        do{
    
    
            System.out.println("请输入商品销售模式 1.原价 2.八折 3.七折");
            discount = Integer.parseInt(scanner.nextLine());
            System.out.println("请输入商品单价:");
            price = Double.parseDouble(scanner.nextLine());
            System.out.println("请输入商品数量:");
            num = Integer.parseInt(scanner.nextLine());
            System.out.println();

            if(price >0 && num>0){
    
    

                //这里就可以去写一个工厂
                CashSuper oper = CashFactory.createCrash(discount);
                totalPrice = oper.getCrash(price,num);
                total = total + totalPrice;
                
                System.out.println();
                System.out.println("单价:" + price + "元 数量:" + num + " 合计:" + totalPrice + "元");
                System.out.println();
                System.out.println("总计:" + total + "元");
                System.out.println();
            }
        }while(price>0 && num>0);

    }
}

CashSuper.java

public abstract class CashSuper {
    
    

    public abstract double getCrash(double price,int num);
}

CashFactory.java

public class CashFactory {
    
    

    public static CashSuper createCrash(int operate){
    
    

        CashSuper oper = null;
        switch (operate){
    
    
            case 1:
                oper = new CashNormal();
                break;
            case 2:
                oper = new CashRebate(0.8d);
                break;
            case 3:
                oper = new CashRebate(0.7d);
            case 4:
                oper = new CashReturn(300d,100d);
                break;
        }
        return oper;
    }

}

CashNormal.java

public class CashNormal extends CashSuper {
    
    

    public double getCrash(double price,int num){
    
    
        return price * num;
    }
}

CashRebate.java

public class CashRebate extends CashSuper {
    
    

    private double moneyRebate = 1d;

    public CashRebate(double moneyRebate){
    
    
        this.moneyRebate = moneyRebate;
    }

    public double getCrash(double price,int num){
    
    
        return price * num * this.moneyRebate;
    }

}

CashReturn.java

public class CashReturn extends CashSuper {
    
    

    private double moneyReturn = 0d;
    private double moneyCondition = 0d;

    public CashReturn(double moneyCondition,double moneyReturn){
    
    
        this.moneyReturn = moneyReturn;
        this.moneyCondition = moneyCondition;
    }

    public double getCrash(double price,int num){
    
    
        double result = price * num;
        if (moneyCondition>0 && result >= moneyCondition){
    
    
            result = result - Math.floor(result/moneyCondition) * moneyReturn;
        }
        return result;
    }

}

insert image description here
The factory model only solves the problem of object creation, and since the factory itself includes all charging methods, the mall may change the discount amount and rebate amount frequently, and the factory must be changed every time the charging method is maintained or expanded, so that the code needs to be recompiled Deployment, this is really bad way to do it.

1.2 Strategy pattern

​Strategy mode: It defines the algorithm family and encapsulates them separately so that they can be replaced with each other. This mode allows changes in the algorithm to not affect the customers who use the algorithm.

In the previous example, only the CashContext class needs to be added

public class CashContext {
    
    
    private CashSuper cs;

    public CashContext(int cashSuper){
    
    

        switch (cashSuper){
    
    
            case 1:
                this.cs = new CashNormal();
                break;
            case 2:
                this.cs = new CashRebate(0.8d);
                break;
            case 3:
                this.cs = new CashRebate(0.7d);
            case 4:
                this.cs = new CashReturn(300d,100d);
                break;
        }

    }

    public double getResult(double price,int num){
    
    
        return this.cs.getCrash(price,num);
    }

}

Client code:

insert image description here

​ In the factory mode, I need to let the client know two classes, CashSuper and CashFactory, and the combination of the strategy mode and the simple factory, the client only needs to know one class CashContext, and the coupling is further reduced.

insert image description here
​ We instantiate the CashContext object on the client side, and call the CashContext method GetResult, which completely separates the specific charging algorithm from the client side.

The Strategy class hierarchy of the strategy pattern defines a series of reusable algorithms or behaviors for the Context. Inheritance helps extract common functionality in these algorithms. For discounts, rebates, or other algorithms, they are actually a calculation method for charging actual goods, and their public functions can be obtained through inheritance. The common function is to obtain the result of calculating the cost GetResult, which makes the algorithm have an abstract parent class CashSuper.

Chapter 6 Decorator Patterns

​ Decorator mode (Decorator) dynamically adds some additional responsibilities to an object. In terms of adding functionality, the decoration mode is more flexible than generating subclasses.

insert image description here

Component defines an object interface, which can dynamically add responsibilities to these objects. ConcreteComponent defines a specific object, and can also add some responsibilities to this object.
​ Decorator, decorating an abstract class, inherits Component, and extends the function of Component class from an external class, but for Component, it is not necessary to know the existence of Decorator. As for ConcreteDecorator, it is a specific decoration object, which has the function of adding responsibilities to Component [DPE].

The decoration mode uses SetComponent to wrap objects. In this way, the implementation of each decoration object is separated from how to use this object. Each decoration object only cares about its own function, and does not need to care about how it is added to the object chain.

​The decorator pattern is a way to dynamically add more functionality to existing functionality.
​ When the system needs new functions, it is to add new code to the old class. These newly added codes usually decorate the core responsibilities or main behaviors of the original class,
and these newly added things are only to meet the needs of some special behaviors that will only be executed under certain circumstances. The decoration pattern provides a very good solution, it puts each function to be decorated in a separate class, and let this class wrap the object it wants to decorate, so when special behavior needs to be performed, the client code Then you can selectively and sequentially use the decoration function to wrap objects according to your needs at runtime.

​The advantage of the decoration
​Effectively separates the core responsibilities of the class from the decorative functions. And the repeated decoration logic in related classes can be removed.

Chapter 7 Proxy Patterns

​ Proxy mode (Proxy), which provides a proxy for other objects to control access to this object.

insert image description here

The IGiveGift class defines the common interface between Persuit and Proxy, so that Proxy can be used wherever Persuit is used.

public interface IGiveGift {
    
    
    void giveDolls();
    void giveFlowers();
    void giveChocolate();
}

SchoolGirl class

public class SchoolGirl {
    
    
    private String name;

    public SchoolGirl(){
    
    

    }

    public SchoolGirl(String name){
    
    
        this.name = name;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }
}

Persuit class, which defines the real entity represented by Proxy

//追求者
public class Persuit implements IGiveGift{
    
    

    private SchoolGirl mm;
    public Persuit(SchoolGirl mm){
    
    
        this.mm = mm;
    }

    @Override
    public void giveDolls() {
    
    
        System.out.println(this.mm.getName() + ",你好!送你洋娃娃");
    }

    @Override
    public void giveFlowers() {
    
    
        System.out.println(this.mm.getName() + ",你好!送你鲜花");
    }

    @Override
    public void giveChocolate() {
    
    
        System.out.println(this.mm.getName() + ",你好!送你巧克力");
    }
}

Proxy class, which defines the real entity represented by Proxy

public class Proxy implements IGiveGift{
    
    

    private Persuit gg;

    public Proxy(SchoolGirl mm){
    
    
        this.gg = new Persuit(mm);
    }


    @Override
    public void giveDolls() {
    
    
        this.gg.giveDolls();
    }

    @Override
    public void giveFlowers() {
    
    
        this.gg.giveFlowers();
    }

    @Override
    public void giveChocolate() {
    
    
        this.gg.giveChocolate();
    }
}

client code

public class ProcyClient {
    
    
    public static void main(String[] args) {
    
    
        SchoolGirl girlLjj = new SchoolGirl();
        girlLjj.setName("李娇娇");
        Proxy boyDl = new Proxy(girlLjj);
        boyDl.giveDolls();
        boyDl.giveFlowers();
        boyDl.giveChocolate();
    }
}

Application of proxy mode

1. <u>远程代理</u>,也就是为一个对象在不同的地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实。
 当我在项目中加入一个WebService,此时会在项目中生成一个wsd1文件和一些相关文件,其实它们就是代理,这就使得客户端程序调用代理就可以解决远程访问的问题。
2. <u>虚拟代理</u>,是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象 。这样就可以达到性能的最优化,比如说你打开一个很大的HTML网页时,里面可能有很多的文字和图片,但你还是可以很快打开它,此时你所看到的是所有的文字,但图片却是一张一张地下载后才能看到。那些未打开的图片框,就是通过虚拟代理来替代了真实的图片,此时代理存储了真实图片的路径和尺寸。
3. <u>安全代理</u>,用来控制真实对象访问时的权限。一般用于对象应该有不同的访问权限的时候。
4. <u>智能指引</u>,是指当调用真实的对象时,代理处理另外一些事。

The proxy mode actually introduces a certain degree of indirection when accessing objects, because of this indirection, it can be used for multiple purposes. Proxies are representations of real objects.

Guess you like

Origin blog.csdn.net/weixin_42322991/article/details/128354665