Design Patterns (I) — Seven Principles
Content from “JavaScript Design Patterns” by Rongming Zhang (2015), “Da Hua Design Patterns” by Jie Cheng, “Java Design Patterns “C language Chinese website
Preface:
Design Patterns refer to a collection of code logic Design Patterns that are frequently used in software/program development to improve code security, reliability, readability, maintainability, and extensibility.
The design pattern is used to reduce the coupling degree of code and increase the possibility of code reuse.
0. Prefixes
Before we look at design patterns, we need to know:
- JavaScript Objects, functions, and Classes (new in Class: ES6)
- Abstract class: A base class that defines common methods for its subclasses. It usually does not instantiate abstract classes directly, but instead derives concrete types from abstract classes
- Composition takes precedence over inheritance
Design patterns follow seven principles:
- Single responsibility principle: There should be only one reason for a class to change.
- Open closed principle: open extension, close modification, that is, cannot modify the original code
- Richter’s substitution principle: Superclasses (base classes) have properties and methods that can be found in instances of derived classes. Derived classes must be used where superclasses (base classes) can be used, and vice versa
- Dependency inversion principle: high-level modules should not depend on low-level modules; both should depend on abstractions. Abstraction should not depend on details, details should depend on abstractions
- Interface isolation principle: The dependency of one class on another class should be based on the smallest interface
- The least known principle: each software entity should interact with as few other entities as possible.
- Principle of composite reuse: try to use object composition/rather than inheritance to achieve the purpose of software reuse
There are currently 23 design patterns in common use (or seen most often), each of which has a different purpose. According to the general classification of their uses, they can be divided into three types:
- Create a type
Creational Pattern
: relates to object creation
- Simple Factory model
Simple Factory
- Factory method pattern
Factory Method
- Abstract Factory pattern
Abstract Factory
- Builder model
Builder
- The prototype pattern
Prototype
- The singleton pattern
Singleton
- structured
Structural Pattern
: Handles “combinations” of classes or objects
- Adapter mode
Adapter
- The bridge model
Bridge
- Portfolio model
Composite
- Decorative pattern
Decorator
- The appearance model
Facade
- The flyweight pattern
Flyweight
- The proxy pattern
Proxy
- Behavior type
Behavioral Pattern
: Describes how classes or objects interact and assign responsibilities
- Chain of Responsibility model
Chain of Responsibility
- Command mode
Command
- Interpreter mode
Interpreter
- Iterator pattern
Iterator
- The mediator pattern
Mediator
- Memo mode
Memento
- Observer model
Observer
- The state pattern
State
- The strategy pattern
Strategy
- Template method pattern
Template Method
- Visitor pattern
Visitor
Design patterns can be divided into two types according to the scope of effect, the class pattern on the class, the object pattern on the object (instance)
- Class model
- Factory method pattern
Factory Method
- Adapter mode
Adapter
- Interpreter mode
Interpreter
- Template method pattern
Template Method
- Object modes (including the remaining 19)
The “class pattern” focuses on the relationships between classes and subclasses. These relationships are established through inheritance and then fixed. They are static.
The “object pattern” deals with the relationship between objects, can change, and is more dynamic
1. Interpretation of seven principles
Single Responsibility Principle
Definition: A class or module should have one and only one reason to change.
The goal is to drastically reduce code coupling and complexity.
Following the single responsibility principle can make the code logic and function clearer, and easier to expand and maintain, and easier to troubleshoot errors. But this can seriously increase the amount of code.
Example:
class ShoppingCart {
constructor() {
this.goods = []
}
addGood(good) {
this.goods.push(good)
}
deleteGood(goodIndex) {
if(goodIndex < 0) {
throw new Error("The serial number of this product cannot be less than 0")}if(goodIndex > this.goods.length - 1) {
throw new Error("The serial number of this product cannot be greater than the total number of all products")}this.goods.splice(goodIndex, 1);
}
getGoods() {
return this.goods; }}Copy the code
The ShoppingCart class, for example, only adds and deletes functions for itself and does not affect other classes.
1.2 Open-closed Principle
Definition: software entities such as classes, modules, and functions should be open for extension and closed for modification. Modules should be extended as far as possible without modifying the “original code”.
The purpose of the open and close principle is to extend the original program design and code without changing it, and to ensure the stability and correctness of the original code.
To realize the open and close principle, it is necessary to abstract the program design at the beginning of the program design. After the abstract module is designed, it is not allowed to modify the properties and methods of the interface or abstract class. Method parameter types and reference objects must also be interfaces or abstract classes to ensure the stability of abstraction layer. When you extend, you must define the methods that you implement.
Example:
class DrawChart {
constructor() {}
draw(){}}class DrawBar extends DrawChart {
draw() {
// Rewrite the base class draw method
// ...}}Copy the code
1. Substitution Principle in Diction
Definition: all references to a base class must transparently use objects of its subclasses.
Superclasses (base classes) have properties and methods that can be found in instances of derived classes. Derived classes must be used where superclasses (base classes) can be used, but not vice versa. A superclass can only be truly reused if it can be replaced by a derived class without affecting the functionality of the program.
Richter’s substitution principle is a supplement to the “open closed principle” and also the basis of inheritance.
In this principle, the SuperClass (base class) SubClass is derived from SubClass. If a method can accept an instance object of type SuperClass, This method must also accept an instance object subObject of type SubClasss, but the reverse is not true.
Realization of Richter’s substitution principle:
- A subclass can implement an abstract method of the parent class, but cannot override a nonabstract method of the parent class
- Subclasses can add their own special methods
- When a subclass’s method overrides a parent class’s method, the method’s preconditions (that is, the method’s input parameters) are looser than the parent class’s method
- When a subclass’s method implements a parent class’s method (overriding/overloading or implementing an abstract method), the method’s postcondition (that is, the output/return value of the method) is stricter or equal to that of the parent class’s method
— From C Language Chinese Language – Design patterns
Example:
// Abstract class, communication device
class CommunicationEquipment {
call() {
throw "Abstract methods cannot be called"}}// Subclass, cell phone
class Phone extends CommunicationEquipment {
// Implement the abstract method of the parent class
// number
call(number) {
console.log(number, "Dialing...")}// Provide a non-abstract method
isMobile() {
console.log("It's mobile...")}}// Specific category, iPhone
class IPhone extends Phone {
// You can override the parent method, but the arguments should be stricter or equal
call(number) {
console.log(number, "The iPhone is dialing...")}// Add the subclass's own methods
noCharger() {
console.log("No charger...")}}Copy the code
1.4 Dependence Inversion Principle
Definition: REFERS to a specific form of decoupling in which high-level modules are not dependent on low-level modules’ implementation details, and dependencies are reversed (reversed) so that low-level modules are dependent on high-level modules’ requirements abstraction (wikipedia).
In short, neither high-level nor low-level modules should depend on concrete implementation methods, and high-level modules should depend not on low-level modules, but on interfaces or abstract classes.
Example:
// Abstract, car
class Vehicle {
shoot(){
throw "Abstract methods cannot be called"; }}// Specific category, SUV
class SUV extends Vehicle {
motion(){
console.log("SUV in motion..."); }}// Sedan
class Sedan extends Vehicle {
motion(){
console.log("Sedan in motion..."); }}Copy the code
Know the Least Knowledge Principle
Definition: The least know principle (LKP), also known as the Law of Demeter (LOD), relates only to directly related entities. If two entities (instance objects) do not need to communicate with each other before, then there should be no direct contact or interaction between the two objects.
The least-know principle works to reduce the coupling between classes.
Example:
class Star {
constructor(name) {
this.name = name
}
}
class Fans {
// ...
}
class Company {
// ...
}
class Agent {
setStar(star) {
this.star = star
}
setFans(fans) {
this.fans = fans
}
setCompany(company) {
this.company = company
}
meeting() {
console.log(`The ${this.star}withThe ${this.fans}To meet the `)}business() {
console.log(`The ${this.star}withThe ${this.company}Negotiate business)}}Copy the code
Star class does not have direct contact with Fans and Company, but is connected by Agent in the middle.
1.6 Interface Segregation Principle
Definition: Clients should not be forced to use methods or functions that are useless to them.
A single interface that provides complex functionality is transformed into multiple interfaces that implement a single functionality.
This reduces the complexity and coupling of interfaces and makes them more flexible.
Advantages of the interface isolation principle:
- Splitting a bloated interface into multiple small-grained interfaces can prevent the proliferation of external changes and improve the flexibility and maintainability of the system.
- Interface isolation improves system cohesion, reduces external interactions, and reduces system coupling.
- If the interface granularity is properly defined, the system stability can be guaranteed. However, if the definition is too small, it will lead to too many interfaces and complicate the design. If the definition is too large, flexibility is reduced and customization services cannot be provided, bringing unpredictable risks to the overall project.
- The use of multiple specialized interfaces also provides a hierarchy of objects, since the overall interface can be defined through interface inheritance.
- Reduces code redundancy in project engineering. A large interface that is too large often places a lot of unnecessary methods inside it, forcing redundant code to be designed when implementing the interface.
— From C Language Chinese Language – Design patterns
Example:
// There is no separation
interface ClickListener {
click(): void;
dblClick(): void;
rightClick(): void;
longClick(): void;
}
// After separation
interface ClickListener {
listener(): void;
}
interface DblClickListener {
listener(): void;
}
interface RightClickListener {
listener(): void;
}
interface LongClickListener {
listener(): void;
}
Copy the code
Composite Reuse Principle
Definition: Also known as the “Composition/Aggregate Reuse Principle”, which uses Composition/Aggregate rather than inheritance for Reuse purposes.
The principle of composite reuse and the Principle of Richter’s substitution complement each other.
Synthetic reuse is realized by incorporating known object A into new object B as A member object, which can call methods of object A.
Portfolio: A “strong” ownership relationship that shares a common life cycle. For example, if an OBJECT A contains an object B, then an object B is destroyed when an object A is destroyed.
Aggregation: A “weak” ownership relationship, but each can exist alone.
Example:
class Car {
constructor(color) {
this.color = new Color(color)
}
move() {
throw "Abstract methods cannot be called"}}class Color {
constructor(color) {
this.color = color
}
getColor() {
return this.color
}
}
Copy the code