What pain points are design patterns designed to address?

It is a series of routines to solve specific problems, is the summary of predecessors’ code design experience, has a certain universality, can be used repeatedly. The goal is to improve code reusability, readability, and maintainability. The essence of design patterns is the practical application of object-oriented design principles, the full understanding of class encapsulation, inheritance and polymorphism, as well as the association and combination relationships of classes. Don’t reinvent the wheel.

What is object-oriented programming

  • Object-oriented programmingIs a programming paradigm or style. It takes class or object as the basic unit of organizing code, and takes encapsulation, abstraction, inheritance and polymorphism as the cornerstone of code design and implementation.
  • Object oriented programming languageIt is a programming language that supports the syntax mechanism of class or object, and has ready-made syntax mechanism, and can conveniently realize the four features of object-oriented programming.
  • Object-oriented developmentIncluding object-oriented analysis OOA, object-oriented design OOD, object-oriented programming OOP.

10 Design Principles

1. Single Responsibility principle SRP

Implementation class should be responsible for A single responsibility: If A code block (function class module) is responsible for multiple functions, then when A function needs to change the code, it may lead to B function problems, so A code block should be responsible for only one responsibility.

2. Open-close principle OCP

Open to extensions, close to modifications: Modifying old code to implement new functionality can cause bugs in old modules, so we should implement new functionality by developing new blocks of code.

3. Richter replacement principle LSP

Don’t break the inheritance system: a child class in a program should be able to replace its parent class wherever it appears and keep expectations unchanged. So a subclass tries not to change the expected behavior of a superclass method.

4. Interface isolation principle ISP

When designing an interface, simplify the simplicity: when class A needs only some of the methods in interface B, because implementing the interface needs to implement all of its methods, class A has some unnecessary code. At this point, interface B should be split, separating the methods that class A does and does not need.

5. Rely on the inversion principle

Programming for interfaces: Abstractions should not depend on details, details should depend on abstractions. The core of programming is interface oriented. We should rely on abstract interfaces, not concrete interface implementation classes or concrete objects.

Note: SOLID above is also known as the five Design Principles

6. The Principle of Least knowledge (Demeter principle)LOD

Reduced coupling: A class or object should have minimal knowledge of other objects. Communicate only with direct (coupled) friends.

7. Combination/aggregation reuse principle CRP

Composition more than inheritance: When possible, implement new functionality by composing existing objects (borrowing their capabilities) rather than using inheritance to acquire those capabilities.

8. Don’t repeat yourself DRY

Functional semantic duplicates should be merged, code execution duplicates should be deleted, and code logic duplicates but semantic differences should be preserved.

9. Keep it simple KISS

As much as possible, use simple and readable code to implement functionality, rather than logically complex, difficult to implement, and less readable.

10. Don’t overdesign logic you don’t need right now

Don’t over-optimize, don’t over-reserve extension points, and don’t design code that your colleagues don’t understand.

How do you evaluate the quality of your code?

  • Readability, scalability, maintainability, reusability, testability…
  • High cohesion and low coupling.
  • War is no HeHeZhiGong good healer tyger, big more if the wisest man often seems stupid, really good code and not by how much technology and exotic curiosity-a solution looking bad, but to look to the best of the world after the busy, a few pen realized functions at the same time without any traces of individual style, comply with the code specification, programming ideas, design patterns, the code.

How to form long-term memories?

  • Try to connect the dots
    • Form a pyramid structure memory from top to bottom.
    • Compiling key words and formulae memory.
  • Get carried away
    • Extract the essence of the branches of knowledge to strengthen memory, to remove the essence.
  • Learning without thought is labor lost; thought without learning is perilous
    • Deep thinking makes other people’s knowledge truly your own.
  • Learning and time to practice, not also said
    • The first learning is only a short-term memory in the mind,It takes a lot of review and reinforcement to form a long-term memory.

Matters needing attention

Knowledge is dead, but code is alive. Don’t hardwire design pattern implementations into living business logic. It is our goal to be able to use what we have learned, but if we write code that is not understood by others in the group, it will affect the maintainability and development efficiency of the project. So we can use it sparingly, but we have to master the idea. Grasp the design pattern firmly, take to interview, interview others, the group to share or can frighten the crowd.

23 Design patterns shorthand

  • Shorthand: 5, 7, 11, 23 are all odd numbers
    • 5 types of creation
    • 7 types of structure
    • 11 behavioral patterns
  • Create a type:Work order to build the prototype
    • Abstract factory, factory, singleton, builder, prototype
  • Structured:Bridge agent decorates adapter, enjoy element combination into facade
    • Bridges, agents, decorators, adapters, savors, compositions, facades
  • Behavior type:Observe the status of template iterations, command mediations interpret chains of responsibility, and access policy memos
    • Observer, template, iteration, status, command, mediator, interpreter, chain of responsibility, visitor, policy, memo

Creative design patterns

Encapsulates the object creation process and decouples the creation and use of objects

The singleton pattern

Application scenarios

Handles resource access conflicts and creates globally unique classes.

The solution

  • Lazy: create when you need it (scenarios: not necessarily needed, expensive to create, lazy loading, need to start the system quickly).
  • Han type: created when the system is started (scenario: must be used, temporary creation affects response speed).
  • Multiple examples: A fixed number of the same instances of the same class.

The factory pattern

Application scenarios

Use to create a subclass object that inherits from the same parent class and implements the same interface. A given type parameter creates a concrete object.

The solution

enum HelloType {
  A,
  B
}

interface Hello {
  sayHello()
}

class A implements Hello {
  sayHello() {
    console.log('A'); }}class B implements Hello {
  sayHello() {
    console.log('B'); }}class HelloFactory {
  static list = new Map<HelloType, Hello>([
    [HelloType.A, new A()],
    [HelloType.B, new B()]
  ])

  static getHello(type: HelloType) {
    return HelloFactory.list.get(type)}}// test
HelloFactory.getHello(HelloType.A).sayHello()
HelloFactory.getHello(HelloType.B).sayHello()
Copy the code

Abstract Factory pattern

Application scenarios

A subclass object that inherits from the same parent class and implements the same interface creates a concrete object from a given number of type parameters.

The solution

enum Type {
  A,
  B
}

enum Occupation {
  TEACHER,
  STUDENT
}

interface Hello {
  sayHello()
}

class TA implements Hello {
  sayHello() {
    console.log('Teacher A say hello')}}class TB implements Hello {
  sayHello() {
    console.log('Teacher B say hello')}}class SA implements Hello {
  sayHello() {
    console.log('Student A say hello')}}class SB implements Hello {
  sayHello() {
    console.log('Student B say hello')}}class AFactory {
  static list = new Map<Occupation, Hello>([
    [Occupation.TEACHER, new TA()],
    [Occupation.STUDENT, new SA()]
  ])

  static getHello(occupation: Occupation) {
    return AFactory.list.get(occupation)
  }
}

class BFactory {
  static list = new Map<Occupation, Hello>([
    [Occupation.TEACHER, new TB()],
    [Occupation.STUDENT, new SB()]
  ])

  static getHello(occupation: Occupation) {
    return BFactory.list.get(occupation)
  }
}

class HelloFactory {
  static list = new Map<Type, AFactory | BFactory>([
    [Type.A, AFactory],
    [Type.B, BFactory]
  ])

  static getType(type: Type) {
    return HelloFactory.list.get(type)}}// test
HelloFactory.getType(Type.A).getHello(Occupation.TEACHER).sayHello()
HelloFactory.getType(Type.A).getHello(Occupation.STUDENT).sayHello()
HelloFactory.getType(Type.B).getHello(Occupation.TEACHER).sayHello()
HelloFactory.getType(Type.B).getHello(Occupation.STUDENT).sayHello()

Copy the code

Builder mode

Application scenarios

  • There are many required parameters to verify at creation time.
  • Parameter evaluation is sequential and interdependent at creation time.
  • There are many steps to creating an object, all of which have to be successful.

The solution

class Programmer {
  age: number
  username: string
  color: string
  area: string

  constructor(p) {
    this.age = p.age
    this.username = p.username
    this.color = p.color
    this.area = p.area
  }

  toString() {
    console.log(this)}}class Builder {
  age: number
  username: string
  color: string
  area: string

  build() {
    if (this.age && this.username && this.color && this.area) {
      return new Programmer(this)}else {
      throw new Error('Lack of information')}}setAge(age: number) {
    if (age > 18 && age < 36) {
      this.age = age
      return this
    } else {
      throw new Error('Not the right age')}}setUsername(username: string) {
    if(username ! = ='Ming') {
      this.username = username
      return this
    } else {
      throw new Error('Xiao Ming is not suitable')}}setColor(color: string) {
    if(color ! = ='yellow') {
      this.color = color
      return this
    } else {
      throw new Error('Yellow is not appropriate')}}setArea(area: string) {
    this.area = area
    return this}}// test
const p = new Builder()
  .setAge(20)
  .setUsername('little red')
  .setColor('red')
  .setArea('hz')
  .build()
  .toString()

Copy the code

The prototype pattern

Application scenarios

  • The prototype pattern is based on the existing object clone data, rather than modifying the prototype chain!
  • Objects are expensive to create, and different instances of the same class have the same property values. Saving resources by means of prototype cloning.
  • Immutable objects are implemented by shallow cloning.
  • Mutable objects are implemented by deep cloning, which consumes a lot of resources.
  • Different versions of the same object can be compared with the same shallow clone and the changed deep clone, and then the new version replaces the old version.

Structural design mode

Some classical structures of classes or objects are summarized. These classical structures can solve the problems of specific application scenarios and decouple the structure and use of classes or objects

The bridge model

Application scenarios

  • Decouple abstraction and implementation so that they can change independently.
  • A class has multiple dimensions that vary independently, and we can extend them independently by combining them.
  • Very similar to the combination over inheritance principle.

The solution

enum MsgLevel {
  ERROR,
  WARN,
}

enum MsgType {
  EMAIL,
  PHONE
}

interface MsgContent {
  content()
}

class ErrorMsg implements MsgContent {
  content() {
    return 'ERROR'}}class WarnMsg implements MsgContent {
  content() {
    return 'WARN'}}interface MsgSender {
  send()
}

class PhoneSend implements MsgSender {
  msgContent: MsgContent

  constructor(msgContent: MsgContent) {
    this.msgContent = msgContent
  }

  send() {
    console.log(`phone send The ${this.msgContent.content()}`)}}class EmailSend implements MsgSender {
  msgContent: MsgContent

  constructor(msgContent: MsgContent) {
    this.msgContent = msgContent
  }

  send() {
    console.log(`email send The ${this.msgContent.content()}`)}}// Test ();
new PhoneSend(new WarnMsg()).send()
new PhoneSend(new ErrorMsg()).send()
new EmailSend(new WarnMsg()).send()
new EmailSend(new ErrorMsg()).send()

Copy the code

The proxy pattern

Application scenarios

  • Add nonfunctional requirements to the original class in order to decouple the code from the original business.
  • Development of non-functional requirements for business systems: monitoring, statistics, authentication, traffic limiting, logging, caching.

The solution

  • Implementation by inheritance (not recommended)
class User{
  login(){
    console.log('user login... ')}}class UserProxy extends User{
  login() {
    console.log('login before')
    super.login()
    console.log('login after')}}Copy the code
  • Implemented through an interface (recommended)
interface Login {
  login()
}

class User implements Login {
  login() {
    console.log('user login... ')}}class UserProxy implements Login {
  user = new User()

  login() {
    console.log('login before')
    this.user.login()
    console.log('login after')}}Copy the code

Decorator pattern

Application scenarios

  • Decorator classes are enhancements to the original functionality.
  • The decorator class and the original class inherit from the same parent class, so we can nest multiple decorator classes into the original class.
  • Mainly to solve the problem of inheritance relationship is too complex, through combination to replace inheritance.
  • You can use more than one decorator by nesting the original class.

The solution

  • Through AOP
Function.prototype.before = function (beforeFn) {
  return (. arg) = >{ beforeFn(... arg);return this(...arg);
  }
};
Function.prototype.after = function (afterFn) {
  return (. arg) = > {
    const result = this(... arg); afterFn(... arg);returnresult; }};function ImportEvent1 {
  console.log('Say important things three times.')}function ImportEvent2 {
  console.log('Important things say two three times.')}function ImportEvent3 {
  console.log('Say the important things three times.')}// test
ImportEvent2.before(ImportEvent1).after(ImportEvent3)()
Copy the code

Adapter mode

Application scenarios

  • The adapter pattern is used to remedy design flaws and make incompatible interfaces compatible.
  • Encapsulate a flawed interface design.
  • Unify the interface design of multiple classes.
  • Replace dependent external systems.
  • Compatible with older versions of interfaces.
  • Suitable for data in different formats.

The solution

  • The original interface method is few, class adapter and object adapter can be.
  • If the original class has many methods and little difference from the target interface, use a class adapter to reduce the amount of code.
  • If the original class has many methods and is very different from the target interface, composition is preferable to inheritance with object adapters.
// Target interface format
interface ITarget {
  f1()

  f2()

  f3()
}

// The original class is not compatible with the target interface
class Origin {
  fa(){}fb(){}f3(){}}// Use an adapter for compatibility
class Adaptor implements ITarget {
  origin = new Origin()

  f1() {
    this.origin.fa()
  }

  f2() {
    this.origin.fb()
  }

  f3() {
    this.origin.f3()
  }
}
Copy the code

The flyweight pattern

Application scenarios

  • Shared units. Reuse objects to save memory, provided that the share object is immutable (it does not change after initialization).

The solution

  • For example, the online chess game has 1000 rooms, each room has 1 board, the board’s current state (the position of the pieces) is different, but the size, color, name of the pieces are the same and fixed, can be designed to enjoy yuan.

Portfolio model

Application scenarios

Organize a set of objects into a tree structure to represent a partial-whole hierarchy. The composite pattern allows the client to unify the processing logic (recursive traversal) for individual and composite objects.

The solution

abstract class FileSystemNode {
  path: string

  abstract getFilesCount()

  abstract getFilesSize()
}

class FileNode extends FileSystemNode {
  constructor(path) {
    super(a);this.path = path
  }

  getFilesCount() {
    return 1
  }

  getFilesSize() {
    return require(this.path).length
  }
}

class Directory extends FileSystemNode {
  subNodes = []

  constructor(path) {
    super(a);this.path = path
  }

  getFilesCount() {
    return this.subNodes.reduce(item= > item.getCount(), 0)}getFilesSize() {
    return this.subNodes.reduce(item= > item.getSize(), 0)}}Copy the code

Facade (appearance) mode

Application scenarios

  • Combine multiple back-end interface requests into one (redundant interface) to improve response speed and solve performance problems.
  • By encapsulating fine-grained interfaces, you can provide high-level interfaces that combine fine-grained interfaces to improve the ease-of-use of interfaces.

Behavioral design patterns

Summarizes some classic ways of class or object interaction to decouple the classes or objects associated with this behavior

Observer mode

Application scenarios

  • Decouple observer from observed.
  • The publish/subscribe pattern has a publish/subscribe scheduling center (middleman), the Observer pattern does not!

The solution

// The target object
class Subject {
  observerList: Observer[]

  constructor() {
    this.observerList = [];
  }

  addObserver(observer) {
    this.observerList.push(observer);
  }

  notify() {
    this.observerList.forEach((observer) = >{ observer.update(); }); }}/ / observer
class Observer {
  cb: Function

  constructor(cb: Function) {
    if (typeof cb === "function") {
      this.cb = cb;
    } else {
      throw new Error("The Observer constructor must pass the function type!"); }}update() {
    this.cb(); }}// test
const observerCallback = function () {
  console.log("I've been informed.");
};
const observer = new Observer(observerCallback);
const subject = new Subject();
subject.addObserver(observer);
subject.notify();

Copy the code

Template pattern

Application scenarios

  • Define an algorithm (business logic) skeleton in a method and defer some steps to subclasses. The template method pattern allows subclasses to redefine certain steps in an algorithm without changing the overall structure of the algorithm.
  • Reuse extensions.

The solution

abstract class Drinks {
  firstStep() {
    console.log('Boil water')}abstract secondStep()

  thirdStep() {
    console.log('Pour into glass')}abstract fourthStep()

  drink() {
    this.firstStep()
    this.secondStep()
    this.thirdStep()
    this.fourthStep()
  }
}

class Tea extends Drinks {
  secondStep() {
    console.log('Soaking tea')}fourthStep() {
    console.log('Add lemon')}}class Coffee extends Drinks {
  secondStep() {
    console.log('Brew coffee')}fourthStep() {
    console.log('sugar')}}// test
const tea = new Tea()
tea.drink()
const coffee = new Coffee()
coffee.drink()
Copy the code

The strategy pattern

Application scenarios

  • Define a family of algorithms, encapsulate each algorithm separately, and make them interchangeable.
  • Avoid lengthy if-else or switch branch decisions.

The solution

enum StrategyType {
  S,
  A,
  B
}

const strategyFn = {
  'S': function (salary: number) {
    return salary * 4
  },
  'A': function (salary: number) {
    return salary * 3
  },
  'B': function (salary: number) {
    return salary * 2}}const calculateBonus = function (level: StrategyType, salary: number) {
  return strategyFn[level](salary)
}

calculateBonus(StrategyType.A, 10000) / / 30000
Copy the code

Chain of Responsibility model

Application scenarios

  • Multiple processors, ABC, process the same request in turn, forming a chain. When one processor can process the request, it will not be passed on to subsequent processors.
  • Filter interceptor processor.

The solution

const order500 = function (orderType, pay, stock) {
  if (orderType === 1 && pay === true) {
    console.log("500 yuan deposit, 100 yuan coupon.");
    return true;
  } else {
    return false; }};const order200 = function (orderType, pay, stock) {
  if (orderType === 2 && pay === true) {
    console.log("200 yuan deposit, 50 yuan coupon.");
    return true;
  } else {
    return false; }};const orderCommon = function (orderType, pay, stock) {
  if ((orderType === 3| |! pay) && stock >0) {
    console.log("Regular purchase, no coupons.");
    return true;
  } else {
    console.log("There's not enough stock to buy.");
    return false; }};class chain {
  fn: Function
  nextFn: Function

  constructor(fn: Function) {
    this.fn = fn;
    this.nextFn = null;
  }

  setNext(nextFn) {
    this.nextFn = nextFn
  }

  init(.arguments) {
    const result = this.fn(... arguments);if(! result &&this.nextFn) {
      this.nextFn.init(... arguments); }}}const order500New = new chain(order500);
const order200New = new chain(order200);
const orderCommonNew = new chain(orderCommon);

order500New.setNext(order200New);
order200New.setNext(orderCommonNew);

order500New.init(3.true.500); // Regular purchase, no coupon

Copy the code

The state pattern

Application scenarios

  • Each state inside a thing is encapsulated as a class, and a change in the internal state produces a different behavior.

The solution

class weakLight {
  light: Light

  constructor(light: Light) {
    this.light = light
  }

  press() {
    console.log('Turn on the bright light')
    this.light.setState(this.light.strongLight)
  }
}

class strongLight {
  light: Light

  constructor(light: Light) {
    this.light = light
  }

  press() {
    console.log('off')
    this.light.setState(this.light.offLight)
  }
}

class offLight {
  light: Light

  constructor(light: Light) {
    this.light = light
  }

  press() {
    console.log('Turn on low light')
    this.light.setState(this.light.weakLight)
  }
}


class Light {
  weakLight: weakLight
  strongLight: strongLight
  offLight: offLight
  currentState: offLight | weakLight | strongLight // Current status: off by default

  constructor() {
    this.weakLight = new weakLight(this)
    this.strongLight = new strongLight(this)
    this.offLight = new offLight(this)
    this.currentState = this.offLight
  }

  press() {
    this.currentState.press()
  }

  setState(state) {
    this.currentState = state
  }
}

// test
const light = new Light()
light.press()
light.press()
light.press()
light.press()
light.press()
light.press()

Copy the code

Iterator pattern

Application scenarios

  • Iterating over collection objects.

Visitor pattern

Application scenarios

  • Allows one or more operations to be applied to a set of objects, decoupling the operations from the objects themselves.

Memorandum Mode

Application scenarios

  • Without violating encapsulation, capture the internal state of an object and save that state outside of the object so that the object can later be restored to its previous state.

The solution

class Programmer {
  age: number
  username: string
  color: string
  area: string

  constructor(p) {
    this.age = p.age
    this.username = p.username
    this.color = p.color
    this.area = p.area
  }

  // Create a snapshot
  createSnapshot() {
    return {
      age: this.age,
      username: this.username,
      color: this.color,
      area: this.area
    }
  }

  // Restore the object state using the snapshot
  restoreSnapshot(snapshot: Programmer) {
    this.age = snapshot.age
    this.username = snapshot.username
    this.color = snapshot.color
    this.area = snapshot.area
  }
}
Copy the code

Command mode

Application scenarios

  • The main functions and application scenarios of the command mode are to control the execution of commands, such as asynchronous, delayed, queued, undoing, storing, and logging commands. Decouple the initiator and the executor of the command.

The solution

interface Command {
  execute()
}

class closeDoorCommand implements Command {
  execute() {
    console.log('close door'); }}class openPcCommand implements Command {
  execute() {
    console.log('open pc'); }}class openQQCommand implements Command {
  execute() {
    console.log('login qq'); }}class CommandManager {
  commandList: Command[] = []

  addCommand(command: Command) {
    this.commandList.push(command)
  }

  execute() {
    this.commandList.forEach(command= > {
      command.execute()
    })
  }
}

//test
const commandManager = new CommandManager();
commandManager.addCommand(new closeDoorCommand());
commandManager.addCommand(new openPcCommand());
commandManager.addCommand(new openQQCommand());
commandManager.execute();

Copy the code

Interpreter mode

Application scenarios

Given a language, define its grammatical representation, and define an interpreter that uses the identity to interpret sentences in the language.

The mediation patterns

Application scenarios

  • The mediation pattern is designed much like the middle tier, by introducing the mediation as a middle tier to transform the interactions (dependencies) between a set of objects into a one-to-many (star-shaped) relationship. Originally, an object needs to interact with n objects, but now it only needs to interact with one intermediary object, which minimizes the interaction between objects, reduces the code complexity, and improves the readability and maintainability of the code.

Don't forget to like, follow and comment on my quality