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..
Name | Core role |
---|---|
Singleton mode | Ensure that a class has only one instance and provide a global access point to the instance |
Factory Mode | Instead of showing the user internal details when creating an object, provide a common interface for creating the object |
Abstract Factory | Provides a unified interface for creating related object families |
Builder Mode | Encapsulate the construction process of an object and follow it to create the corresponding object |
Prototype | Use 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.
Name | Core role |
---|---|
Adapter mode | Interfaces needed to convert one class interface to another |
Bridge mode | Separate abstraction from implementation so that they can change independently |
Decorator Mode (Director) | Provides an internal way to dynamically add functionality to objects |
Composite mode | Grouping objects into a tree structure to represent an overall/partial hierarchical relationship |
Facade | Provides a unified interface for accessing other internal interfaces, making subsystems easier to use |
Flyweight mode | Support a large number of fine-grained objects in a shared manner, some of which have the same internal state |
Proxy mode | Control 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.
Name | Core role |
---|---|
Command mode | Encapsulates commands into objects that can be used to parameterize other objects |
Iterator mode | Provides a way to access aggregated object elements sequentially without exposing the internal details of the aggregated object |
Observer mode | When an object's state changes, all its dependent objects are notified and the state is updated automatically |
Mediator mode | Centralize complex communication and control between related objects |
Memento mode | Gets the internal state of the object so that it can be restored to its original state when needed |
Template Method Mode | With the template method, subclasses can redefine some steps of the algorithm without changing its structure |
Interpreter mode | To create an interpreter for a language, usually defined by its grammar and parsing |
State | Allow an object to change its own behavior when its internal state changes |
Responsibility Chain Mode | Enabling multiple objects to process the same request avoids coupling between the sender and receiver of the request |
Strategy | Define a series of implementation methods that call the corresponding implementation when needed |
Visitor mode (Visitor) | Add new capabilities to an object structure |