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:

  1. JavaScript Objects, functions, and Classes (new in Class: ES6)
  2. 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
  3. Composition takes precedence over inheritance

Design patterns follow seven principles:

  1. Single responsibility principle: There should be only one reason for a class to change.
  2. Open closed principle: open extension, close modification, that is, cannot modify the original code
  3. 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
  4. 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
  5. Interface isolation principle: The dependency of one class on another class should be based on the smallest interface
  6. The least known principle: each software entity should interact with as few other entities as possible.
  7. 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:

  1. Create a type Creational Pattern: relates to object creation
  • Simple Factory modelSimple Factory
  • Factory method patternFactory Method
  • Abstract Factory patternAbstract Factory
  • Builder modelBuilder
  • The prototype patternPrototype
  • The singleton patternSingleton
  1. structured Structural Pattern: Handles “combinations” of classes or objects
  • Adapter modeAdapter
  • The bridge modelBridge
  • Portfolio modelComposite
  • Decorative patternDecorator
  • The appearance modelFacade
  • The flyweight patternFlyweight
  • The proxy patternProxy
  1. Behavior type Behavioral Pattern: Describes how classes or objects interact and assign responsibilities
  • Chain of Responsibility modelChain of Responsibility
  • Command modeCommand
  • Interpreter modeInterpreter
  • Iterator patternIterator
  • The mediator patternMediator
  • Memo modeMemento
  • Observer modelObserver
  • The state patternState
  • The strategy patternStrategy
  • Template method patternTemplate Method
  • Visitor patternVisitor

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)

  1. Class model
  • Factory method patternFactory Method
  • Adapter modeAdapter
  • Interpreter modeInterpreter
  • Template method patternTemplate Method
  1. 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:

  1. 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.
  2. Interface isolation improves system cohesion, reduces external interactions, and reduces system coupling.
  3. 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.
  4. The use of multiple specialized interfaces also provides a hierarchy of objects, since the overall interface can be defined through interface inheritance.
  5. 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