Why are design patterns important? Because of readability? Because little bug? Because it’s a good test? I think these are not the most important, just like the dog plane and swimming posture, only master the swimming posture, swimming will be fun, (except to wash your eyes to the natatorium). Design patterns allow us to write code with discipline and artistry.

Everything in JavaScript is an object, and while ES6 has always used a dizzying chain of prototypes, __proto__ was implemented inconsistently across browsers, causing confusion for many people, resulting in several ways to write a simple inheritance. Fortunately, after ES6, we have class, which is still the syntactic sugar of the prototype chain, but at least standardizes the writing of inheritance, the official way of writing.

So let’s start with the SOLID laws that object orientation requires

S-single Responsibility

A class should be responsible for only one responsibility is all there is to this principle, but how do you make a class responsible for only one responsibility? What is a duty? Okay, one violation first.

class HttpClient {
    getPosts(url) {
        fetch(url,{
            headers: {
                'Accept': 'application/json'
            }
        })
        .then(response= > {
            if(response.ok) return response.json()
            else if (response.statue === 401) {
               // 401's processing logic
            } else if (response.statue === 404) {
                // 404's processing logic}}}})Copy the code

The idea is very good, because it is inevitable to encounter mistakes when using fetch, so it must be dealt with according to the positive thinking, which needs to be divided into several cases. Pop-ups or toast are user-friendly interactions.

HttpClient is only responsible for making requests and returning data. Error handling should be handled by a separate function.

import ErrorHandler from 'xxx'
class HttpClient {
    getPosts(url) {
        fetch(url,{
            headers: {
                'Accept': 'application/json'
            }
        })
        .then(response= > {
            if(response.ok) return response.json()
            else {
                return ErrorHandler.handleResponseStatue(response.statue)
            }
        })
    }
}
Copy the code

The ErrorHandler class handles errors, while HttpClient handles requests, and both have clear responsibilities. That’s what S means. As for the granularity of responsibility division, there is no clear standard, and a balance needs to be struck. If a class has only one function, there will be too many classes, reducing readability. If a class is mixed up too much, it will increase coupling.

O-open /Close Principle

Open to extensions, close to modifications. What is an extension? What is change? It can’t be blatant in its stupor; this is another example of violation

class Person {
    static studentID = 'xxx'
    static employID = 'xxx'
    static professorID = 'xxx'
    constructor(name, age, id) {
        this.name = name
        this.age = age
        this.id = id
    }
    // Returns the person type
    get type () {
        return this._type
    }
    // Set the personnel type
    set type(type) {
        this._type = type
    }
    authorize() {
        if (!this.type) throw Error('No type signature')
        if (this.type === 'student') return this.id === Person.studentID
        elif(this.type === 'employ') return this.id === Person.employID
        elif(this.type === 'professor') return this.id === Person.professorID
    }
}
const studentA = new Person('lorry'.26.123)
const employA = new Person('Lebron'.29.321)
studentA.type = 'student'
employA.type = 'employ'
studentA.authorize()
employA.autorize()
Copy the code

Looks all right, doesn’t it? Going back to the question above, is it easy to expand? Bad, because other classes may not need the authorize method, nor type. For example, I want to extend a class ChinaPerson with a region. Close for modification? No, you can change the value of type arbitrarily. Here is the optimized version

class Person {
  constructor(name, age) {
      this.name = name
      this.age = age
  }
}
class Student extends Person {
  static AuthorizedID = 'xxx'
  constructor(id, name, age) {
    super(name, age)
    this.id = id
  }
  authorize() {
    return this.id === Student.AuthorizedID
  }
}

class Employ extends Person {
  static AuthorizedID = 'xxx'
  constructor(id, name, age) {
    super(name, age)
    this.id = id
  }
  authorize() {
    return this.id === Employ.AuthorizedID
  }
}

const student = new Student(123.'lorry'.26)
const employ = new Employ(321.'Lebron'.30)
student.authorize()
employ.authorize()
Copy the code

This is a lot easier to extend, and the Person, Student, and Employ classes cannot be modified after new, so the implementation is open to extension, but closed to modification

L-liskov Substitution Principle IN LSP Substitution

This was proposed by Liskov in 1897. This principle states that wherever a base class (parent class) can occur, subclasses can occur without breaking the functionality of the software. The Richter substitution principle is intended to state inheritance, that is, subclasses should inherit all methods of their parent without breaking the interface definition of the parent class. Take a look at the violation

class ProductStorage {
    products = []
    get length = (a)= > this.products.length
    save(product) {
        this.products.push(product)
    }
}
class Product {
    constructor(name, price) {
        this.name = name
        this.price = price
    }
    save(storage) {
        storage.save({name: this.name, price: this.price})
        return storage.length
    }
}
class DiscountProduct extends Product {
    constructor(name, price, discount){
        super(name, price)
        this.discont = discont
    }
    save(storage) {
        const discounted = {name: this.name, price: this.price * (1-this.discont)})
        storage.save(discounted)
        return disconted
    }
}
const products = [
    {
        name: 'cat'.price: 1000
    },
    {
        name: 'airplane'.price: 500000
    },
    {
        name: 'mobilePhont'.price: 300.discount: 0.2}]function insertAll(products) {
    let storage = new ProductStorage()
    for p of products {
        let product
        if (p.discount) {
            produce = newDiscountProduct(... p) }else {
            product = newProduct(... p) } product.save(storage)console.log(`product saved, the things count is ${storage.length}`)
    }
}
insertAll(products)
Copy the code

The code rarely believes that you see the problem. In the save method, the subclass implements a different interface than the base class. The base class returns a number, while the subclass returns an object, which violates the Richter substitution principle and is easy to change

class DiscountProduct extends Product {
    // ...
    save(storage) {
        const discounted = {name: this.name, price: this.price * (1-this.discont)})
        storage.save(discounted)
        return storage.length
    }
}
Copy the code

I-interface Segregation principle

There is no concept of an interface in JavaScript, but you can use classes to simulate interfaces. Ts is highly recommended, and I’ll write about TS later to explain the concept of interface isolation: Don’t include any interface that isn’t implemented. Such as

This figure shows that Shape implements IDrawable, which is calculated by two properties, Draw and calculateArea. Rectangle and Line inherit Shape, so they are both implemented by IDrawable’s methods, respectively.

class Shape {
    draw() {
        throw Error(`haven't implement this method yet`)
    }
    calculateArea() {
        throw Error(`haven't implement this method yet`)}}class Rectangle extends Shape {
    constructor(x1,y1,x2,y2) {
        this.height = y2 - y1
        this.width = x2 - x1
        this.startX = x1
        this.startY = y1
    }
    draw() {
        drawRectangle(this.startX, this.startY, this.width, this.height) // Draw the rectangle function
    }
    calculateArae() {
        return this.height * this.width
    }
}
class Line extends Shape
Copy the code

The problem is that the Line has no area to calculate, which violates interface isolation. You can change it to

class Shape {
    draw() {
        throw Error(`haven't implement this method yet`)}}Copy the code

This is consistent with the concept of interface isolation. Contains only sufficient and necessary interfaces. Be it a base class or an implementation class

D-Dependencies Inversion

This is the last design pattern in Solid, and one that I think is the least understood. The concept is that programs should rely on abstract interfaces rather than concrete implementations to decouple dependencies. Next violation

class BMWCar {}class BENSCar {}class AutoSystem {
    constructor(type) {
        this.bmw = new BMWCar()
        this.benz = new BENZCar()
        this.type = type
    }
    runCar () {
        if(this.type === 'bmw') {this.bmw.run()
        } else {
            this.benz.run()
        }
    }
    stop() {
        if(this.type === 'bmw') {
            this.bmw.stop()
        } else {
            this.benz.stop()
        }
    }
}
Copy the code

If a AUDI is to be added now, there will be many changes to be made, and each method will add an IF judgment. When there are more and more vehicle types, this AutoSystem class will become very halal. This is implementation-dependent rather than interface-dependent, and the following modifications can be made

class AutoSystem {
    constructor(car) {
        // Pass in the instance
        this.car = car
    }
    run() {
        thia.car.run()
    }
    stop() {
        this.car.stop()
    }
}
Copy the code

AutoSystem now relies only on the CarBase abstraction. Not a specific car implementation. Thus the inversion of dependence is realized

Of course, it’s much more intuitive if it’s TS

interface Icar {
    run: (a)= > void;
    stop: (a)= > void;
}

class BMWCar implements Icar {
    run() {
        
    }
    stop() {
        
    }
}
class BenzCar implement Icar {
    run() {
        
    }
    stop() {
        
    }
}
class AutoSystem{
    constructor(car: Icar) {
        this.car = car
    }
    runCar () {
        this.car.run()
    }
    stopCar() {
        this.car.stop()
    }
}
Copy the code

The above is all the understanding and elaboration of SOLID. I will try my best to explain this concept clearly. If you have any questions, please leave a message to me and I will try my best to answer them.

Reference links:

  1. www.oreilly.com/library/vie…
  2. baike.baidu.com/item/ dependence inversion principle…