Design Patterns (Java): Preface (UML Class Diagram, Seven Principles)

In the process of writing software, programmers face many challenges from coupling, cohesion, maintainability, scalability, reusability, flexibility, etc. The design pattern is to make the program (software) better:

  • Code reusability (i.e. code with the same function does not need to be written multiple times)
  • Readability (ie: programming normative, easy for other programmers to read and understand)
  • Scalability (ie: when new functions need to be added, it is very convenient, called maintainability)
  • Reliability (ie: when we add new functions, there is no effect on the original functions)
  • Make the program present the characteristics of high cohesion and low coupling

1. Seven principles

The design pattern principle is actually the principle that programmers should follow when programming, and it is also the basis of various design patterns.

1.1 Single Responsibility Principle

overview

A class should only be responsible for one responsibility . If a class is responsible for two responsibilities, modifying the functionality of one of them may affect the other.

example

Option One:

/**
 * 示例单一原则
 * @author cVzhanshi
 * @create 2023-01-29 20:23
 */
public class single {
    
    
    public static void main(String[] args) {
    
    
        Vehicle vehicle = new Vehicle();
        vehicle.run("摩托车");
        vehicle.run("轮船");
        vehicle.run("飞机");
    }
}


class Vehicle{
    
    
    public void run (String vehicle){
    
    
        System.out.println(vehicle + "在公路上走...");
    }
}

// 运行结果
// 摩托车在公路上走...
// 轮船在公路上走...
// 飞机在公路上走...

analyze:

  1. In the run method of Example 1, the single responsibility principle is violated
  2. The solution is very simple. According to the different operation methods of the vehicles, they can be decomposed into different categories.

Option II:

class RoadVehicle {
    
    
    public void run(String vehicle) {
    
    
        System.out.println(vehicle + "公路运行");
    }
}
class AirVehicle {
    
    
    public void run(String vehicle) {
    
    
        System.out.println(vehicle + "天空运行");
    }
}

class WaterVehicle {
    
    
    public void run(String vehicle) {
    
    
        System.out.println(vehicle + "水中运行");
    }
}

analyze:

  1. Follow the Single Responsibility Principle
  2. The change is very big, that is, the class will be decomposed and the client will be modified at the same time
  3. Improvement: Modify the Vehicle class directly, and the code to be changed will be less

third solution:

class Vehicle2 {
    
    
    public void run(String vehicle) {
    
    
        //处理
        System.out.println(vehicle + " 在公路上运行....");
    }
    public void runAir(String vehicle) {
    
    
        System.out.println(vehicle + " 在天空上运行....");
    }
    public void runWater(String vehicle) {
    
    
        System.out.println(vehicle + " 在水中行....");
    }
}

analyze:

  1. This modification method does not make major modifications to the original class, but only adds methods
  2. Although the single responsibility principle is not followed at the class level, it is still a single responsibility at the method level.

Single Responsibility Principle Considerations and Details

  • Reduce the complexity of the class, a class is responsible for only one responsibility.
  • Improve the readability and maintainability of the class
  • Reduce the risk of change
  • Under normal circumstances, we should abide by the single responsibility principle. Only when the logic is simple enough can the single responsibility principle be violated at the code level; only when the number of methods in the class is small enough can the single responsibility principle be maintained at the method level (such as the above scheme three, if There are other methods in Scheme 3, which violates the principle of single responsibility)

1.2 Interface Segregation Principle

overview

The client should not depend on interfaces it does not need, that is, the dependence of a class on another class should be established on the smallest interface

example

illustrate:

  • There are five methods in interface A
  • Class D relies on class B to use methods 1, 2, and 3 in the interface
  • Class E relies on class C to use methods 1, 4, and 5 in the interface
/**接口隔离原则示例
 * @author cVzhanshi
 * @create 2023-01-30 15:44
 */
public class demo01 {
    
    
    public static void main(String[] args) {
    
    
        B b = new B();
        D d = new D();
        d.depend1(b);
        d.depend2(b);
        d.depend3(b);

        C c = new C();
        E e = new E();
        e.depend1(c);
        e.depend4(c);
        e.depend5(c);

    }
}

class D{
    
    
    public void depend1(A a){
    
    
        a.function1();
    }
    public void depend2(A a){
    
    
        a.function2();
    }
    public void depend3(A a){
    
    
        a.function3();
    }
}

class E{
    
    
    public void depend1(A a){
    
    
        a.function1();
    }
    public void depend4(A a){
    
    
        a.function4();
    }
    public void depend5(A a){
    
    
        a.function5();
    }
}


class C implements A{
    
    
    @Override
    public void function1() {
    
    
        System.out.println("C 实现了 function1");
    }

    @Override
    public void function2() {
    
    
        System.out.println("C 实现了 function2");
    }

    @Override
    public void function3() {
    
    
        System.out.println("C 实现了 function3");
    }

    @Override
    public void function4() {
    
    
        System.out.println("C 实现了 function4");
    }

    @Override
    public void function5() {
    
    
        System.out.println("C 实现了 function5");
    }
}


class B implements A{
    
    

    @Override
    public void function1() {
    
    
        System.out.println("B 实现了 function1");
    }

    @Override
    public void function2() {
    
    
        System.out.println("B 实现了 function2");
    }

    @Override
    public void function3() {
    
    
        System.out.println("B 实现了 function3");
    }

    @Override
    public void function4() {
    
    
        System.out.println("B 实现了 function4");
    }

    @Override
    public void function5() {
    
    
        System.out.println("B 实现了 function5");
    }
}

interface A{
    
    
    void function1();
    void function2();
    void function3();
    void function4();
    void function5();
}

analyze:

  • If interface A is not the minimum interface for classes D and E, then classes B and C must implement methods they don't need. So it violates the interface segregation principle

  • Improvement: Split interface A into several independent interfaces, and class D and class E respectively establish dependencies with the interfaces they need. Adopt the interface isolation principle

  • Split details:

    • Method 1 exists in interface A, because both classes D and E use
    • Interface B exists method 2, 3, because class D alone uses
    • Interface C has methods 4 and 5, because class E alone uses

    In this way, classes D and E depend on do not need to implement methods they do not need

sample code

/**
 * 接口隔离原则示例
 * @author cVzhanshi
 * @create 2023-01-30 15:57
 */
public class demo02 {
    
    
    public static void main(String[] args) {
    
    
        D d = new D();
        E e = new E();
        F f = new F();
        G g = new G();
        
        d.depend1(f);
        d.depend2(f);
        d.depend3(f);
        
        e.depend1(g);
        e.depend4(g);
        e.depend5(g);
    }
}

class D{
    
    
    public void depend1(F f){
    
    
        f.function1();
    }

    public void depend2(F f){
    
    
        f.function2();
    }

    public void depend3(F f){
    
    
        f.function3();
    }
}

class E{
    
    
    public void depend1(G g){
    
    
        g.function1();
    }

    public void depend4(G g){
    
    
        g.function4();
    }

    public void depend5(G g){
    
    
        g.function5();
    }
}

// 依赖类
class F implements A, B{
    
    

    @Override
    public void function1() {
    
    
        System.out.println("F 实现了 function1");
    }

    @Override
    public void function2() {
    
    
        System.out.println("F 实现了 function2");
    }

    @Override
    public void function3() {
    
    
        System.out.println("F 实现了 function3");
    }
}

// 依赖类
class G implements A, C{
    
    

    @Override
    public void function1() {
    
    
        System.out.println("G 实现了 function1");
    }

    @Override
    public void function4() {
    
    
        System.out.println("G 实现了 function4");
    }

    @Override
    public void function5() {
    
    
        System.out.println("G 实现了 function5");
    }
}

interface A{
    
    
    void function1();
}

interface B{
    
    
    void function2();
    void function3();
}

interface C{
    
    
    void function4();
    void function5();
}

1.3 Dependency Inversion Principle

overview

  • High-level modules should not depend on low-level modules, both should depend on its abstractions
  • Abstractions should not depend on details, details should depend on abstractions
  • The central idea of ​​dependency inversion (inversion) is interface-oriented programming
  • The principle of dependency inversion is based on the design concept: Compared with the variability of details, abstract things are much more stable. An architecture based on abstractions is much more stable than an architecture based on details. In java, abstraction refers to interfaces or abstract classes, and details refer to concrete implementation classes
  • The purpose of using interfaces or abstract classes is to formulate specifications without involving any specific operations, and leave the task of showing details to the implementation class to complete

example

Example one:

/**
 * 依赖倒转原则示例
 * @author cVzhanshi
 * @create 2023-01-30 20:24
 */
public class demo01 {
    
    
    public static void main(String[] args) {
    
    
        Person person = new Person();
        person.receive(new Email());
    }
}

class Email{
    
    
    public String getInfo(){
    
    
        return "电子邮箱信息:hello";
    }
}


class Person{
    
    
    public void receive(Email email){
    
    
        System.out.println(email.getInfo());
    }
}

analyze:

  1. If we want to get the information of WeChat, SMS, etc., we need to add new classes, and at the same time, Perons should also add corresponding receiving methods
  2. Improvement : Introduce an abstract interface IReceiver to represent the receiver, so that the Person class is dependent on the interface IReceiver . Email, WeiXin, etc. belong to the scope of receiving, and they can implement the IReceiver interface respectively, so the client does not need to be changed . This way we comply with the Dependency Inversion Principle

Example two:

/**
 * @author cVzhanshi
 * @create 2023-01-30 20:31
 */
public class demo02 {
    
    
    public static void main(String[] args) {
    
    
        Person person = new Person();
        person.receive(new WeChat());
        person.receive(new Email());
    }
}

interface IReceive{
    
    
    String getInfo();
}

class Email implements IReceive{
    
    
    @Override
    public String getInfo() {
    
    
        return "电子邮箱信息:hello";
    }
}

class WeChat implements IReceive{
    
    
    @Override
    public String getInfo() {
    
    
        return "微信信息:weChat";
    }
}

class Person{
    
    
    public void receive(IReceive receive){
    
    
        System.out.println(receive.getInfo());
    }
}

Three ways of dependency transfer

interface passing

/**
 * @author cVzhanshi
 * @create 2023-01-30 20:31
 */
public class demo02 {
    
    
    public static void main(String[] args) {
    
    
        Person person = new Person();
        person.receive(new WeChat());
        person.receive(new Email());
    }
}

interface IReceive{
    
    
    String getInfo();
}

class Email implements IReceive{
    
    
    @Override
    public String getInfo() {
    
    
        return "电子邮箱信息:hello";
    }
}

class WeChat implements IReceive{
    
    
    @Override
    public String getInfo() {
    
    
        return "微信信息:weChat";
    }
}

class Person{
    
    
    public void receive(IReceive receive){
    
    
        System.out.println(receive.getInfo());
    }
}

Constructor pass

/**
 * @author cVzhanshi
 * @create 2023-01-30 20:31
 */
public class demo02 {
    
    
    public static void main(String[] args) {
    
    
        EmailClass emailClass = new EmailClass();
        Person person = new Person(emailClass);
        person.getInfo();

    }
}

interface IReceive{
    
    
    void getInfo();
}

interface Email{
    
    
    public void getMessage();
}

class EmailClass implements Email{
    
    

    @Override
    public void getMessage() {
    
    
        System.out.println("EmailClass messages");
    }
}

class Person implements IReceive{
    
    
    private Email email;

    public Person(Email email) {
    
    
        this.email = email;
    }

    @Override
    public void getInfo() {
    
    
        email.getMessage();
    }
}

passed through the setter method

/**
 * @author cVzhanshi
 * @create 2023-01-30 20:31
 */
public class demo02 {
    
    
    public static void main(String[] args) {
    
    
        EmailClass emailClass = new EmailClass();
        Person person = new Person();
        person.setEmail(emailClass);
        person.getInfo();

    }
}

interface IReceive{
    
    
    void getInfo();
}

interface Email{
    
    
    public void getMessage();
}

class EmailClass implements Email{
    
    

    @Override
    public void getMessage() {
    
    
        System.out.println("EmailClass messages");
    }
}

class Person implements IReceive{
    
    
    private Email email;

    public void setEmail(Email email) {
    
    
        this.email = email;
    }

    @Override
    public void getInfo() {
    
    
        email.getMessage();
    }
}

Notes and details of the Dependency Inversion Principle

  • Low-level modules should have abstract classes or interfaces, or both, for better program stability
  • The declaration type of the variable should be an abstract class or interface as much as possible , so that there is a buffer layer between our variable reference and the actual object, which is conducive to program expansion and optimization
  • Follow the Liskov substitution principle when inheriting

1.4 Liskov Substitution Principle

overview

While inheritance brings convenience to program design, it also brings disadvantages. For example, the use of inheritance will bring intrusion to the program , reduce the portability of the program, and increase the coupling between objects . If a class is inherited by other classes, when this class needs to be modified, all subclasses must be considered , and after the parent class is modified, all functions related to the subclass may fail.

Liskov Substitution Principle

  • All places that refer to the base class must be able to transparently use objects of its subclasses

  • When using inheritance, follow the Liskov substitution principle, and try not to override the methods of the parent class in the subclass

  • Inheritance actually enhances the coupling of two classes, and under appropriate circumstances, problems can be solved through aggregation, composition, and dependency .

That is to say, use inheritance not to destroy the original things, and not to bring intrusion to the program.

example

Example one:

public class demo01 {
    
    
    public static void main(String[] args) {
    
    
        A a = new A();
        System.out.println("11-3=" + a.func1(11, 3));  // 11-3=8
        System.out.println("1-8=" + a.func1(1, 8));    // 1-8=-7
        System.out.println("-----------------------");
        B b = new B();
        System.out.println("11-3=" + b.func1(11, 3));   // 11-3=14    
        System.out.println("1-8=" + b.func1(1, 8));     // 1-8=9
        System.out.println("11+3+9=" + b.func2(11, 3)); // 11+3+9=23
    }
}

class A {
    
    
    public int func1(int num1, int num2) {
    
    
        return num1 - num2;
    }
}


class B extends A {
    
    
    public int func1(int a, int b) {
    
    
        return a + b;
    }

    public int func2(int a, int b) {
    
    
        return func1(a, b) + 9;
    }
}

Explanation: Because class B rewrites the method of class A without knowing it, the subtraction of class A is changed into addition. It caused the main function class B to use subtraction to make an error. This is the invasiveness brought about by inheritance.

Transformation : Both the original parent class and subclass inherit a more popular base class, the original inheritance relationship is removed, and the dependencies, aggregation, combination and other relationships are used instead.

public class demo02 {
    
    
    public static void main(String[] args) {
    
    
        A a = new A();
        System.out.println("11-3=" + a.func1(11, 3));  // 11-3=8
        System.out.println("1-8=" + a.func1(1, 8));    // 1-8=-7
        System.out.println("-----------------------");
        B b = new B();
        System.out.println("11-3=" + b.func3(11, 3));   // 11-3=8
        System.out.println("1-8=" + b.func3(1, 8));     // 1-8=-7
        System.out.println("11+3+9=" + b.func2(11, 3)); // 11+3+9=23
    }
}

class Base {
    
    

}
class A extends Base {
    
    
    public int func1(int num1, int num2) {
    
    
        return num1 - num2;
    }
}


class B extends Base {
    
    
    private A a = new A();
    public int func1(int a, int b) {
    
    
        return a + b;
    }

    public int func2(int a, int b) {
    
    
        return func1(a, b) + 9;
    }

    public int func3(int a, int b) {
    
    
        return this.a.func1(a, b);
    }
}

1.5 Principle of opening and closing

overview

  • Modules and functions should be open for extension (for the provider) and closed for modification (for the consumer) . Build frameworks with abstractions and extend details with implementations.
  • When the software needs to change, try to implement the change by extending the behavior of the software entity instead of modifying the existing code .

example

Code example one:

public class demo01 {
    
    
    public static void main(String[] args) {
    
    
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
    }
}

// 使用方  这是一个用于绘图的类
class GraphicEditor {
    
    
    public void drawShape(Shape s) {
    
    
        if (s.m_type == 1)
            drawRectangle(s);
        else if (s.m_type == 2)
            drawCircle(s);
    }

    public void drawRectangle(Shape r) {
    
    
        System.out.println("矩形");
    }

    public void drawCircle(Shape r) {
    
    
        System.out.println("圆形");
    }
}

class Shape {
    
    
    int m_type;
}

class Rectangle extends Shape {
    
    
    Rectangle() {
    
    
        super.m_type = 1;
    }
}

class Circle extends Shape {
    
    
    Circle() {
    
    
        super.m_type = 2;
    }
}

illustrate:

  • The advantage is that it is easy to understand and easy to operate.
  • The disadvantage is that it violates the ocp principle of the design pattern, that is, it is open to extension (provider) and closed to modification (user). That is, when we add new functions to the class, try not to modify the code, or modify the code as little as possible.
  • For example, if we want to add a new graphics type triangle at this time, we need to make the following modifications, and there are many places to modify

Modification : Idea: Make the created Shape class an abstract class, and provide an abstract draw method, which can be implemented by subclasses. In this way, when we have a new graphic type, we only need to let the new graphic class inherit Shape and implement The draw method is enough, and the code of the user does not need to be modified -> the principle of opening and closing is satisfied

/**
 * @author cVzhanshi
 * @create 2023-03-14 10:12
 */
public class demo02 {
    
    
    public static void main(String[] args) {
    
    
        Editor editor = new Editor();

        editor.drawShape(new A());
        editor.drawShape(new B());
    }
}

class Editor{
    
    
    public void drawShape(Shape s){
    
    
        s.draw();
    }
}
abstract class Shape{
    
    
    public abstract void draw();
}

class A extends Shape {
    
    
    @Override
    public void draw() {
    
    
        System.out.println("画A");
    }
}

class B extends Shape {
    
    
    @Override
    public void draw() {
    
    
        System.out.println("画B");
    }
}

1.6 Law of Demeter

overview

  • An object should keep a minimum of knowledge about other objects
  • The closer the relationship between classes and classes, the greater the degree of coupling
  • Demeter Principle (Demeter Principle) is also called the principle of least knowledge, that is, the less a class knows about the classes it depends on, the better. That is to say, no matter how complex the dependent class is, try to encapsulate the logic inside the class. Except for the public methods provided, no information will be disclosed to the outside world.
  • There is also a simpler definition of Demeter's Law: only communicate with immediate friends

Direct friends : Every object has a coupling relationship with other objects. As long as there is a coupling relationship between two objects, we say that the two objects are friends. There are many ways of coupling, such as dependence, association, combination, aggregation, etc. Among them, we call the classes that appear in member variables, method parameters, and method return values ​​as direct friends , while the classes that appear in local variables are not direct friends. That is to say, unfamiliar classes are best not to appear inside the class in the form of local variables .

example

Incorrect example, violated the law of Demeter

class Test {
    
    
    public void test() {
    
    
        System.out.println("测试");
    }
}


class Apple {
    
    
    public void test() {
    
    
        // 陌生的类最好不要以局部变量的形式出现在类的内部
        Test test = new Test();
        test.test();
    }
}

revision

class Test {
    
    
    public void test() {
    
    
        System.out.println("测试");
    }
}


class Apple {
    
    
    public void test(Test test) {
    
    
        test.test();
    }
}

Dimit's Law Notes and Details

  • The core of Dimit's law is to reduce the coupling between classes.
  • But note: Since unnecessary dependencies are reduced for each class, Dimiter's law only requires the reduction of inter-class (inter-object) coupling relationships, and does not require no dependencies at all.

1.7 Synthesis and reuse principles

Try to use composition or aggregation instead of aggregation

1.8 Core idea of ​​design principles

  • Find out where changes may be needed in the application, isolate them, and don't mix them with code that doesn't need to be changed .
  • Program to the interface , not the implementation.
  • Strive for loosely coupled design between interacting objects .

Two, UML class diagram

Used to describe the composition of classes (objects) in the system and various static relationships between classes (objects)

Relationships between classes: dependency, generalization (inheritance), implementation, association, aggregation, and composition

  • Class : The class diagram is divided into three layers. The first layer is the class name. If it is an abstract class, it will be expressed in italics; the second layer represents attributes or fields; the third layer is the operation of the class, that is, the method. Symbols will be used in front of the method to indicate permission modification character, '+' means public, '-' means private, and '#' means protected.

    insert image description here

  • Interface : The difference between an interface and a class diagram is that it is <<interface>>displayed on the top, and the second line represents the method of the interface; the interface also has a second representation method ( lollipop notation ), the interface name is represented on the side of the circle, and the method of the interface is then Inside the implementation class.

    insert image description here

  • Inheritance relationship : hollow triangle + solid line

    insert image description here

  • Realization relationship : hollow triangle + dotted line

    insert image description here

  • Association relationship : solid line + arrow

    insert image description here

  • Aggregation relationship : hollow rhombus + realization arrow; aggregation represents a weak "owning" relationship, which means that A object can contain B object, but B object is not part of A object

    Hollow diamond + solid arrow means

    insert image description here

  • Composition (synthesis) relationship : Composition is a strong "owning" relationship, which embodies a strict relationship between parts and the whole, and the life cycle of parts and the whole is the same . Combination relationships (association and aggregation relationships can also have cardinalities) also have cardinalities, indicating corresponding relationships.

    Solid diamond + solid arrow

    insert image description here

  • Dependencies : dotted line + arrow

    Such as: the class uses the other party, member attributes, the return type of the method, the parameter type received by the method, and the method used in the method

    insert image description here

    insert image description here

3. Introduction to Design Patterns

  • Design pattern is the useful experience summed up by programmers in the face of similar software engineering design problems. Patterns are not codes, but general solutions to certain types of problems. Design patterns represent best practices.
  • The nature of design patterns improves software maintainability, versatility, and scalability, and reduces software complexity.

Design patterns are divided into three types, a total of 23 types

  • Creational patterns: singleton pattern , abstract factory pattern, prototype pattern, builder pattern, factory pattern .
  • Structural patterns: Adapter pattern, Bridge pattern, Decoration pattern , Composite pattern, Appearance pattern, Flyweight pattern, Proxy pattern.
  • Behavioral patterns: template method pattern, command pattern, visitor pattern, iterator pattern, observer pattern , intermediary pattern, memo pattern, interpreter pattern (Interpreter pattern), state pattern, strategy pattern, responsibility chain pattern (responsibility chain model).

Guess you like

Origin blog.csdn.net/qq_45408390/article/details/131385665