Design pattern is a programming idea, regardless of front-end and back-end. They will have some same design ideas for programs running in any language and environment. By understanding these ideas, we can improve our programming ability, and writing code will become something to enjoy. We are not only completing the work, but also creating a work, a work of art that belongs to you and is pleasing to the heart- My own understanding
Design principles
You need to follow some guidelines to do everything, and design patterns are no exception. When designing some design patterns, we generally follow the following seven basic principles.
- Single responsibility principle
- Open closed principle
- Liskov Substitution Principle
- Dependency Inversion Principle
- Interface Segregation Principle
- The Least Knowledge Principle
- Composite / Aggregate Reuse Principle
Among these principles, I think the more practical and important are: the principle of single responsibility, the principle of open-close and the principle of least knowledge. Because these basic principles are used as conventions, our project will develop healthily and have strong expansibility. Explain these three principles in detail. If you are interested in other principles, you can search them yourself and find them easier.
Single responsibility principle
Single Responsibility Principle
- An object or method does only one thing.
- If a method assumes too many responsibilities, the more likely it is to rewrite the method in the process of demand change.
- Objects or methods should be divided into smaller granularity to improve code readability and system maintainability.
Open close principle
Open for extension, close for modification
- Open to extension: when there are new requirements or changes, you can extend the existing code to adapt to the new situation.
- Close to modification: once the design is completed, the source code of the module cannot be infringed, and no one is allowed to modify the existing source code.
- The core idea of opening and closing is to program abstractly, not concretely, because abstractions are relatively stable.
- Let the class depend on fixed abstraction, so the modification is closed;
- Through the object-oriented inheritance and polymorphism mechanism, we can inherit the abstract body, change the inherent behavior by overwriting its methods, and realize new extension methods. Therefore, it is open to extension.
Least known principle
The Least Knowledge Principle
- Or Law of Demeter, which describes a strategy to maintain loose code coupling. Each unit has only limited knowledge of other units and only knows the units closely related to the current unit;
- Modern object-oriented programming languages usually use "." as the access identifier, and LoD can be simplified to "use only one dot".
- That is, the code a.b.Method() violates the LoD, while a.Method() conforms to LoD. For example, people can command a dog to walk, but they should not direct the dog's legs to walk, but the dog should direct its legs to walk.
Design pattern
In JS design pattern, the core idea is encapsulation change.
Separate change from invariance to ensure that the changed part is flexible and the unchanged part is stable.
Constructor mode
Constructor Pattern features:
- Constructor is a special function used to initialize a new object after its memory is allocated. Almost everything in JavaScript is an object.
- At the same time, the constructor can use parameters to set the value of the method of the member property when the object is first created.
- Object definition: status, attribute, behavior
Example code:
function GirlFriend(name, age, cup, height) { this.name = name; this.age = age; this.cup = cup; this.height = height; } GirlFriend.prototype.myIndentity = function () { console.log(`I'm a created girlfriend. My name is ${this.name}`); }; const aGirl = new GirlFriend('Small A girl friend', 28, '36F', 'LOL'); const bGirl = new GirlFriend('Small B girl friend', 19, '36C', 'DOTA');
Module mode
Module Pattern features:
- Module mode is the most commonly used mode in the front end, which is especially suitable for maintaining an independent code fragment.
- Module mode is the basis of almost all JS popular plug-ins and frameworks, such as jQuery, Backbone, Ember, etc.
- You can create private and public attributes.
- Module mode uses IIFE (immediate call function expression) and closed packet to form private scope.
Example code:
const Exposer = (function () { let privateVariable = 10; const privateMethod = function () { console.log('Inside a private method!'); privateVariable++; } return { first: function () { console.log('This is a method I want to expose!'); }, second: function () { privateMethod(); } }; })(); Exposer.first(); // Output: This is a method I want to expose! Exposer.second(); // Output: Inside a private method! Exposer.privateMethod; // undefined
Singleton mode
Singleton Pattern features:
- The singleton mode limits the number of instances to one. This unique instance is called a singleton.
- Singleton mode is suitable for handling some logic in a centralized place in a wide range of systems.
- The use of global variables is reduced, thereby eliminating the risk of namespace pollution and naming conflicts.
- Example: database connection pool. The connection pool needs to manage the life cycle of many connections so that connections can be reused when requests are made to the database in the future.
Example code:
const Singleton = (function () { let instance; // example function init() { return { publicProperty: 'I am a property', publicMethod: function () { console.log('I am a mehtod'); }, }; } return { getInstance: function () { if (!instance) { instance = init(); } return instance; } }; })(); // Get instance const instance1 = Singleton.getInstance(); const instance2 = Singleton.getInstance();
Adapter mode
Adapter Pattern features:
- Adapter pattern can convert an interface to a completely different interface, which allows different components of the interface to work together.
- Example: the newly developed components are completely different from the previous interfaces, and can be compatible with the existing old code through the adapter mode.
Example code:
// Package pricing - old service function Shipping() { this.request = function (zipStart, zipEnd, weight) { // ... return "$49.75"; } } // Package pricing - new service (with authority verification and logical decoupling) function AdvancedShipping() { this.login = function (credentials) { /* ... */ }; this.setStart = function (start) { /* ... */ }; this.setDestination = function (destination) { /* ... */ }; this.calculate = function (weight) { return "$39.50"; }; } // Adapter function ShippingAdapter(credentials) { // create a new service const shipping = new AdvancedShipping(); // Verify login permissions shipping.login(credentials); return { // Maintain the old service interface and adapt the content logic to the new service request: function (zipStart, zipEnd, weight) { shipping.setStart(zipStart); shipping.setDestination(zipEnd); return shipping.calculate(weight); } }; } // Call old service const shipping = new Shipping(); const cost = shipping.request("78701", "10010", "2 lbs"); // Call new service code const credentials = { token: "30a8-6ee1" }; const adapter = new ShippingAdapter(credentials); const cost = adapter.request("78701", "10010", "2 lbs");
Factory mode
Simple Factory Pattern definition:
- The factory model, as the name suggests, is to create objects.
- The factory model is similar to the real factory production line, which can produce a large number of similar goods.
- The advantage of factory mode is that it can solve multiple similar problems and reduce a large amount of redundant code.
Example code:
const FactoryGirlFriend = function () { function GirlFriend(name, age, cup, height, nationality) { this.name = name; this.age = age; this.cup = cup; this.height = height; this.nationality = nationality } this.create = function (category) { switch (category) { case 'Imperial sister type': return new GirlFriend('Small A girl friend', 28, '36F', 170, 'China') break; case 'Lori type': return new GirlFriend('Small B girl friend', 19, '36C', 160, 'Japan') break; default: throw new Error('Parameter error') } }; } const aGirl = FactoryGirlFriend.create('Imperial sister type'); const bGirl = FactoryGirlFriend.create('Lori type');
Strategy mode
Strategy Pattern features:
- The policy pattern defines a series of algorithms, encapsulates them one by one, and enables them to replace each other.
- The policy pattern can effectively avoid many if conditional statements.
- The policy pattern conforms to the open closed principle, making the code easier to understand and expand.
- The code in the policy pattern can be reused.
Example code:
const express = { 'Shunfeng': function (package) { // Path calculation return "¥45.95"; }, 'JD.COM': function (package) { // Path calculation return "¥39.40"; }, 'Yunda': function (package) { // Path calculation return "¥24.45"; } }; const calculateExpense = function (type, package) { return express[type] ? express[type](package) : 0; }; calculateExpense('JD.COM', 1); // ¥39.40 calculateExpense('Yunda', 1); // ¥24.45
Observer mode
Observer Pattern features:
- The observer mode includes two types of objects: observation target and observer,
- A target can have any number of dependent observers,
- Once the state of the observation target changes, all observers will be notified.
Example code:
// Define a host object class Subject { constructor() { this.observers = []; // Observer } add(observer) { // add to this.observers.push(observer) } remove(observer) { // remove this.observers = this.observers.filter(item => item !== observer); } notify() { // notice this.observers.forEach(item => { item.update(); }) } } // Define the observed object class Observer { constructor(name) { this.name = name; } update() { console.log(`my name is:${this.name}`); } } // call const observer1 = new Observer('observer1'); const observer2 = new Observer('observer2'); const subject = new Subject(); subject.add(observer1); subject.add(observer2); subject.notify();
Publish subscribe mode
Publish subscribe pattern features:
- Publish + subscribe = observer mode?
- In the observer mode, the observer knows the Subject, and the Subject keeps a record of the observer. However, in the publish subscribe mode, publishers and subscribers do not know the existence of each other, and they only communicate through the message broker.
- In publish subscribe mode, components are loosely coupled, as opposed to observer mode.
Example code:
const publisher = { topics: [], // Subscribable topics subscribe(key, fn) { // subscribe if (!this.topics[key]) { this.topics[key] = []; } this.topics[key].push(fn); }, unSubscribe(key, fn) { // delete let fns = this.topics[key]; for (let i = 0; i < fns.length; ++i) { if (fns[i] === fn) { fns.splice(i, 1); } } }, publish(...args) { // release let key = args.shift(); let fns = this.topics[key]; if (!fns || fns.length <= 0) return; for (let i = 0, len = fns.length; i < len; i++) { fns[i].apply(this, args); } } } // call publisher.subscribe('name', (name) => { console.log(`your name is ${name}`); }) publisher.subscribe('gender', (gender) => { console.log(`your gender is ${gender}`); }) publisher.publish('name', 'Ultraman'); // your name is Altman publisher.publish('gender', 'male'); // your gender is male
Example code: the role of publish subscribe mode in vue
// Traverse the properties of the data object of the incoming instance and set it as the accessor property of the Vue object function observe(obj, vm) { Object.keys(obj).forEach(function (key) { defineReactive(vm, key, obj[key]); }); } // Set as accessor property and use subscription publication mode in its getter and setter functions. Listen to each other. function defineReactive(obj, key, val) { // The observer mode is used here. It defines a one to many relationship, allowing multiple observers to listen to a topic object, // When the state of the subject object changes, all observer objects will be notified, and the observer object can update its own state. // Instantiate a topic object, and there is an empty observer list in the object let dep = new Dep(); // Set each attribute of data as the accessor attribute of Vue object, and the attribute name is the same as that in data // Therefore, each time Vue.data is modified, the get and set methods below will be called. Then it will listen for the input event of v-model, // When the value of input is changed, the data of Vue.data is changed accordingly, and then the set method here is triggered Object.defineProperty(obj, key, { get: function () { // The Dep.target pointer points to the watcher and adds the subscriber watcher to the subject object Dep if (Dep.target) { dep.addSub(Dep.target); } return val; }, set: function (newVal) { if (newVal === val) { return } val = newVal; // console.log(val); // Notify watchers in the subscriber list dep.notify(); } }); } // Subject object Dep constructor function Dep() { this.subs = []; } // Dep has two methods, adding subscribers and publishing messages Dep.prototype = { addSub: function (sub) { this.subs.push(sub); }, notify: function () { this.subs.forEach(function (sub) { sub.update(); }); } }
proxy pattern
Proxy Pattern features:
- Provide a proxy for other objects to control access to this object, and the client can't even perceive the existence of the proxy layer.
- The proxy object can be instantiated instead of the ontology object. At this time, the ontology object is not really instantiated, and it can be instantiated at an appropriate time.
- Proxy mode can delay the creation of expensive ontology objects. It will delay the instantiation of ontology until a method is called.
Example code: image loading
// Normal mode const myImage = (function () { const imgNode = document.createElement('img'); return { setSrc: function (src, id) { imgNode.src = src; document.getElementById(id).appendChild(imgNode); } } })(); // Loading pictures in normal mode myImage.setSrc('https://tengmed.com/a.png', "avatar"); // proxy pattern const ProxyImage = (function () { const img = new Image(); img.onload = function () { myImage.setSrc(this.src, this.id); }; return { setSrc: function (src, id) { myImage.setSrc('http://loading.gif', id); // Add default loading picture img.src = src; img.id = id; } } })(); // Loading pictures in proxy mode ProxyImage.setSrc('https://tengmed.com/a.png', "avatar");
Example code: cache agent
let mult = function () { let a = 1; for (let i = 0, ilen = arguments.length; i < ilen; i += 1) { a = a * arguments[i]; } return a; }; // Computational addition let plus = function () { let a = 0; for (let i = 0, ilen = arguments.length; i < ilen; i += 1) { a += arguments[i]; } return a; } // Surrogate function let proxyFunc = function (fn) { let cache = {}; // Cache object return function () { let args = Array.prototype.join.call(arguments, ','); if (args in cache) { return cache[args]; // Using cache proxy } return cache[args] = fn.apply(this, arguments); } }; let proxyMult = proxyFunc(mult); console.log(proxyMult(1, 2, 3, 4)); // 24 console.log(proxyMult(1, 2, 3, 4)); // Cache fetch 24 let proxyPlus = proxyFunc(plus); console.log(proxyPlus(1, 2, 3, 4)); // 10 console.log(proxyPlus(1, 2, 3, 4)); // Cache fetch 10