Each mode is to refer to a variety of information summed up, this article is long, I hope to help you, if useful to you, please like to support a, is also to give me the motivation to write
Introduction to Design Patterns
Design patterns represent best practices and are generally adopted by experienced object-oriented software developers. Design pattern is a solution to the common problems faced by software developers during software development. These solutions have been developed by numerous software developers over a long period of trial and error.
A design pattern is a set of repeated, well-known, catalogued code design lessons. Design patterns are used to reuse code, make it easier for others to understand, and ensure code reliability. There is no doubt that design patterns are a win-win for yourself, others and systems. Design patterns make coding truly engineering. Design patterns are the cornerstones of software engineering, like the bricks and stones of a mansion.
Design pattern principles
- S — Single Responsibility Principle
- A program only does one thing
- If the functionality is too complex, break it down and keep each part separate
- O — OpenClosed Principle
- Open to extension, closed to modification
- When adding requirements, extend new code, not modify existing code
- L — Liskov Substitution Principle
- A subclass can override its parent class
- Subclasses can appear wherever a parent class can
- I-interface Segregation Principle Interface Segregation Principle
- Keep interfaces single and separate
- Similar to the single responsibility principle, there is a greater focus on interfaces
- D — Dependency Inversion Principle
- Interface – oriented programming relies on abstraction rather than concrete
- The user only cares about the interface and not the implementation of the concrete class
SO, for example :(e.g. Promise)
- The single responsibility principle: The logic in each then does one thing well
- Open closed principle (open to extensions, closed to modifications) : If new requirements are added, extend then
For example, I would like to give you another exampleVigil – Improves all aspects of the code)
//result: false let checkType=function(STR, type) {switch (type) {case 'email': return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str) case 'mobile': return /^1[3|4|5|7|8][0-9]{9}$/.test(str); Case 'tel: return / ^ (0 \ d {2, 3} - \ d {7, 8}) (\ d {1, 4})? $/.test(str); default: return true; }}Copy the code
There are two problems:
- If you want to add other rules, you have to add cases to the function. Add a rule and change it once! This violates the open-closed principle (open for extensions, closed for modifications). It also makes the entire API bloated and difficult to maintain.
- For example, A value check is required on page A and A date check is required on page B, but the value check is required only on page A and the date check is required only on page B. If you keep adding cases. That is, page A adds verification rules that are only needed on page B, resulting in unnecessary overhead. The same goes for page B.
The suggested approach is to add an extended interface to the API:
let checkType=(function(){ let rules={ email(str){ return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str); }, mobile(str){ return /^1[3|4|5|7|8][0-9]{9}$/.test(str); }}; Return {check(STR, type){return rules[type]? rules[type](str):false; }, // addRule addRule(type,fn){rules[type]=fn; }}}) (); Console. log(checktype. check('188170239','mobile')); / / add amount checking rules checkType. AddRule (' money ', function (STR) {return / ^ [0-9] + (. [0-9] {2})? $/.test(str) }); Console. log(checktype. check('18.36','money'));Copy the code
For more details on this example, see -> Waiting for I – Refactoring – Improving aspects of your code
Classification of Design Patterns (23 design Patterns)
- Create a type
- The singleton pattern
- The prototype pattern
- The factory pattern
- Abstract Factory pattern
- Builder model
- structured
- Adapter mode
- Decorator mode
- The proxy pattern
- The appearance model
- The bridge model
- Portfolio model
- The flyweight pattern
- Behavior type
- Observer model
- Iterator pattern
- The strategy pattern
- Template method pattern
- Chain of Responsibility model
- Command mode
- Memo mode
- The state pattern
- Visitor pattern
- The mediator pattern
- Interpreter mode
The factory pattern
The factory pattern defines an interface for creating objects that subclasses decide which class to instantiate. This pattern delays instantiation of a class to subclasses. Subclasses can override interface methods to specify their own object types when they are created.
class Product {
constructor(name) {
this.name = name
}
init() {
console.log('init')
}
fun() {
console.log('fun')
}
}
class Factory {
create(name) {
return new Product(name)
}
}
// use
let factory = new Factory()
let p = factory.create('p1')
p.init()
p.fun()
Copy the code
Applicable scenario
- The factory pattern is ideal if you don’t want strong coupling between one subsystem and the larger object, but want the runtime to pick and choose from many subsystems
- Encapsulate the new operation simply. When encountering new, we should consider whether to use factory mode.
- When you need to create different instances that have the same behavior depending on the specific environment, you can use the factory pattern to simplify the implementation process and reduce the amount of code required for each object, which helps eliminate the coupling between objects and provides greater flexibility
advantages
- The process of creating an object can be complex, but we only need to care about the result.
- Constructor and creator separated, in accordance with the “open closed principle”
- A caller who wants to create an object needs only to know its name.
- High scalability. If you want to add a product, just extend a factory class.
disadvantages
- When new products are added, new specific product classes need to be written, which increases the complexity of the system to a certain extent
- Considering the scalability of the system, it is necessary to introduce an abstraction layer, which is used to define the client code, increasing the abstraction and difficulty of understanding the system
When not to use it
When applied to the wrong type of problem, this pattern introduces a great deal of unnecessary complexity into the application. Unless providing an interface for creating objects is a design goal of the library or framework we write, I recommend using explicit constructors to avoid unnecessary overhead.
Because of the fact that the object creation process is efficiently abstracted behind an interface, this also poses problems for unit tests that depend on how complex the process may be.
example
- The once familiar $() of JQuery is a factory function that creates elements depending on the parameters passed in or finds elements in context to create the appropriate JQuery object
Class jQuery {constructor(selector) {super(selector)} add() {}} window.$= function(selector) {return new jQuery(selector) }Copy the code
- Asynchronous components of VUE
In large applications, we may need to break the application into smaller code blocks and load a module from the server only when needed. For simplicity, Vue allows you to define your component as a factory function that asynchronously parses your component definition. Vue fires the factory function only when the component needs to be rendered, and caches the result for future re-rendering. Such as:
Vue.component('async-example', function (resolve, Reject) {setTimeout(function () {resolve({template: '<div>I am async! </div>' }) }, 1000) })Copy the code
The singleton pattern
A class has only one instance and provides a global access point to access it.
Class LoginForm {constructor() {this.state = 'hide'} show() {if (this.state === 'show') {alert(' already shown ') return} This. State = 'show' console. The log (' login box shows success ')} hide () {if (this. State = = = 'hide') {alert (' has hidden) return} this. State = 'hide' console.log(' hide' console.log ')}} LoginForm. GetInstance = (function () {let instance return function () {if (! instance) { instance = new LoginForm() } return instance } })() let obj1 = LoginForm.getInstance() obj1.show() let obj2 = LoginForm.getInstance() obj2.hide() console.log(obj1 === obj2)Copy the code
advantages
- Partition the namespace and reduce global variables
- Improve modularity, organize your code in a global variable name, in a single location, easy maintenance
- And it will only be instantiated once. Simplifies code debugging and maintenance
disadvantages
- Because the singleton pattern provides a single point of access, it can lead to strong coupling between modules that is not conducive to unit testing. You cannot test a class that calls a method from a singleton alone, but only with that singleton as a unit.
Example scenario
- Define namespaces and implement branching methods
- The login dialog
- Store in vuex and Redux
Adapter mode
The interface of one class is converted into another interface to meet user requirements, so that interface incompatibilities between classes can be solved through adapters.
Class Plug {getName() {return 'iPhone charger '; } } class Target { constructor() { this.plug = new Plug(); } getName() {return this.plug.getName() + 'adapter Type-c charging head '; } } let target = new Target(); target.getName(); // iPhone charging head adapter to Type-C charging headCopy the code
advantages
- You can have any two unrelated classes run together.
- Improved class reuse.
- Adapt objects, adapt libraries, adapt data
disadvantages
- The creation of additional objects, not directly called, has some overhead (and unlike the proxy pattern, performance is optimized at some function points)
- If the adapter pattern is not necessary, consider refactoring, and if so, document as well as possible
scenario
- Integrate third-party SDKS
- Encapsulate old interface
Ajax ({url: '/getData', type: 'Post', dataType: 'json', data: {test: 111}}) done (function () {}) / / because of historical reasons, the code is all: / / $. Ajax ({... Var $= {ajax: function (options) {return ajax(options)}}Copy the code
- The computed vue
<template> <div id="example"> <p>Original message: "{{ message }}"</p> <! -- Hello --> <p>Computed reversed message: "{{ reversedMessage }}"</p> <! -- olleH --> </div> </template> <script type='text/javascript'> export default { name: 'demo', data() { return { message: 'Hello' } }, computed: { reversedMessage: function() { return this.message.split('').reverse().join('') } } } </script>Copy the code
The data in the original data does not meet the current requirements, and is adapted to the format we need through the rules of calculating attributes. The original data is not changed, but only the expression form of the original data
The difference between
The adapter and proxy patterns are similar
- Adapter pattern: Provide a different interface (such as different versions of plugs)
- Proxy mode: provides identical interfaces
Decorator pattern
- Adding additional responsibilities to an object dynamically is an alternative to implementing inheritance
- By wrapping and extending the original object without changing it, the original object can meet the user’s more complex needs without affecting other objects derived from this class
}} class Decorator {constructor(Cellphone) {this. Cellphone = Create () this.cellphone. Create () this.createshell (cellphone)} createShell() {console.log(' Generate mobile case ')}} // Create () console.log('------------') Let dec = new Decorator(cellphone) dec.create()Copy the code
Example scenario
- For example, there are four types of bicycles, and we have defined a list for each of them
Alone in the class. Now, each bike has to be fitted with three accessories: headlights, tail lights and bells. If you use inheritance to create subclasses for each bike, you need 4×3 = 12 subclasses. But if you dynamically group objects like headlights, taillights, and bells onto a bike, you only need to add three additional classes
- Decorator ES7 Decorator
- core-decorators
advantages
- The decorator class and the decorator class are decoupled from each other by caring only about their core business.
- Convenient dynamic extension functionality, and provides more flexibility than inheritance.
disadvantages
- Multi-layer decoration is more complicated.
- Our application architecture is often complicated by the introduction of many small objects that look similar but do very different things
The proxy pattern
Is to provide a proxy or placeholder for an object to control access to it
Assuming that A receives flowers when she is in A good mood, xiaoming has A chance of success
60%, and when A received flowers when she was in A bad mood, the success rate of Xiao Ming’s confession approached 0. Xiao Ming and A have only known each other for two days and still can’t tell when A is in A good mood. If you send the flowers to A in an inappropriate way, there is A high possibility that the flowers will be thrown away directly. Xiao Ming got the flowers after eating instant noodles for seven days. However, A’s friend B knows A very well, so Xiaoming just gives the flower to B, and B will monitor A’s mood change and then choose A to give the flower to A when A is in A good mood, the code is as follows:
let Flower = function() {} let xiaoming = { sendFlower: function(target) { let flower = new Flower() target.receiveFlower(flower) } } let B = { receiveFlower: function(flower) { A.listenGoodMood(function() { A.receiveFlower(flower) }) } } let A = { receiveFlower: Function (flower) {console.log(' received flowers '+ flower)}, listenGoodMood: function(fn) { setTimeout(function() { fn() }, 1000) } } xiaoming.sendFlower(B)Copy the code
scenario
- HTML element event broker
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
let ul = document.querySelector('#ul');
ul.addEventListener('click', event => {
console.log(event.target);
});
</script>
Copy the code
- ES6 proxy Ruan Yifeng Proxy
- JQuery. The proxy () method
advantages
- The proxy mode can separate the proxy object from the called object and reduce the coupling degree of the system. The proxy mode acts as an intermediary between the client and the target object, thus protecting the target object
- Proxy objects extend the functionality of target objects; By modifying the proxy object is ok, in line with the open and close principle;
disadvantages
The speed of processing requests can vary, and indirect access has overhead
The difference between
The decorator pattern is similar in implementation to the proxy pattern
- Decorator mode: extended functions, original functions remain unchanged and can be used directly
- Proxy mode: shows the original functionality, but with limitations
The appearance model
Providing a consistent interface for a set of interfaces for a subsystem defines a high-level interface that makes the subsystem easier to use
- Compatible with browser event binding
let addMyEvent = function (el, ev, fn) {
if (el.addEventListener) {
el.addEventListener(ev, fn, false)
} else if (el.attachEvent) {
el.attachEvent('on' + ev, fn)
} else {
el['on' + ev] = fn
}
};
Copy the code
- Encapsulated interface
let myEvent = { // ... stop: e => { e.stopPropagation(); e.preventDefault(); }};Copy the code
scenario
- Early in the design process, there should be a conscious separation of the two different layers, such as the classic three-tier structure, with a Facade between the data access layer and the business logic layer, and between the business logic layer and the presentation layer
- During development, subsystems tend to become more complex as they evolve through refactoring, and adding a Facade can provide a simple interface that reduces dependencies between them.
- Using a Facade is also appropriate when maintaining a large legacy system that may already be difficult to maintain. Develop a Facade class for the system to provide a clear interface to poorly designed and highly complex legacy code to allow the new system to interact with Facade objects. The Facade interacts with the legacy code for all the complex work.
Reference: Big talk design patterns
advantages
- Reduce system interdependence.
- Increase flexibility.
- Improved security
disadvantages
- Inconsistent open and close principle, if you want to change things is very troublesome, inheritance rewrite are not appropriate.
Observer model
Defines a one-to-many relationship, let more observer object at the same time to monitor a theme, the theme of the state of an object changes will notify all observers object, allowing them to automatically update themselves, when an object changes to other objects, and it does not know how many objects need to be changed, You should consider using the observer model.
- Publish & Subscribe
- More than a pair of
// The theme saves the state, Class Subject {constructor() {this.state = 0 this.state = []} getState() {return this.state} setState(state) { this.state = state this.notifyAllObservers() } notifyAllObservers() { this.observers.forEach(observer => {obser.update ()})} attach(observer) {this.observers. Push (observer)}} // observer class observer { constructor(name, subject) { this.name = name this.subject = subject this.subject.attach(this) } update() { console.log(`${this.name} update, state: ${this.subject.getState()} ')}} let s = new subject () let o1 = new Observer('o1', s) let o2 = new Observer('02', s) s.setState(12)Copy the code
scenario
- DOM events
document.body.addEventListener('click', function() { console.log('hello world! '); }); document.body.click()Copy the code
- Vue responsive
advantages
- Supports simple broadcast communication that automatically notifies all objects that have subscribed
- The abstract coupling between the target object and the observer can be independently extended and reused
- Increased flexibility
- What the observer pattern does is decouple, making both sides of the coupling dependent on the abstract rather than the concrete. So that the changes in each side don’t affect the changes in the other side.
disadvantages
Overuse can weaken the relationship between objects and make programs difficult to track, maintain and understand
The state pattern
Allows an object to change its behavior when its internal state changes, and the object appears to modify its class
Class State {constructor(State) {this. State = State} handle(context) {console.log(' this is ${this.state} light`) context.setState(this) } } class Context { constructor() { this.state = null } getState() { return this.state } setState(state) { this.state = state } } // test let context = new Context() let weak = new State('weak') // Weak light weak. Handle (context) console.log(context.getState()) Handle (context) console.log(context.getState()) // Disable off.handle(context) console.log(context.getState())Copy the code
scenario
- An object’s behavior depends on its state, and it must change its behavior at runtime based on its state
- An operation contains a large number of branch statements, and these branch statements depend on the state of the object
advantages
- Define the relationship between state and behavior, encapsulated in a class, more intuitive and clear, easy to change
- States and actions are independent of each other
- Use objects instead of strings to record the current state, making state switching more obvious
disadvantages
- Many state classes are defined in the system
- Logic scattered
Iterator pattern
Provides a way to order elements in an aggregate object without exposing the internal representation of the object.
class Iterator { constructor(conatiner) { this.list = conatiner.list this.index = 0 } next() { if (this.hasNext()) { return this.list[this.index++] } return null } hasNext() { if (this.index >= this.list.length) { return false } return Class Container {constructor(list) {this.list = list} getIterator() {return new Iterator(this)} let container = new Container([1, 2, 3, 4, 5]) let iterator = container.getIterator() while(iterator.hasNext()) { console.log(iterator.next()) }Copy the code
Example scenario
- Array.prototype.forEach
- JQuery $. In the each ()
- ES6 Iterator
The characteristics of
- Access the content of an aggregate object without exposing its internal representation.
- Provides a unified interface for traversing different collection structures, enabling the same algorithm to operate on different collection structures
conclusion
The iterator pattern can be used if the internal structure of a collection is often variable and you do not want to expose the internal structure, but you want the client code to transparently access its elements
The bridge model
The Bridge pattern separates the abstract part from its implementation so that they can all change independently.
class Color { constructor(name){ this.name = name } } class Shape { constructor(name,color){ this.name = name this.color = color} draw(){console.log(' ${this.color.name} ${this.name} ')}} // test let red = new color ('red') let yellow = new Color('yellow') let circle = new Shape('circle', red) circle.draw() let triangle = new Shape('triangle', yellow) triangle.draw()Copy the code
advantages
- Helps manage the components independently and decouples abstraction from implementation
- Improve scalability
disadvantages
- A large number of classes will result in an increase in development costs and possibly a reduction in performance.
Portfolio model
- Group objects into a tree structure to represent a whole-part hierarchy.
- Through the polymorphic representation of objects, users can use single objects and combined objects coherently.
Class TrainOrder {create () {console.log(' create train ticket order ')}} class HotelOrder {create () {console.log(' create HotelOrder ')}} class TotalOrder { constructor () { this.orderList = [] } addOrder (order) { this.orderList.push(order) return this } create ForEach (item => {item.create()}) return this}} let train = new TrainOrder() let hotel = new HotelOrder() let total = new TotalOrder() total.addOrder(train).addOrder(hotel).create()Copy the code
scenario
- Represents an object-whole hierarchy
- The user is expected to ignore the difference between a composite object and a single object, and the user will use all objects (methods) in the composite structure uniformly
disadvantages
If too many objects are created through the composite pattern, they may become unaffordable for the system.
The prototype pattern
The prototype pattern refers to the type of object being created by pointing to prototype instances and creating new objects by copying those prototypes.
class Person {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class Student extends Person {
constructor(name) {
super(name)
}
sayHello() {
console.log(`Hello, My name is ${this.name}`)
}
}
let student = new Student("xiaoming")
student.sayHello()
Copy the code
The prototype pattern, which creates a shared prototype and copies it to create new classes for creating duplicate objects, improves performance.
The strategy pattern
Define a set of algorithms, encapsulate them one by one, and make them interchangeable
< HTML > <title> <meta Content ="text/ HTML; charset=utf-8" http-equiv="Content-Type"> </head> <body> <form id = "registerForm" method="post" Action = "http://xxxx.com/api/register" > userName: < input type = "text" name = "userName" > password: <input type="text" name="password"> <input type="text" name="phoneNumber"> <button type="submit" </button> </form> <script type="text/javascript" const strategies = { isNoEmpty: function (value, errorMsg) { if (value === '') { return errorMsg; } }, isNoSpace: function (value, errorMsg) { if (value.trim() === '') { return errorMsg; } }, minLength: function (value, length, errorMsg) { if (value.trim().length < length) { return errorMsg; } }, maxLength: function (value, length, errorMsg) { if (value.length > length) { return errorMsg; } }, isMobile: function (value, errorMsg) { if (! /^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|17[7]|18[0|1|2|3|5|6|7|8|9])\d{8}$/.test(value)) { return errorMsg; Constructor () {this.cache = []} add(dom, rules) {for(let I = 0, rule; rule = rules[i++];) { let strategyAry = rule.strategy.split(':') let errorMsg = rule.errorMsg this.cache.push(() => { let strategy = strategyAry.shift() strategyAry.unshift(dom.value) strategyAry.push(errorMsg) return strategies[strategy].apply(dom, strategyAry) }) } } start() { for(let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {let errorMsg = validatorFunc() if (errorMsg) {return errorMsg}}}} // call let registerForm = document.getElementById('registerForm') let validataFunc = function() { let validator = new Validator() Add (registerform. userName, [{strategy: 'isNoEmpty', errorMsg: 'userName cannot be empty'}, {strategy: 'isNoSpace', errorMsg: 'Whitespace is not allowed'}, {strategy: 'minLength:2', errorMsg: }]) validator.add(registerform. password, [{strategy: 'minLength:6', errorMsg: 'password length can not less than 6}]) the validator. Add (registerForm. PhoneNumber, [{strategy:' isMobile, errorMsg: }]) return validator.start()} registerform.onsubmit = function() {let errorMsg = validataFunc() if (errorMsg) { alert(errorMsg) return false } } </script> </body> </html>Copy the code
Example scenario
- If there are many classes in a system that are distinguished only by their ‘behavior’, then using the policy pattern can dynamically let an object choose one behavior among many behaviors.
- A system needs to dynamically choose one of several algorithms.
- Form validation
advantages
- Multiple conditional selection statements can be effectively avoided by using combination, delegate and polymorphism techniques
- Provides perfect support for the open-closed principle, encapsulating algorithms in a separate strategy, making them easy to switch, understand, and extend
- Using composition and delegation to give the Context the ability to perform the algorithm is also a lighter alternative to inheritance
disadvantages
- Many policy classes or policy objects are added to the program
- To use the strategy pattern, it is necessary to understand all strategies, and to understand the differences between strategies in order to choose a suitable strategy
The flyweight pattern
The reuse of a large number of fine – grained objects is effectively supported by sharing technology. The system only uses a small number of objects, and these objects are very similar, the state change is very small, can realize the object multiple reuse. Because the share mode requires that the objects that can be shared must be fine grained objects, it is also called lightweight mode, which is an object structure mode
ExamCarNum = 0 // ExamCar {constructor(carType) {examCarNum++ this. CarId = examCarNum this.carType = carType ? 'Manual' : /* examine(candidateId) {return new Promise((resolve) => { This. UsingState = true console.log(' candidateId} - ${this.carType} - ${this.carId}) setTimeout(() => {this.usingState = false console.log(' %c candidate - ${candidateId}) ${this.carType} - ${this.carId} 'color:#f40') resolve(), math.random () * 2000)}))}} / [], // candidatequeue: []. // candidateList (candidateList) {candidateList. ForEach (candidateId => this.registCandidate(candidateId)) }, // if (examCar) {// if (examCar) {// if (examCar) {// if (examCar) {// if (examCar) { ExamCar. Examine (candidateId) // Then (() => {const nextCandidateId = this._candidatequue. Length && this._candidatequue. Shift ()) nextCandidateId && this.registCandidate(nextCandidateId) }) } else this._candidateQueue.push(candidateId) }, */ initexamcar (manualExamCarNum) {for (let I = 1; i <= manualExamCarNum; I++) {this._pool.push(new ExamCar(true))}}, /* getManualExamCar() {return this._pool.find(car =>! Car. UsingState)}} ManualExamCarPool. InitManualExamCar (3) / / there are three driving test car ManualExamCarPool. RegistCandidates ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) // Ten candidates come to take the examCopy the code
Example scenario
- File upload requires the creation of multiple file instances
- The free element pattern should be considered if an application uses a large number of objects, and these large numbers of objects cause significant storage overhead
advantages
- Greatly reduce the creation of objects, reduce the system memory, improve efficiency.
disadvantages
- It increases the complexity of the system, the need to separate out the external state from the internal state, and the external state has inherent properties,
It should not change as the internal state changes, otherwise it will cause chaos in the system
Template method pattern
The template method pattern consists of two parts, the first part is the abstract parent class, the second part is the concrete implementation child class. The algorithm framework of a subclass is usually encapsulated in an abstract superclass, including implementing some common methods and encapsulating the execution order of all methods in the subclass. By inheriting this abstract class, subclasses also inherit the entire algorithm structure and can choose to override the methods of the parent class.
class Beverage { constructor({brewDrink, AddCondiment}) {this.brewdrink = brewDrink this.addcondiment = addCondiment} BoilWater () {console.log(' Water has boiled === shared ')} /* Fill the cup, /* Template method */ init() {this.boilwater () this.brewdrink () this.pourcup () {console.log(' pour into a cup === share ')} This.addcondiment ()}} /* coffee */ const coffee = new Beverage({/* brew coffee, overlay abstract method */ brewDrink: Function () {console.log(' make coffee ')}, /* addCondiment */ addCondiment: function() {console.log(' add milk and sugar ')}}) coffee.init()Copy the code
Example scenario
- Implement the invariant parts of an algorithm once and leave the mutable behavior to subclasses
- Common behaviors in subclasses should be extracted and aggregated into a common parent class to avoid code duplication
advantages
- Extract common code parts, easy to maintain
disadvantages
- Increased system complexity, mainly by increasing abstract classes and inter-class relationships
Chain of Responsibility model
To avoid coupling between the sender and receiver of the request by giving multiple objects the opportunity to process it, link the objects into a chain and pass the request along the chain until one object processes it
// Leave approval, Class Action {constructor(name) {this.name = name this.nextAction = null} setNextAction(Action) {constructor(name) {this.name = name this.nextAction = null} NextAction = Action} handle() {console.log(' ${this.name} approve ') if (this.nextAction! Nextaction.handle ()}} let a2 = new Action(" manager ") let a3 = new Action(" director ") a1.setNextAction(a2) a2.setNextAction(a3) a1.handle()Copy the code
Example scenario
- Events bubble in JS
- The scope chain
- Prototype chain
advantages
- Reduce coupling. It decouples the sender and receiver of the request.
- Simplifies the object. The object does not need to know the structure of the chain
- Enhanced flexibility in assigning responsibilities to objects. Responsibilities can be dynamically added or removed by changing members in the chain or reordering them
- It is convenient to add new request handling classes.
disadvantages
- There is no guarantee that a request will be processed by the nodes in the chain, in which case you can add a guaranteed receiver node at the end of the chain to handle requests that are leaving the chain.
- Make a lot of nodes in the program object, may request again in the process, most of the nodes did not play a substantial role. Their role is simply to get the request passed on, in terms of performance, to avoid the performance cost that comes with a long chain of responsibilities.
Command mode
Encapsulating a request as an object allows you to parameterize clients with different requests, queue requests or log requests, and provide command undo and recovery capabilities.
// Class Receiver {execute() {console.log(' Receiver performs the request ')}} // Class Command {constructor(Receiver) { This.receiver = receiver} execute () {console.log(' command '); This.receiver. Execute () {constructor(command) {this.receiver = command} invoke() {this.receiver. Console. log(' start ') this.man.execute ()}} // warehouse const warehouse = new Receiver(); // const order = new Command(warehouse); // const client = new Invoker(order); client.invoke()Copy the code
advantages
- Encapsulate commands to make them easy to expand and modify
- The command sender and receiver are decoupled so that the command sender can execute the command without knowing the specific execution process
disadvantages
- Using command mode can cause some systems to have too many specific command classes.
Memo mode
Capture the internal state of an object and store the state outside of the object without breaking encapsulation. This allows you to restore the object to its saved state later.
Constructor (content){this.content = content} getContent(){return this.content}} // Memento{constructor(content){this.content = content CareTaker { constructor(){ this.list = [] } add(memento){ this.list.push(memento) } get(index){ return this.list[index] 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()) //222Copy the code
Example scenario
- Paging controls
- Revocation of the component
advantages
- It provides a mechanism for the user to restore the state, enabling the user to easily return to a historical state
disadvantages
- Consume resources. If a class has too many member variables, it will take up a lot of resources, and each save will consume a certain amount of memory.
The mediator pattern
Decouple objects from each other. By adding a mediator object, all related objects communicate through the mediator object rather than referring to each other, so when an object changes, the mediator object only needs to be notified. The mediator loosens the coupling between objects and can change their interactions independently. The mediator pattern transforms the netted many-to-many relationship into a relatively simple one-to-many relationship (similar to the observer pattern, but unidirectional and centrally managed by the mediator).
class A {
constructor() {
this.number = 0
}
setNumber(num, m) {
this.number = num
if (m) {
m.setB()
}
}
}
class B {
constructor() {
this.number = 0
}
setNumber(num, m) {
this.number = num
if (m) {
m.setA()
}
}
}
class Mediator {
constructor(a, b) {
this.a = a
this.b = b
}
setA() {
let number = this.b.number
this.a.setNumber(number * 10)
}
setB() {
let number = this.a.number
this.b.setNumber(number / 10)
}
}
let a = new A()
let b = new B()
let m = new Mediator(a, b)
a.setNumber(10, m)
console.log(a.number, b.number)
b.setNumber(10, m)
console.log(a.number, b.number)
Copy the code
Example scenario
- There are complex reference relationships among objects in the system, which leads to the disorganized structure of the dependency relationship between them and the difficulty of reuse the object
- You want an intermediate class to encapsulate behavior in multiple classes without generating too many subclasses.
advantages
- Loose coupling between objects and the ability to change their interactions independently
- The one-to-many relationship between intermediaries and objects replaces the net-like many-to-many relationship between objects
- If the complexity of the coupling between objects makes maintenance difficult, and the coupling increases rapidly from project to project, an intermediary needs to refactor the code
disadvantages
- A new mediator object is added to the system, and because of the complexity of the interaction between the objects, the complexity of the mediator object is transferred to the mediator object, making the mediator object often huge. The mediator object itself is often a difficult object to maintain.
Interpreter mode
Given a language, define a representation of its grammar and define an interpreter that uses that representation to interpret sentences in the language.
This example comes from Xintan blog
class Context { constructor() { this._list = []; This. _sum = 0; } get sum() {return this._sum; } set sum(newValue) { this._sum = newValue; } add(expression) { this._list.push(expression); } get list() { return [...this._list]; } } class PlusExpression { interpret(context) { if (! (context instanceof Context)) { throw new Error("TypeError"); } context.sum = ++context.sum; } } class MinusExpression { interpret(context) { if (! (context instanceof Context)) { throw new Error("TypeError"); } context.sum = --context.sum; **/ const context = new context (); / / in turn add: addition | | addition subtraction expression context. The add (new PlusExpression ()); context.add(new PlusExpression()); context.add(new MinusExpression()); / / in sequence: addition | | addition subtraction expression context. The list. The forEach (expression = > expression. Interpret (context)); console.log(context.sum);Copy the code
advantages
- Easy to change and extend grammars.
- Because classes are used in the interpreter schema to represent the grammar rules of the language, the grammar can be changed or extended through mechanisms such as inheritance
disadvantages
- The execution is inefficient, and the interpreter mode uses a lot of loops and recursive calls, making it slow to interpret more complex sentences
- Complex grammars are difficult to maintain
Visitor pattern
Represents an operation that operates on elements in an object structure. It allows you to define new operations on elements without changing their classes.
/ / visitors to a class Visitor {constructor () {} visitConcreteElement (ConcreteElement) {ConcreteElement. Operation ()}} / / element class class ConcreteElement{ constructor() { } operation() { console.log("ConcreteElement.operation invoked"); } accept(visitor) { visitor.visitConcreteElement(this) } } // client let visitor = new Visitor() let element = new ConcreteElement() element.accept(visitor)Copy the code
Example scenario
- The class corresponding to an object in an object structure rarely changes, but it is often necessary to define new operations on this object structure
- You need to do many different and unrelated operations on objects in an object structure, and you want to avoid having those operations “contaminate” the classes of those objects, and you don’t want to modify those classes when you add new operations.
advantages
- Consistent with the principle of single responsibility
- Excellent scalability
- flexibility
disadvantages
- Specific elements disclose details to visitors, violating the Demeter principle
- Violates the dependency inversion principle by relying on concrete classes instead of abstractions.
- Specific element changes are difficult
Brother, if it helps you, please give me a thumbs up as well as support
The resources
- Double Yue -Javascript design pattern system explanation and application
- JavaScript design patterns and development practices
- Uncle Tom’s design pattern
- www.yuque.com/wubinhp/uxi…