Basic overview of design patterns

1. What is design mode

  • In software engineering, design patterns are common solutions to problems that are common in software design. The term was introduced into computer science in the 1990s by Alice Gamma et al. from the field of architecture design.
  • Design Patterns are useful experiences that people have learned in the face of similar types of software engineering design problems. These experiences form a thinking mode. Note that patterns are not codes, but generic design solutions to certain types of problems.
  • We know that the three elements of software engineering methodology are process, tool and method, and design pattern is just like a specific method and tool. Its main purpose is to make the design of software engineering get a good improvement and Optimization in terms of maintainability, extensibility, variability and complexity.

2. Benefits of design patterns

  • Enhances code reuse
  • Enhances code flexibility
  • Can improve code readability
  • Can improve system scalability
  • Can improve system robustness
  • Can improve system stability
  • Ability to improve software maintainability
  • Can improve system development efficiency

3. Basic Principles of Design Mode

1. Opening and closing principle

The Open and Close Principle means to be open to extensions and closed to modifications. When software needs to be changed, try to make changes by extending the behavior of software entities instead of modifying existing code. It is also the most basic and important design principle.

  • Code Samples

    Breach the Open and Close Principle

    public class A{
        public static void main(String[] args){
            DrawShape d = new DrawShape();
            d.draw(new Triangle());
            d.draw(new Circle());
        }
    }
    
    class Shape{
        int shapeType;
    }
    
    class Triangle extends Shape{
        public Triangle(){
            super.shapeType = 1;
        }
    }
    
    class Circle extends Shape{
        public Triangle(){
            super.shapeType = 2;
        }
    }
    
    class DrawShape{
        public void draw(Shape shape){
            if(shape.shapeType == 1){
                drawTriangle();
            }else if(shape.shapeType == 2){
                drawCircle();
            }
        }
        
        private void drawTriangle(){
            System.out.println("Draw a triangle");
        }
        
        private void drawCircle(){
            System.out.println("Draw a circle");
        }
    }
    

    Follow the open and close principle

    public class A{
        public static void main(String[] args){
            DrawShape d = new DrawShape();
            d.draw(new Triangle());
            d.draw(new Circle());
        }
    }
    
    abstract class Shape{
        public abstract void draw(){
            System.out.println("Draw default graphics");
        }
    }
    
    class Triangle extends Shape{
        public void draw(){
            System.out.println("Draw a triangle");
        }
    }
    
    class Circle extends Shape{
        public void draw(){
            System.out.println("Draw a circle");
        }
    }
    
    class DrawShape{
        public void draw(Shape shape){
            shape.draw();
        }
    }
    
  • Functional advantages

    • Ability to make applications easier to maintain and extend

2. Richter Replacement Principle

The Richter replacement principle is that inheritance must ensure that the properties of the parent class are still valid in the subclasses, that is, where the base class is present, its subclasses must be present, and the subclasses can extend the functions of the base class, but try not to override the functions of the base class, but this can be solved by aggregation, composition, dependency, and so on.

  • Code Samples

    Violation of Richter Replacement

    public class A{
        public static void main(String[] args){
            B b = new B();
            System.out.println("5 + 6 =" + b.fun1());
            System.out.println("5 * 6 =" + b.fun2());
        }
    }
    
    class A{
        public int fun1(int a, int b){
            return a + b;
        }
    }
    
    class B extends A{
        public int fun1(int a, int b){
            return a - b;
        }
        
        public int fun2(int a, int b){
            return a * b;
        }
    }
    

    Follow Richter Replacement

    public class A{
        public static void main(String[] args){
            B b = new B();
            System.out.println("5 + 6 =" + b.fun3());
            System.out.println("5 * 6 =" + b.fun2());
        }
    }
    
    class Base(){
        
    }
    
    class A extends Base{
        public int fun1(int a, int b){
            return a + b;
        }
    }
    
    class B extends Base{
        private A a = new A();
        
        public int fun1(int a, int b){
            return a - b;
        }
        
        public int fun2(int a, int b){
            return a * b;
        }
        
        public int fun3(int a, int b){
            return a.fun1(a, b);
        }
    }
    
  • Functional advantages

    • We can regulate the use of inheritance in the right place without overflowing it

3. Dependence on the principle of inversion

  • The principle of dependency inversion is the basis of the open-close principle, that is, when we write object-oriented applications, we need to program interfaces or abstract classes instead of relying specifically on an implementation class. High-level modules should not rely on low-level modules, and abstraction should not rely on detail.

    • Code Samples

      Reverse Dependency

      public class A{
          public static void main(String[] args){
              Person p = new Person();
              p.receiveMessage(new Email());
              p.receiveMessage(new Phone());
          }
      }
      
      class Email{
          public String getInfo(){
              return "Mail Information";
          }
      }
      
      class Phone{
          public String getInfo(){
              return "Mobile SMS";
          }
      }
      
      class Person{
          public void receiveMessage(Email email){
              System.out.println(email.getInfo());
          }
          
          public void receiveMessage(Phone phone){
              System.out.println(phone.getInfo());
          }
      }
      

      Follow Dependency Inversion

      public class A{
          public static void main(String[] args){
              Person p = new Person();
              p.receiveMessage(new Email());
              p.receiveMessage(new Phone());
          }
      }
      
      interface Message{
          String getInfo();
      }
      
      class Email implements Message{
          public String getInfo(){
              return "Mail Information";
          }
      }
      
      class Phone implements Message{
          public String getInfo(){
              return "Mobile SMS";
          }
      }
      
      class Person{
          public void receiveMessage(Message message){
              System.out.println(message.getInfo());
          }
      }
      
    • Functional advantages

      • Improve system stability
      • Improved system maintainability
      • Improved system scalability
    • Dependency Transfer Mode

      • Pass through interface

        interface Message{
            void getInfo(Mechain mechain);
        }
        
        interface Mechain{
            void do();
        }
        
        class Person implements Message{
            public void getInfo(Mechain mechain){
                System.out.println(mechain.do());
            }
        }
        
      • Pass Through Constructor

        interface Message{
            void getInfo();
        }
        
        interface Mechain{
            void do();
        }
        
        class Person implements Message{
            private Mechain mechain;
            
            public Person(Mechain mechain){
                this.mechain = mechain;
            }
            
            public void getInfo(){
                System.out.println(this.mechain.do());
            }
        }
        
      • Pass through setter

        interface Message{
            void getInfo();
        }
        
        interface Mechain{
            void do();
        }
        
        class Person implements Message{
            private Mechain mechain;
            
            public void getInfo(){
                System.out.println(this.mechain.do());
            }
            
            public setMechain(Mechain mechain){
                this.mechain = mechain;
            }
        }
        

4. Principle of Single Responsibility

The single responsibility principle is that a class should have and only have one reason to cause it to change, that is, only one responsibility, otherwise it should be split. Why is a class not allowed to take on multiple responsibilities, because if one of the responsibilities is modified, the other responsibilities may also be modified, which may lead to errors in the execution of other responsibilities.

  • Code Samples

    Violation of a single responsibility

    public class A{
        public static void main(String[] args){
            Animal a1 = new Animal();
            a1.run("zebra");
            a1.run("Whale");
            a1.run("Eagle");
        }
    }
    
    class Animal{
        public void run(String name){
            System.out.println(name + "Run on the ground");
        }
    }
    

    Follow a single responsibility

    public class A{
        public static void main(String[] args){
            RoadAnimal a1 = new RoadAnimal();
            a1.run("zebra");
    
            AirAnimal a2 = new AirAnimal();
            a2.run("Eagle");
    
            WaterAnimal a3 = new WaterAnimal();
            a3.run("Whale");
        }
    }
    
    class RoadAnimal{
        public void run(String name){
            System.out.println(name + "Run on the ground");
        }
    }
    
    class AirAnimal{
        public void run(String name){
            System.out.println(name + "Fly in the sky");
        }
    }
    
    class WaterAnimal{
        public void run(String name){
            System.out.println(name + "Swimming in water");
        }
    }
    
  • Functional advantages

    • Reduced code complexity
    • Reduce the risk of code changes
    • Improved code readability
    • Enhanced high cohesion and low coupling of the system

5. Interface Isolation Principle

The principle of interface isolation is that when an interface has too many functions and responsibilities, we need to divide the large interface into several small interfaces, each of which serves only the related functions of its corresponding client, and should not let the client rely on functions that are not needed.

  • Code Samples

    Breach Interface Isolation

    public class Example{
        public static void main(String[] args){
            A a = new A(); // A depends on B, but using only methods 1, 2, 3, 4, and 5 is wasteful
            a.execute1(new B());
            a.execute2(new B());
            a.execute3(new B());
    		
            C c = new C(); // C depends on D, but using methods 1, 4, 5 only, 2 and 3 causes waste
            c.exceute1(new D());
            c.exceute4(new D());
            c.exceute5(new D());
        }
    }
    
    interface Interface1{
        void operation1();
        void operation2();
        void operation3();
        void operation4();
        void operation5();
    } 
    
    class B implements Interface1{
        public void operation1(){}
        public void operation2(){}
        public void operation3(){}
        public void operation4(){}
        public void operation5(){}
    }
    
    class D implements Interface1{
        public void operation1(){}
        public void operation2(){}
        public void operation3(){}
        public void operation4(){}
        public void operation5(){}
    }
    
    class A{
        public void execute1(Interface1 i1){ i1.operation1(); }
        public void execute2(Interface1 i1){ i1.operation2(); }
        public void execute3(Interface1 i1){ i1.operation3(); }
    }
    
    class C{
        public void execute1(Interface1 i1){ i1.operation1(); }
        public void execute4(Interface1 i1){ i1.operation4(); }
        public void execute5(Interface1 i1){ i1.operation5(); }
    }
    

    Follow interface isolation

    public class Example{
        public static void main(String[] args){
            A a = new A();
            a.execute1(new B());
            a.execute2(new B());
            a.execute3(new B());
    		
            C c = new C();
            c.exceute1(new D());
            c.exceute4(new D());
            c.exceute5(new D());
        }
    }
    
    interface Interface1{
        void operation1();
    } 
    
    interface Interface2{
        void operation2();
        void operation3();
    } 
    
    interface Interface3{
        void operation4();
        void operation5();
    } 
    
    class B implements Interface1,Interface2{
        public void operation1(){}
        public void operation2(){}
        public void operation3(){}
    }
    
    class D implements Interface1,Interface3{
        public void operation1(){}
        public void operation4(){}
        public void operation5(){}
    }
    
    class A{
        public void execute1(Interface1 i1){ i1.operation1(); }
        public void execute2(Interface2 i2){ i2.operation2(); }
        public void execute3(Interface2 i2){ i2.operation3(); }
    }
    
    class C{
        public void execute1(Interface1 i1){ i1.operation1(); }
        public void execute4(Interface3 i3){ i3.operation4(); }
        public void execute5(Interface3 i3){ i3.operation5(); }
    }
    
  • Functional advantages

    • Avoid having many different responsibilities in an interface, and make each interface more distinct
    • Enhanced high cohesion and low coupling of the system

6. Principles of Composite Multiplexing

The principle of composite reuse is that when we need to reuse some system's code, we should give priority to combining or aggregating, and then inheriting. Inheritance causes unnecessary trouble if there are too many functions in a parent class and we only want to reuse a small portion of them.

  • Code Samples

    Composite reuse violation

    public class A{
        public static void main(String[] args){
            Bird bird = new Bird();
            bird.talk();
            bird.eat();
            bird.fly();
        }
    }
    
    class Action{
        public void eat(){
            System.out.println("Having dinner");
        }
        
        public void fly(){
            System.out.println("Fly in the sky");
        }
        
        public void swim(){
            System.out.println("Water swimming");
        }
        
        public void run(){
            System.out.println("Run on the ground");
        }
    }
    
    class Bird extends Action{
        public void talk(){
            System.out.println("call");
        }
        
        public void eat(){
            super.eat();
        }
        
        public void fly(){
            super.fly();
        }
    }
    

    Follow Composite Reuse

    public class A{
        public static void main(String[] args){
            Bird bird = new Bird();
            bird.talk();
            bird.eat();
            bird.fly();
        }
    }
    
    class Action{
        public void eat(){
            System.out.println("Having dinner");
        }
        
        public void fly(){
            System.out.println("Fly in the sky");
        }
        
        public void swim(){
            System.out.println("Water swimming");
        }
        
        public void run(){
            System.out.println("Run on the ground");
        }
    }
    
    class Bird{
        private Action action = new Action();
        
        public void talk(){
            System.out.println("call");
        }
        
        public void eat(){
            action.eat();
        }
        
        public void fly(){
            action.fly();
        }
    }
    
  • Functional advantages

    • Enhances system maintainability
    • Can improve code readability

7. Dimitt's Law

Dimitt's rule, also known as the principle of least knowledge, means "only talk to friends, not to strangers",The less a class knows about its dependent classes, the better it can do. Try to encapsulate implementation logic inside the class and expose public methods to the outside without disclosing internal information. The meaning of this sentence is that if there is no need for direct communication between two classes, then direct calls should not occur between them, but instead forward communication by providing another method. If one class contains other classesObject dependency, then this object in the form of member variables, method parameters, method return values is a friend, this object in the form of local variables is a stranger.

  • Code Samples

    Violation of Dimitt's Law

    public class A{
        public static void main(String[] args){
            School school = new School();
            school.printAllName(new TeacherManager(), new StudentManager());
        }
    }
    
    class Teacher{}
    
    class Student{}
    
    class TeacherManager{
        public List<Teacher> getTeachers(){
            return new ArrayList<Teacher>();
        }
    }
    
    class StudentManager{
        public List<Student> getStudents(){
            return new ArrayList<Student>{};
        }
    }
    
    class School{
        public void printAllName(TeacherManager tm, StudentManager sm){
            List<Teacher> teachers = tm.getTeachers();
            teachers.foreach(System.out::println);
            
            List<Student> students = sm.getStudents();
            students.foreach(System.out::println);
        }
    }
    

    Follow Dimitt's Law

    public class A{
        public static void main(String[] args){
            School school = new School();
            school.printAllName(new TeacherManager(), new StudentManager());
        }
    }
    
    class Teacher{}
    
    class Student{}
    
    class TeacherManager{
        private List<Teacher> getTeachers(){
            return new ArrayList<Teacher>();
        }
        
        public void printTeachers(){
            List<Teacher> teachers = this.getTeachers();
            teachers.foreach(System.out::println);
        }
    }
    
    class StudentManager{
        private List<Student> getStudents(){
            return new ArrayList<Student>{};
        }
        
        public void printStudents(){
            List<Student> students = this.getStudents();
            students.foreach(System.out::println);
        }
    }
    
    class School{
        public void printAllName(TeacherManager tm, StudentManager sm){
            tm.printTeachers();
    		sm.printStudents();
        }
    }
    
  • Functional advantages

    • Decrease system coupling
    • Reduce the degree of correlation between systems

4. What are the categories of design patterns?

1. Creative mode

Creative mode abstracts the instantiation process of a class and separates the creation of objects in a software module from the use of objects. In order to make the structure of the software clearer, the outside world only needs to know their common interfaces, not their specific implementation details, which also makes the design of the whole system more in line with the single responsibility principle..

NameCore role
Singleton modeEnsure that a class has only one instance and provide a global access point to the instance
Factory ModeInstead of showing the user internal details when creating an object, provide a common interface for creating the object
Abstract FactoryProvides a unified interface for creating related object families
Builder ModeEncapsulate the construction process of an object and follow it to create the corresponding object
PrototypeUse a prototype instance to specify the type of object you want to create, and copy the prototype to create a new object

2. Structural Mode

Structural patterns describe how classes or objects can be combined to form larger structures, like building blocks, which can be combined to form complex, more powerful structures.

It can also be divided into classified structure mode and object structure mode:

  • Class structured patterns are mainly concerned with the combination of classes, which can be combined into a larger system. Generally, there are only inheritance and implementation relationships in class structured patterns.

  • Object structured patterns are primarily concerned with the combination of classes and objects through which an instance object of another class is defined in one class and its methods are invoked.

NameCore role
Adapter modeInterfaces needed to convert one class interface to another
Bridge modeSeparate abstraction from implementation so that they can change independently
Decorator Mode (Director)Provides an internal way to dynamically add functionality to objects
Composite modeGrouping objects into a tree structure to represent an overall/partial hierarchical relationship
FacadeProvides a unified interface for accessing other internal interfaces, making subsystems easier to use
Flyweight modeSupport a large number of fine-grained objects in a shared manner, some of which have the same internal state
Proxy modeControl direct access to a class of objects, indirect access by proxy

3. Behavioral patterns

Behavioral patterns are abstract ways of dividing responsibilities between different objects, focusing not only on the structure of classes and objects, but also on the interaction between classes and objects.

It can also be divided into classified behavior mode and object behavior mode:

  • Behavioral patterns of classes use inheritance to assign behavior among several classes. Class Behavioral patterns mainly assign responsibilities of parent and child classes through polymorphism, etc.

  • Behavioral patterns of objects use aggregated associations of objects to assign behaviors. Object behavioral patterns mainly assign responsibilities to two or more classes by means of object associations, etc.

NameCore role
Command modeEncapsulates commands into objects that can be used to parameterize other objects
Iterator modeProvides a way to access aggregated object elements sequentially without exposing the internal details of the aggregated object
Observer modeWhen an object's state changes, all its dependent objects are notified and the state is updated automatically
Mediator modeCentralize complex communication and control between related objects
Memento modeGets the internal state of the object so that it can be restored to its original state when needed
Template Method ModeWith the template method, subclasses can redefine some steps of the algorithm without changing its structure
Interpreter modeTo create an interpreter for a language, usually defined by its grammar and parsing
StateAllow an object to change its own behavior when its internal state changes
Responsibility Chain ModeEnabling multiple objects to process the same request avoids coupling between the sender and receiver of the request
StrategyDefine a series of implementation methods that call the corresponding implementation when needed
Visitor mode (Visitor)Add new capabilities to an object structure

Tags: Java Design Pattern

Posted on Thu, 07 Oct 2021 12:58:07 -0400 by hotcigar