21
头图

Preface

Last year, I first read and 's performance optimization booklet on Nuggets, and gained a lot.

Then I bought this JavaScript design pattern core principle and application practice , just recently there was a booklet free learning activity, I quickly sorted out this note, and added the no in the booklet. The rest of the design patterns written are combined with examples written in JavaScript during the learning process to facilitate understanding and deepen the impression.

Rather than being an article, it is actually more like a summary study note.

Why learn design patterns?

Before learning, first understand what is a design pattern?

Design Pattern (Design Pattern) is the predecessor's summary of code development experience, a series of routines to solve specific problems. It is not a grammatical regulation, but a set of solutions to improve code reusability, maintainability, readability, robustness, and security.

The short answer to understand is a set of code design experience summaries that have been used repeatedly, known to many people, and classified.

There are recipes for cooking, and strategies for games. In each field, there are some "routines" that allow us to achieve our goals quickly and well. In the programming world, programming "routines" are design patterns.

Learning it is to learn the routines of this programming world, which will be of great help to upgrading the monsters and fighting equipment in the future. In the ever-changing front-end field, design patterns are also a kind of "learning once, using for life" knowledge.

Principles of design patterns

Describe a recurring problem and the core of the solution to the problem.
In this way, you can use the program again and again without having to do repetitive work.

:

  • Dimit’s Law: Also known as the Law of Least Knowledge, a software entity should interact with other entities as little as possible. Each software unit has minimal knowledge of other units and is limited to those closely related to the unit. Software unit.

Five Principles:

  • Single responsibility principle: A class should have only one reason for its change. In short, the function should be single.
  • The principle of openness and closure: open for expansion and closed for modification
  • Richter substitution principle: where the base class appears, the subclass must appear.
  • The principle of interface isolation: an excuse should be a role. Don't dare to do things that shouldn't be done, and do all that should be done. In short, it is to reduce coupling and dependence.
  • Relying on the flipping principle: For interface programming, rely on abstraction instead of concrete.

Commonly used in JavaScript is the single function and the open and closed principle.

High cohesion and low coupling

Design patterns can help us enhance the reusability, scalability, maintainability, and flexibility of the code. The ultimate goal of our use of design patterns is to achieve high cohesion and low coupling of code.

Take a real-life example, such as a company, where various departments generally perform their duties without interfering with each other. When each department needs to communicate with each other through a dedicated person in charge.

The same is true in software. A functional module only focuses on one function. A module should only implement one function. This is the so-called cohesive .

The interaction between modules and between systems and between systems is inevitable, but we should try our best to reduce the situation that a single module cannot be used independently or transplanted due to the interaction, and provide as many separate interfaces as possible for external use. Operation, this is the so-called low-coupling

Packaging changes

In the actual development process, the code that does not change basically does not exist, so I want to minimize the change of the code.

design pattern is to observe the changes and changes in your entire logic, and then separate the changes to achieve the purpose of making the changed parts flexible and stable.

Types of design patterns

Commonly used can be divided into three types: creation type, structure type, and behavior type, with a total of 23 modes.

设计模式.png

Creation type

Factory mode

This type of design pattern is a creational pattern, which provides the best way to create objects.

In the factory model, we do not expose the creation logic to the client when creating an object, and we use a common interface to point to the newly created object.
In JS, it is actually achieved with the help of a constructor.

example

A certain class needs to have an entry system, and to enter a person, it must be written once.

let liMing = {
  name: "李明",
  age: 20,
  sex: "男",
};

If there are multiple entries, you can create a class.

class Student {
  constructor(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
  }
}
let zhangSan = new Student("张三", 19, "男");

The factory model is to encapsulate the process of creating objects separately, so that you only need to transfer parameters without thinking, just like a factory, as long as you give enough raw materials, you can easily manufacture finished products.

Summary

  • The constructor is separated from the creator, and the new operation is encapsulated
  • Comply with the open and closed principle

Singleton mode

The definition of singleton mode: ensure that a class has only one instance, and provide a global variable to access it.

The method of implementation is to judge whether the instance exists before, if it exists, return directly, if it does not exist, create and return, which ensures that a class has only one instance object.

For example: Vuex, jQuery

example

Usage scenario: A single object, such as a pop-up window, no matter how many times it is clicked, the pop-up window should only be created once. The implementation is also very simple, just use a variable cache.

[Click to view Demo]: singleton mode-online example

Like the above bullet box, the bullet box will only be created when the button is clicked for the first time, and will not be created afterwards, but will use the previously created bullet box.

In this way, a bullet frame applied to the singleton mode is realized.

summary

  • Maintain an instance, if it has been created, return directly
  • Comply with the open and closed principle

Prototype mode

Use prototype instances to specify the types of objects to be created, and create new objects by copying these prototypes.

example

In JavaScript, the prototype mode is implemented in ECMAscript5, the Object.create method proposed, using existing objects to provide the created object __proto__ .

var prototype = {
  name: "Jack",
  getName: function() {
    return this.name;
  },
};

var obj = Object.create(prototype, {
  job: {
    value: "IT",
  },
});

console.log(obj.getName()); // Jack
console.log(obj.job); // IT
console.log(obj.__proto__ === prototype); //true

If there is a prototype, there is a principle

Constructor pattern

In object-oriented programming languages, a constructor is a special method in a class used to initialize new objects. And can accept parameters to set the properties of the instance object
function Car(model, year, miles) {
  this.model = model;
  this.year = year;
  this.miles = miles;
  // this.info = new CarDetail(model)
  // 属性也可以通过 new 的方式产生
}

// 覆盖原型对象上的toString
Car.prototype.toString = function() {
  return this.model + " has done " + this.miles + " miles";
};

// 使用:
var civic = new Car("Honda Civic", 2009, 20000);
var mondeo = new Car("Ford Mondeo", 2010, 5000);
console.log(civic.toString()); // Honda Civic has done 20000 miles
console.log(mondeo.toString()); // Ford Mondeo has done 5000 miles

In fact, it is to use the inherited characteristics of the prototype chain to realize the constructor.

Abstract factory pattern

The abstract factory pattern (Abstract Factory) is to make the business suitable for the creation of a product cluster through the abstraction of the class, instead of being responsible for the instance of a certain type of product.

There is no direct abstract class in JS. Abstract is a reserved word, but it has not yet been implemented. Therefore, we need to throw an error in the class method to simulate the abstract class. If the inherited subclass does not override the method and call it, Will throw an error.

const Car = function() {};
Car.prototype.getPrice = function() {
  return new Error("抽象方法不能调用");
};

There is an abstract factory pattern in object-oriented languages. First, declare an abstract class as a parent class to summarize the characteristics required by a certain type of product. Subclasses that inherit from the parent class need to implement the methods declared in the parent class to implement the parent class. Declared function:

/**
 * 实现subType类对工厂类中的superType类型的抽象类的继承
 * @param subType 要继承的类
 * @param superType 工厂类中的抽象类type
 */
const VehicleFactory = function(subType, superType) {
  if (typeof VehicleFactory[superType] === "function") {
    function F() {
      this.type = "车辆";
    }
    F.prototype = new VehicleFactory[superType]();
    subType.constructor = subType;
    subType.prototype = new F(); // 因为子类subType不仅需要继承superType对应的类的原型方法,还要继承其对象属性
  } else throw new Error("不存在该抽象类");
};
VehicleFactory.Car = function() {
  this.type = "car";
};
VehicleFactory.Car.prototype = {
  getPrice: function() {
    return new Error("抽象方法不可使用");
  },
  getSpeed: function() {
    return new Error("抽象方法不可使用");
  },
};
const BMW = function(price, speed) {
  this.price = price;
  this.speed = speed;
};
VehicleFactory(BMW, "Car"); // 继承Car抽象类
BMW.prototype.getPrice = function() {
  // 覆写getPrice方法
  console.log(`BWM price is ${this.price}`);
};
BMW.prototype.getSpeed = function() {
  console.log(`BWM speed is ${this.speed}`);
};
const baomai5 = new BMW(30, 99);
baomai5.getPrice(); // BWM price is 30
baomai5 instanceof VehicleFactory.Car; // true

Through the abstract factory, a product of a certain cluster can be created, and the category of the product can also be checked through instanceof, and it also has the necessary methods for the cluster.

Structure type

Decorator mode

Decorator mode, also known as decorator mode. Its definition is "on the basis of not changing the original object, by packaging and expanding it, so that the original object can meet the more complex needs of users".

decorator case

There is a pop-up window function, a pop-up box will pop up after clicking the button.

function openModal() {
  let div = document.craeteElement("div");
  div.id = "modal";
  div.innerHTML = "提示";
  div.style.backgroundColor = "gray";
  document.body.appendChlid(div);
}
btn.onclick = () => {
  openModal();
};

But suddenly the product manager wants to change the requirement, he needs to change the prompt text from "prompt" to "warning", and the background color from gray to red.

When you hear this, do you immediately want to change the source function directly:

function openModal() {
  let div = document.craeteElement("div");
  div.id = "modal";
  div.innerHTML = "警告";
  div.style.backgroundColor = "red";
  document.body.appendChlid(div);
}

However, if it is a complex business logic, or a product left over from the previous code when this code was used, it is really troublesome to modify this way every time after taking into account future changes in requirements.

Moreover, directly modifying the existing function body violates our "open and closed principle", and stuffing so much logic into a function also violates the "single responsibility principle", so the above method is not the best.

The most time-saving and labor-saving way is to not care about its existing logic, and only extend new functions on top of this logic, so the decorator pattern was born.

// 新逻辑
function changeModal() {
  let div = document.getElemnetById("modal");
  div.innerHTML = "告警";
  div.style.backgroundColor = "red";
}
btn.onclick = () => {
  openModal();
  changeModal();
};

This way of adding new functions through functions without modifying the old logic is the charm of decorators.

ES7

There is a decorator proposal in the latest ES7, but it has not been finalized, so the syntax may not be the final version, but the idea is the same.

  1. Decorative attributes
@tableColor
class Table {
  // ...
}
function tableColor(target) {
  target.color = "red";
}
Table.color; // true

For Table , add a tableColor to change the property of Table color

  1. Methods of decorating class
class Person {
  @readonly
  name() {
    return `${this.first} ${this.last}`;
  }
}

Add a read-only decorator to the name method of the Person class so that the method cannot be modified.

In fact, it is Object.defineProperty by the wirteable feature of 06077a03d9de81.

  1. Decoration function

    Because functions in JS have function promotion, it is not advisable to use decorators directly, but they can be implemented in advanced functions.

    function doSomething(name) {
      console.log("Hello, " + name);
    }
    function loggingDecorator(wrapped) {
      return function() {
        console.log("fun-Starting");
        const result = wrapped.apply(this, arguments);
        console.log("fun-Finished");
        return result;
      };
    }
    const wrapped = loggingDecorator(doSomething);
    let name = "World";
    
    doSomething(name); // 装饰前
    // output:
    // Hello, World
    
    wrapped(name); // 装饰后
    // output:
    // fun-Starting
    // Hello, World
    // fun-Finished

    The above decorator is to print a log for a function at the beginning and end of execution.

reference

Adapter mode

The role of the adapter mode is to solve the problem of interface incompatibility between two software entities. After using the adapter mode, two software entities that were originally unable to work due to incompatible interfaces can work together.

Simply put, it is to turn a class of interface into another interface that the client expects. solves the compatibility problem .

For example: axios

Example: A method of rendering a map. The default is to call the show method of the current map object to perform the rendering operation. When there are multiple maps and the rendering method of each map is different, in order to facilitate the user to call, it needs to be adapted Up.

let googleMap = {
  show: () => {
    console.log("开始渲染谷歌地图");
  },
};
let baiduMap = {
  display: () => {
    console.log("开始渲染百度地图");
  },
};
let baiduMapAdapter = {
  show: () => {
    return baiduMap.display();
  },
};
function renderMap(obj) {
  obj.show();
}
renderMap(googleMap); // 开始渲染谷歌地图
renderMap(baiduMapAdapter); // 开始渲染百度地图

Among them, the "Baidu Map" has been adapted.

Summary

  • The adapter mode mainly solves the problem of mismatch between the two interfaces. It does not change the original interface, but is a package of one object to another.
  • Adapter mode conforms to the open and closed principle
  • Leave the changes to yourself and the unification to the users.

Agency model

Proxy mode-In some cases, due to various considerations/restrictions, an object cannot directly access another object, and a third party (agent) is needed to bridge the line to achieve the purpose of access indirectly. This mode is the proxy mode .

Speaking of Proxy, I am familiar with the front end, and I can think of a series of things, such as:

  • ES6 new proxy attributes
  • In order to solve cross-domain problems, webpack proxy configuration and Nginx proxy are often used
  • There is also the proxy used by learn to surf the Internet
  • and many more

Event Agent

When common lists and tables need to handle events separately, using the parent element event proxy can greatly reduce the amount of code.

<div id="father">
  <span id="1">新闻1</span>
  <span id="2">新闻2</span>
  <span id="3">新闻3</span>
  <span id="4">新闻4</span>
  <span id="5">新闻5</span>
  <span id="6">新闻6</span>
  <!-- 7、8... -->
</div>

With the code above, I want to click on each news to get the id current news, so I can proceed to the next step.

If you bind a onclick span , it will be too expensive for performance, and it is also very troublesome to write.

Our common practice is to use the principle of event bubbling to delegate the event to the parent element, and then process it uniformly.

let father = document.getElementById("father");
father.addEventListener("click", (evnet) => {
  if (event.target.nodeName === "SPAN") {
    event.preventDefault();
    let id = event.target.id;
    console.log(id); // 拿到id,进行下一步操作
  }
});

virtual agent

For example: a costly operation can be created through a virtual proxy until it is needed (for example: using a virtual proxy to realize lazy loading of images)

Picture preloading: first take a place with a loading picture, then load the picture asynchronously, and then replace the loading picture with the original picture after the picture is loaded.

Ask what to use pre-loading + lazy loading? Take Taobao as an example. There are so many pictures of items in the mall. Requesting so many pictures at once is a huge workload for both the js engine and the browser itself. It will slow down the browser’s response speed and the user experience is extremely poor. The preloading + lazy loading method will greatly save the browser request speed. The placeholder image is loaded first through preloading (the second time and afterwards are read in the cache), and then lazy loading is used until the real image to be loaded is loaded. Replace instantly. This mode is a good solution to the shortcomings of poor user experience when the pictures are displayed on the page little by little.

Note: When the image is set to src for the first time, the browser sends a network request; if a requested src is set, the browser will read from disk cache from the cache

class PreLoadImage {
  constructor(imgNode) {
    // 获取真实的DOM节点
    this.imgNode = imgNode;
  }

  // 操作img节点的src属性
  setSrc(imgUrl) {
    this.imgNode.src = imgUrl;
  }
}

class ProxyImage {
  // 占位图的url地址
  static LOADING_URL = "xxxxxx";

  constructor(targetImage) {
    // 目标Image,即PreLoadImage实例
    this.targetImage = targetImage;
  }

  // 该方法主要操作虚拟Image,完成加载
  setSrc(targetUrl) {
    // 真实img节点初始化时展示的是一个占位图
    this.targetImage.setSrc(ProxyImage.LOADING_URL);
    // 创建一个帮我们加载图片的虚拟Image实例
    const virtualImage = new Image();
    // 监听目标图片加载的情况,完成时再将DOM上的真实img节点的src属性设置为目标图片的url
    virtualImage.onload = () => {
      this.targetImage.setSrc(targetUrl);
    };
    // 设置src属性,虚拟Image实例开始加载图片
    virtualImage.src = targetUrl;
  }
}

ProxyImage helped us schedule preloading related work. We can use ProxyImage achieve indirect access to the real img node and get the effect we want.

In this instance, virtualImage is a "hero behind the scenes", it always exists in the JavaScript world, instead of the real DOM, it initiates the image loading request and completes the image loading work, but it has never made a public appearance at the rendering level. So this mode is called "virtual agent" mode.

[Click to view Demo]: virtual agent-online example

Cache Proxy

The caching proxy is easier to understand, and it is used in some scenarios with a large amount of calculation. In this scenario, we need to "use space for time"-when we need to use a value that has already been calculated, we don't want to take time to perform a second calculation, but hope to get it from the memory. The result of the calculation.

In this scenario, an agent is needed to help us cache the calculation results while performing calculations.

Example: Cache proxy for parameter summation function.

// addAll方法会对你传入的所有参数做求和操作
const addAll = function() {
  console.log("进行了一次新计算");
  let result = 0;
  const len = arguments.length;
  for (let i = 0; i < len; i++) {
    result += arguments[i];
  }
  return result;
};

// 为求和方法创建代理
const proxyAddAll = (function() {
  // 求和结果的缓存池
  const resultCache = {};
  return function() {
    // 将入参转化为一个唯一的入参字符串
    const args = Array.prototype.join.call(arguments, ",");

    // 检查本次入参是否有对应的计算结果
    if (args in resultCache) {
      // 如果有,则返回缓存池里现成的结果
      console.log("无计算-使用缓存的数据");
      return resultCache[args];
    }
    return (resultCache[args] = addAll(...arguments));
  };
})();

let sum1 = proxyAddAll(1, 2, 3); // 进行了一次新计算

let sum2 = proxyAddAll(1, 2, 3); // 无计算-使用缓存的数据

The first calculation returns the result and stores it in the cache. If the same parameter is passed in again, it will not be calculated and the result in the cache will be returned directly.

In common HTTP caching, the browser is equivalent to a layer of proxy caching, and it is judged whether to enable caching through HTTP caching mechanism control (strong caching and negotiation caching).

Frequent network requests with small changes, such as getUserInfo , can use proxy requests to set up unified sending and access.

Summary

  • The agency model conforms to the open and closed principle.
  • The ontology object and the proxy object have the same methods, and the user does not know whether the request is an ontology object or a proxy object.

Bridge mode

Bridge mode: Separate the abstract part and the concrete realization part, the two can be changed independently or work together.

In the realization of this mode, an object is required to act as a "bridge" and play a role in connection.

example:

The typical application of bridge mode in JavaScript is: forEach function on Array

This function is responsible for looping through each element of the array, which is the abstract part ; and the callback function callback is the specific implementation part .

The following is the simulation method of forEach

const forEach = (arr, callback) => {
  if (!Array.isArray(arr)) return;

  const length = arr.length;
  for (let i = 0; i < length; ++i) {
    callback(arr[i], i);
  }
};

// 以下是测试代码
let arr = ["a", "b"];
forEach(arr, (el, index) => console.log("元素是", el, "位于", index));
// 元素是 a 位于 0
// 元素是 b 位于 1

Appearance mode

Facade Pattern hides the complexity of the system and provides an interface for the client to access the system. This type of design pattern is a structural pattern, which adds an interface to the existing system to hide the complexity of the system.

This pattern involves a single class that provides simplified methods for client requests and delegated calls to existing system class methods.

example

Appearance mode means that one method can be executed so that multiple methods can be called together.

When it comes to compatibility, the parameters support multiple formats, environments, etc.. Expose a unified api to the outside

For example, the self-encapsulated event object contains compatible methods to prevent bubbling and add event listeners:

const myEvent = {
    stop (e){
        if(typeof e.preventDefault() == 'function'){
            e.preventDefault();
        }
        if(typeof e.stopPropagation() == 'function'){
            e.stopPropagation()
        }
        // IE
        if(typeOd e.retrunValue === 'boolean'){
            e.returnValue = false
        }
        if(typeOd e.cancelBubble === 'boolean'){
            e.returnValue = true
        }
    }
    addEvnet(dom, type, fn){
        if(dom.addEventListener){
            dom.addEventlistener(type, fn, false);
        }else if(dom.attachEvent){
            dom.attachEvent('on'+type, fn)
        }else{
            dom['on'+type] = fn
        }
    }
}

Combination mode

The composite pattern (Composite Pattern), also called the partial overall pattern, is used to treat a group of similar objects as a single object.

The combination mode combines objects according to a tree structure, which is used to represent part and overall levels. This type of design pattern is a structural pattern, which creates a tree structure of object groups.

This pattern creates a class that contains its own object group. This class provides a way to modify the same object group.

example

Imagine that we now have multiple universal remote controls. When we get home and press the switch, the following things will be executed

  • Open the door
  • Turn on the computer
  • Music on
// 先准备一些需要批量执行的功能
class GoHome {
  init() {
    console.log("开门");
  }
}
class OpenComputer {
  init() {
    console.log("开电脑");
  }
}
class OpenMusic {
  init() {
    console.log("开音乐");
  }
}

// 组合器,用来组合功能
class Comb {
  constructor() {
    // 准备容器,用来防止将来组合起来的功能
    this.skills = [];
  }
  // 用来组合的功能,接收要组合的对象
  add(task) {
    // 向容器中填入,将来准备批量使用的对象
    this.skills.push(task);
  }
  // 用来批量执行的功能
  action() {
    // 拿到容器中所有的对象,才能批量执行
    this.skills.forEach((val) => {
      val.init();
    });
  }
}

// 创建一个组合器
let c = new Comb();

// 提前将,将来要批量操作的对象,组合起来
c.add(new GoHome()); // 添加'开门'命令
c.add(new OpenComputer()); // 添加'开电脑'命令
c.add(new OpenMusic()); // 添加'开音乐'命令

c.action(); // 执行添加的所有命令

Summary

  • The combination mode forms a tree structure between objects
  • In the combined mode, the basic object and the combined object are treated consistently
  • No need to care about how many layers the object has, you only need to call
  • Assemble the functions of multiple objects to achieve batch execution of

Flyweight model

Flyweight Pattern is mainly used to reduce the number of objects created to reduce memory usage and improve performance.

This type of design pattern is a structural pattern, which provides a way to reduce the number of objects to improve the object structure required by the application.

Features

  • Shared memory (mainly considering memory, not efficiency)
  • Same data (memory), shared use

example

For example, a common event proxy, by proxying the events of several child elements to a parent element, the child elements share a method. If they are all bound to the <span> tag, the memory overhead is too large.

<!-- 点击span,拿到当前的span中的内容 -->
<div id="box">
  <span>1</span>
  <span>2</span>
  <span>3</span>
  <span>4</span>
</div>

<script>
  var box = document.getElementById("box");
  box.addEventListener("click", function(e) {
    let target = e.target;
    if (e.nodeName === "SPAN") {
      alert(target.innerHTML);
    }
  });
</script>

Summary

  • Abstract out the same parts
  • Comply with the principle of open and closed

Behavioral

Iterator mode

The iterator pattern provides a way to sequentially access the elements in an aggregate object without exposing the internal representation of the object.

The iterator pattern can separate the iterative process from the business logic. After using the iterator pattern, you do not care about the internal structure of the object in time, and you can also access each element in order.

Simply put, its purpose is to traverse a traversable object.

Methods like the native forEach and map in JS are all implementations of the iterator pattern. Generally speaking, you don't need to implement the iterator yourself.

There is a type array in JS. They have no iterative method. For example, nodeList and arguments cannot directly use the iterative method. You need to use the each method of jQuery or replace the class number assembly with a real array for iterating.

In the latest ES6, for..of.. can be used to traverse any data type that has an Iterator interface. The bottom layer is repeated calls to the next method. For details, refer to 16077a03db0ee3 Ruan Yifeng-Iterator and for ...of loop .

example

We can implement an iterator ourselves with the Iterator interface.

class Creater {
  constructor(list) {
    this.list = list;
  }
  // 创建一个迭代器,也叫遍历器
  createIterator() {
    return new Iterator(this);
  }
}
class Iterator {
  constructor(creater) {
    this.list = creater.list;
    this.index = 0;
  }
  // 判断是否遍历完数据
  isDone() {
    if (this.index >= this.list.length) {
      return true;
    }
    return false;
  }
  next() {
    return this.list[this.index++];
  }
}

var arr = [1, 2, 3, 4];
var creater = new Creater(arr);
var iterator = creater.createIterator();
console.log(iterator.list); // [1, 2, 3, 4]
while (!iterator.isDone()) {
  console.log(iterator.next());
  // 1
  // 2
  // 3
  // 4
}

Summary

  1. Ordered data collections in JavaScript include Array, Map, Set, String, typeArray, arguments, NodeList, excluding Object
  2. Any data deployed with the [Symbol.iterator] interface can be traversed using the for...of loop
  3. The iterator mode separates the target object from the iterator object, which conforms to the open and closed principle

Subscribe/publish mode (observer)

The publish/subscribe model is also called the observer model, which defines a one-to-many dependency relationship between objects. When an object's state changes, all objects that depend on it will be notified. In JavaScrtipt, we generally use the time model to replace the traditional publish/subscribe model.

For example: the two-way binding and event mechanism in Vue.

The difference between publish/subscribe mode and observer mode

  • The publisher can directly receive the subscription operation, called the observer mode
  • The publisher does not directly reach the subscribers, but a unified third party completes the communication operation, which is called the publish/subscribe model

    发布订阅模式和观察者模式.png

example

You can implement an event bus yourself to simulate $emit and $on

class EventBus {
  constructor() {
    this.callbacks = {};
  }
  $on(name, fn) {
    (this.callbacks[name] || (this.callbacks[name] = [])).push(fn);
  }
  $emit(name, args) {
    let cbs = this.callbacks[name];
    if (cbs) {
      cbs.forEach((c) => {
        c.call(this, args);
      });
    }
  }
  $off(name) {
    this.callbacks[name] = null;
  }
}
let event = new EventBus();
event.$on("event1", (arg) => {
  console.log("event1", arg);
});

event.$on("event2", (arg) => {
  console.log("event2", arg);
});

event.$emit("event1", 1); // event1 1
event.$emit("event2", 2); // event2 2

Strategy mode

Define a series of algorithms, encapsulate them one by one, and make them replaceable.

The purpose of the strategy mode is to separate the use of the algorithm from the realization of the algorithm.

A strategy pattern usually consists of two parts:

  • A set of variable strategy classes: encapsulates specific algorithms and is responsible for specific calculation processes
  • A set of unchanging environment classes: after receiving the request, the request is then delegated to a certain strategy class

Explain that the environment class should maintain a reference to a certain policy object.

example

To calculate the bonus through the performance level, you can easily write the following code:

var calculateBonus = function(performanceLevel, salary) {
  if (performanceLevel === "S") {
    return salary * 4;
  }
  if (performanceLevel === "A") {
    return salary * 3;
  }
  if (performanceLevel === "B") {
    return salary * 2;
  }
};

calculateBonus("B", 20000); // 输出:40000
calculateBonus("S", 6000); // 输出:24000

Use strategy mode to modify the code:

var strategies = {
  S: (salary) => {
    return salary * 4;
  },
  A: (salary) => {
    return salary * 3;
  },
  B: (salary) => {
    return salary * 2;
  },
};
var calculateBonus = function(level, salary) {
  return strategies[level](salary);
};
console.log(calculateBonus("S", 200)); // 输出:800
console.log(calculateBonus("A", 200)); // 输出:600

State mode

State mode allows an object to change when its internal state changes

The state mode mainly solves the situation when the conditional expression that controls the state of an object is too complicated. By transferring the judgment logic of the state to a series of classes representing different states, the complex judgment logic can be simplified.

example

Realize the switch of a traffic light.

Click to view Demo: traffic signal light-online example

At this time, if you are adding a Blu-ray, you can directly add a Blu-ray class, and then add the parssBtn method, and other states do not need to change.

Summary

  • By defining different state classes and changing the behavior of the state according to the change of state, it is not necessary to write a large amount of logic in the class of the operated object, and it is easy to add new states.
  • Comply with the open and closed principle

Interpreter mode

Interpreter Mode (Interpreter): Given a language, define a representation of its grammar, and define an interpreter, the interpreter uses the representation to interpret sentences in the language.

There are relatively few used, you can refer to two articles to understand.

Summary

  • Describe how the language syntax is defined, how to interpret and compile
  • For professional scenes

Intermediary model

The Mediator Pattern is used to reduce the complexity of communication between multiple objects and classes.

This mode provides an intermediary class, which usually handles the communication between different classes, and supports loose coupling, making the code easy to maintain

Through an intermediary object, all other related objects communicate through this object instead of referencing each other, but when one of the objects changes, only the intermediary object needs to be notified.

The intermediary model can release the coupling relationship between the object and the object.

For example: Vuex

middle-parttern.png

Reference link: JavaScript intermediary mode

Summary

  • Isolate related objects through intermediaries
  • Comply with the open and closed principle
  • Reduce coupling

Visitor mode

In the Visitor Pattern (Visitor Pattern), we use a visitor class, which changes the execution algorithm of the element class.

In this way, the execution algorithm of the element can be changed as the visitor changes.

example

Call the method of the element class through the visitor.

// 访问者
function Visitor() {
  this.visit = function(concreteElement) {
    concreteElement.doSomething(); // 谁访问,就使用谁的doSomething()
  };
}
// 元素类
function ConceteElement() {
  this.doSomething = function() {
    console.log("这是一个具体元素");
  };
  this.accept = function(visitor) {
    visitor.visit(this);
  };
}
// Client
var ele = new ConceteElement();
var v = new Visitor();
ele.accept(v); // 这是一个具体元素

Summary

  • If there are some operations that are irrelevant (or weakly related) to the object in an object, in order to avoid these operations from polluting the object, you can use the visitor pattern to encapsulate these operations into the visitor.
  • If there are similar operations in a group of objects, in order to avoid a large amount of repeated code, these repeated operations can also be encapsulated in the visitor.

Memo mode

Memento Pattern (Memento Pattern) saves a certain state of an object in order to restore the object at an appropriate time

example

Realize an "editor" with the function of saving records, the functions include

  • Record the state change of an object at any time
  • You can restore a previous state at any time (such as undo function)
// 状态备忘
class Memento {
  constructor(content) {
    this.content = content;
  }
  getContent() {
    return this.content;
  }
}

// 备忘列表
class CareTaker {
  constructor() {
    this.list = [];
  }
  add(memento) {
    this.list.push(memento);
  }
  get(index) {
    return this.list[index];
  }
}

// 编辑器
class Editor {
  constructor() {
    this.content = null;
  }
  setContent(content) {
    this.content = content;
  }
  getContent() {
    return this.content;
  }
  saveContentToMemento() {
    return new Memento(this.content);
  }
  getContentFromMemento(memento) {
    this.content = memento.getContent();
  }
}

// 测试代码
let editor = new Editor();
let careTaker = new CareTaker();

editor.setContent("111");
editor.setContent("222");
careTaker.add(editor.saveContentToMemento()); // 存储备忘录
editor.setContent("333");
careTaker.add(editor.saveContentToMemento()); // 存储备忘录
editor.setContent("444");

console.log(editor.getContent()); // 444
editor.getContentFromMemento(careTaker.get(1)); // 撤销
console.log(editor.getContent()); // 333
editor.getContentFromMemento(careTaker.get(0)); // 撤销
console.log(editor.getContent()); // 222

Summary

  • The state object is separated from the user (decoupled)
  • Comply with the open and closed principle

Template method pattern

In Template Pattern, an abstract class publicly defines the way/template of its methods.

Its subclasses can be implemented by overriding methods as needed, but the call will be made in the way defined in the abstract class.

I don't feel that I use a lot. If you want to know, you can click on the reference link below.

Reference: JavaScript design pattern template method pattern

Chain of Responsibility Model

As the name implies, the Chain of Responsibility Pattern creates a chain of recipient objects for the request.

This mode gives the type of request and decouples the sender and receiver of the request.

In this model, usually each recipient contains a reference to another recipient.

If an object cannot handle the request, it will pass the same request to the next recipient, and so on.

example

The company’s reimbursement approval process: team leader = "project manager =" chief financial officer

// 请假审批,需要组长审批、经理审批、最后总监审批
class Action {
  constructor(name) {
    this.name = name;
    this.nextAction = null;
  }
  setNextAction(action) {
    this.nextAction = action;
  }
  handle() {
    console.log(`${this.name} 审批`);
    if (this.nextAction != null) {
      this.nextAction.handle();
    }
  }
}

let a1 = new Action("组长");
let a2 = new Action("项目经理");
let a3 = new Action("财务总监");
a1.setNextAction(a2);
a2.setNextAction(a3);
a1.handle();
// 组长 审批
// 项目经理 审批
// 财务总监 审批

// 将一步操作分为多个职责来完成,一个接一个的执行,最终完成操作。

Summary

  • You can think of chain operations like jQuery and Promise
  • Initiator and processor are isolated
  • Comply with the development closed principle

Command mode

Command Pattern (Command Pattern) is a data-driven design pattern, which is a behavioral pattern.

The request is wrapped in the object in the form of a command and passed to the calling object.

The calling object looks for a suitable object that can handle the command, and passes the command to the corresponding object, which executes the command.

example

To implement an editor, there are many commands, such as: write, read, and so on.

class Editor {
  constructor() {
    this.content = "";
    this.operator = [];
  }
  write(content) {
    this.content += content;
  }
  read() {
    console.log(this.content);
  }
  space() {
    this.content += " ";
  }
  readOperator() {
    console.log(this.operator);
  }
  run(...args) {
    this.operator.push(args[0]);
    this[args[0]].apply(this, args.slice(1));
    return this;
  }
}

const editor = new Editor();

editor
  .run("write", "hello")
  .run("space")
  .run("write", "zkk!")
  .run("read"); // => 'hello zkk!'

// 输出操作队列
editor.readOperator(); // ["write", "space", "write", "read"]

Summary

  • Reduce coupling
  • New commands can be easily added to the system

Summary

(1) The final object-oriented design goal:

  • A. Scalability: With new requirements, new performance can be easily added to the system without affecting the existing performance or bringing new defects.
  • B. Flexibility: Adding new function codes and modifying them happen smoothly without affecting other parts.
  • C replaceability: Some codes in the system can be replaced with other classes of the same interface without affecting the system.

(2) The benefits of design patterns:

  • A design pattern allows people to reuse successful designs and architectures more simply and conveniently.
  • The B design pattern will also make it easier for new system developers to understand their design ideas.

(3) There are three levels of learning design patterns (see many times on the Internet):

  • The first weight: When you learn a design pattern, you are thinking about where you can use it in the project I just made (a knife in your hand, no knife in your heart)
  • Second: You have finished learning design patterns, but when you encounter a problem, you find that there are several design patterns for you to choose from, and you have nowhere to go (a knife in your hand, and a knife in your heart)
  • The third level: and the last level. You may not have the concept of design patterns. You only have a few major design principles in your mind. When you use them, you will be able to use them at your fingertips. (The highest state of the knife: no knife in your hand, no knife in your heart)

Conclusion

The following is an excerpt from the Nuggets Booklet- JavaScript Design Pattern Core Principles and Application Practice 's conclusion.

The journey of design patterns has come to an end. But for everyone, the real battle has just begun. The charm of design patterns is not on paper, but in practice.

Learn design patterns:

One is reading more-read the source code, read the information, read the good book;

Second, practice more-restore what you have learned to business development and see if it is OK. Is there any problem? If there is a problem, how to fix it and how to optimize it? No design pattern is perfect. Design patterns are in the process of dynamic development just like people. The 23 design patterns proposed by GOF are not the only ones that can be called design patterns.

As long as a scheme follows the design principles and solves a type of problem, it can be crowned with the honor of "design mode".

When you graduate from the design pattern booklet, I hope you will take away not only knowledge, but also good study habits and reading habits. The most important thing is the courage to dig deep into theoretical knowledge and the determination to tackle technical problems. These things are not the patents of the so-called "science class", but the necessity of a good engineer.

reference

Original from the : 16077a03dcb606 blog original link

九旬
1.1k 声望1.2k 粉丝