1. Introduction to design patterns
What is a design pattern
- Design patterns are an idea to solve problems and have nothing to do with language. In the engineering of object-oriented software design, a simple and elegant solution to specific problems. To put it simply, the design pattern is a solution that conforms to a certain problem in a certain scenario. Through the design pattern, the reusability, scalability and maintainability of the code can be increased, and ultimately, making our code highly cohesive and low coupling.
Five major design principles of design patterns
- Single Responsibilities: A program only needs to do one thing well. If the function is too complex, separate it to ensure the independence of each part
- The principle of openness and closure: openness to expansion and closure to modification. When increasing demand, expand new code instead of modifying the source code. This is the ultimate goal of software design.
- Rich's substitution principle: subclasses can cover the parent class, and subclasses can also appear where the parent class can appear.
- The principle of interface independence: keep the interface single and independent and avoid "fat interface". This is currently used in TS.
- Dependency leads to the principle: interface-oriented programming, relying on abstraction rather than concrete. The user only focuses on the interface and does not need to pay attention to the implementation of specific classes. Commonly known as "duck type"
Three categories of design patterns
- Creative: Factory mode, Abstract factory mode, Builder mode, Singleton mode, Prototype mode
- Structural type: adapter mode, decorator mode, proxy mode, appearance mode, bridge mode, combination mode, enjoyment mode
- Behavior: policy mode, template method mode, publish subscription mode, iterator mode, responsibility chain mode, command mode, memo mode, status mode, visitor mode, mediator mode, interpreter mode.
2. Design mode
1. Factory model
- Factory pattern is a common design pattern used to create objects. Instead of exposing the specific logic of creating objects, it can be called a factory. Factory mode is also called static factory mode, which is determined by a factory object to create an instance of a certain class.
advantage
- When the caller creates an object, it only needs to know its name.
- It has high scalability. If you want to add a new product, just expand a factory class directly.
- The specific implementation of hidden products is only concerned with the product interface.
shortcoming
- Each time a product is added, a specific class needs to be added, which invisibly increases the pressure of system memory and system complexity, and also increases the dependence of specific classes.
example
- A garment factory can produce different types of clothes, and we simulate output through a factory method class.
class DownJacket { production(){ ('Produce down jackets') } } class Underwear{ production(){ ('Produce underwear') } } class TShirt{ production(){ ('Produce T-shirts') } } // Factoryclass clothingFactory { constructor(){ = DownJacket = Underwear this.t_shirt = TShirt } getFactory(clothingType){ const _production = new this[clothingType] return _production.production() } } const clothing = new clothingFactory() ('t_shirt')// Production of T-shirts
2. Abstract factory pattern
- The abstract factory model is to make the business suitable for the creation of a product class cluster through class abstraction, and is not responsible for instances of a certain class of products. Abstract factories can be regarded as an upgraded version of ordinary factories. Ordinary factories mainly use production examples, while abstract factories are aimed at producing factories.
advantage
- When multiple objects in a product family are designed to work together, it ensures that clients always use only objects in the same product family.
shortcoming
- It is very difficult to expand the product family. To add a series of products, you need to add code to the abstract Creator and code to the specific ones.
example
- Also based on the above example, an abstract class is simulated and the implementation of inherited subclasses is constrained. Finally, the specified class cluster is returned through the factory function.
/* Abstract class Abstract is a reserved word in js. The implementation of abstract classes can only be verified. Prevent abstract classes from being instanced directly, and if the subclass does not override the specified method, an error will be thrown */ class ProductionFlow { constructor(){ if( === ProductionFlow){ throw new Error('Abstract classes cannot be instanced') } } production(){ throw new Error('production is to be rewritten') } materials(){ throw new Error('materials to be rewritten') } } class DownJacket extends ProductionFlow{ production(){ (`Material:${()},Production of down jackets`) } materials(){ return 'Duck Feather' } } class Underwear extends ProductionFlow{ production(){ (`Material:${()},Production of underwear`) } materials(){ return 'Merrored cotton' } } class TShirt extends ProductionFlow{ production(){ (`Material:${()},ProductiontShirt`) } materials(){ return 'Pure Cotton' } } function getAbstractProductionFactory(clothingType){ const clothingObj = { downJacket:DownJacket, underwear:Underwear, t_shirt:TShirt, } if(clothingObj[clothingType]){ return clothingObj[clothingType] } throw new Error(`工厂暂时不支持Production这个${clothingType}Types of clothing`) } const downJacketClass = getAbstractProductionFactory('downJacket') const underwearClass = getAbstractProductionFactory('underwear') const downJacket = new downJacketClass() const underwear = new underwearClass() () // Material: duck feather, down jacket production() // Material: mercerized cotton, production of underwear
3. Builder Mode
- Builder mode is a relatively complex and low-frequency creation design model. Builder mode returns not a simple product for the client, but a complex product composed of multiple components. It is mainly used to separate the construction of a complex object from its representation, so that the same construction process can create different representations.
advantage
- Builder independent and easy to expand
- Convenient to control details and risks
shortcoming
- Products must have common points and limit their scope
- When there are complex internal changes, there will be many construction classes
example
Let’s continue to use the production process of the garment factory as an example.
// Abstract classclass Clothing { constructor() { = '' } } class Underwear extends Clothing { constructor() { super() = 'underwear' = 10 } } class TShirt extends Clothing { constructor() { super() = 't_shirt' = 50 } } class DownCoat extends Clothing { constructor() { super() = 'DownCoat' = 500 } } // productclass Purchase { constructor() { = [] } addClothing(clothing) { (clothing) } countPrice() { return ((prev, cur)=> + prev,0) } } // Factory Directorclass FactoryManager { createUnderwear() { throw new Error(`Subclasses must be rewrite createUnderwear`) } createTShirt() { throw new Error(`Subclasses must be rewrite createTShirt`) } createDownCoat() { throw new Error(`Subclasses must be rewrite DownCoat`) } } // Workerclass Worker extends FactoryManager { constructor() { super() = new Purchase() } createUnderwear(num) { for (let i = 0; i < num; i++) { (new Underwear()) } } createTShirt(num) { for (let i = 0; i < num; i++) { (new TShirt()) } } createDownCoat(num) { for (let i = 0; i < num; i++) { (new DownCoat()) } } } // Saleclass Salesman { constructor() { = null } setWorker(worker) { = worker } reserve(clothing) { ((item) => { if ( === 'underwear') { () } else if ( === 't_shirt') { () } else if ( === 'DownCoat') { () } else { try { throw new Error('The company does not produce or does not exist for the time being') } catch (error) { (error) } } }); const purchase = return () } } const salesman = new Salesman() const worker = new Worker() (worker) const order = [ { type: 'underwear', num: 10 }, { type: 't_shirt', num: 4 }, { type: 'DownCoat', num: 1 } ] (`The amount required for this order:${(order)}`)
4. Singleton mode
- The idea of singleton pattern is: ensure that a class can only be instanced once. Each time it is retrieved, if the class has already created an instance, it will be returned directly, otherwise an instance will be created to save and return.
- The core of the singleton pattern is to create a unique object, and creating a unique object in javascript is too simple. Creating a class to get an object is a bit unnecessary. like
const obj = {}
,obj
It is a unique object that can be accessed anywhere under the declaration of global scope, which meets the conditions of singleton pattern.
advantage
- There is only one instance in memory, which reduces memory overhead.
- Avoid multiple resource usage.
shortcoming
- Violating a single responsibility, a class should only care about internal logic, not external implementations
example
- The login pop-ups we often see are either displayed or hidden, and it is impossible for two pop-ups to appear at the same time. Below we simulate pop-ups through a class.
class LoginFrame { static instance = null constructor(state){ = state } show(){ if( === 'show'){ ('Login box is displayed') return } = 'show' ('Login box display successfully') } hide(){ if( === 'hide'){ ('Login box is hidden') return } = 'hide' ('Login box is hidden successfully') } // Get whether there is an instance on the static property instance through a static method. If no one is created and returned, instead, directly return the existing instance static getInstance(state){ if(!){ = new LoginFrame(state) } return } } const p1 = ('show') const p2 = ('hide') (p1 === p2) // true
5. Adapter mode
- The purpose of the adapter mode is to solve the problem of interface incompatibility between objects. Through the adapter mode, two originally incompatible objects can be enabled to work normally when invoked without changing the source code.
advantage
- Make any two unrelated classes run efficiently at the same time, and improve reusability, transparency, and flexibility
shortcoming
- Too much use of the adapter mode will make the system messy and difficult to control as a whole. It is recommended to use an adapter if it cannot be refactored.
example
- Take a realistic example as an example. Jack only knows English, and Xiao Ming only knows Chinese. They have obstacles in communication. Xiao Hong can also speak Chinese and English. Through Xiao Hong, Jack's English is translated into Chinese, allowing Xiao Ming and Jack to communicate with each other without barriers. Here Xiao Hong plays the role of an adapter.
class Jack { english() { return 'I speak English' } } class Xiaoming { chinese() { return 'I only know Chinese' } } // Adapterclass XiaoHong { constructor(person) { = person } chinese() { return `${()} translate: "I can speak English"` } } class Communication { speak(language) { (()) } } const xiaoming = new Xiaoming() const xiaoHong = new XiaoHong(new Jack()) const communication = new Communication() (xiaoming) (xiaoHong)
6. Decorator mode
- Decorator mode can add responsibilities without changing the source code itself. It is lighter than inheriting the decorator. In layman's terms, we put films on our beloved mobile phones, with mobile phone cases and stickers, which are the decorations for mobile phones.
advantage
- The decorative class and the decorated class can develop independently of each other and will not be coupled with each other. The decorator pattern is an alternative pattern inherited, which can dynamically extend the functions of an implementing class.
shortcoming
- Multi-layer decoration will increase complexity
example
- In the game of writing Aircraft Wars, the attack method of aircraft objects is only ordinary bullet attack. How to use other attack methods such as laser weapons and missile weapons without changing the original code?
class Aircraft { ordinary(){ ('Shot of ordinary bullets') } } class AircraftDecorator { constructor(aircraft){ = aircraft } laser(){ ('Emitting laser') } guidedMissile(){ ('Start missile') } ordinary(){ () } } const aircraft = new Aircraft() const aircraftDecorator = new AircraftDecorator(aircraft) () // Launch ordinary bullets() // emitting laser light() // Launch missiles// You can see that it has been decoratively extended without changing the source code
7. Agent Mode
- The key to the proxy mode is that when the customer is inconvenient to access an object directly or does not meet the needs, a substitute object is provided to control access to this object. The customer actually accesses the substitute object. After the substitute object makes some processing on the request, it forwards the request to the ontology object.
- The proxy and ontology interface need consistency. The proxy and ontology can be said to be a duck-type relationship. It doesn’t care how it is implemented, as long as the exposed methods are consistent.
advantage
- Clear responsibilities, high scalability, and intelligent
shortcoming
- When adding proxy between objects and objects, it may affect the speed of processing.
- Implementing a proxy requires additional work, and some proxy can be very complicated.
example
- We all know that the leader has the highest authority in the company. Suppose the company has 100 employees, if everyone goes to the leader to handle affairs, the leader will definitely collapse. Therefore, the leader recruits a secretary to help him collect and organize affairs. The secretary will hand over the business that needs to be handled to the boss at the right time to handle at one time. Here, the secretary is an agent role of the leader.
// staffclass Staff { constructor(affairType){ = affairType } applyFor(target){ () } } // secretaryclass Secretary { constructor(){ = new Leader() } receiveApplyFor(affair){ (affair) } } //leadclass Leader { receiveApplyFor(affair){ (`approve:${affair}`) } } const staff = new Staff('Promotion and salary increase') (new Secretary()) // Approval: promotion and salary increase
8. Appearance mode
- The essence of appearance mode is to encapsulate interaction, hide the complexity of the system, and provide an accessible interface. A high-level interface that integrates a group of subsystems into a group to provide a consistent appearance and reduce direct interaction between the outside world and multiple subsystems, making it easier to use the subsystem.
advantage
- Reduce system interdependence, as well as security and flexibility
shortcoming
- Violating the principle of openness and closure, it will be very troublesome to change when there are changes, and even inheritance and reconstruction is not feasible.
example
- Appearance mode is often used to handle compatible handling of some interfaces for advanced and lower versions of the browser.
function addEvent(el,type,fn){ if(){// Advanced browser add event DOM API (type,fn,false) }else if(){// Add event API for lower version browsers (`on${type}`,fn) }else {//other el[type] = fn } }
- In another scenario, when a certain parameter in a certain function can be passed or not, the transfer parameters can be made more flexible through function overloading.
function bindEvent(el,type,selector,fn){ if(!fn){ fn = selector } // Other codes (el,type,fn) } bindEvent(,'click','#root',()=>{}) bindEvent(,'click',()=>{})
9. Publish subscription model
- Publish subscription, also known as observer pattern, defines a 1-to-N dependency between objects, and when one of the objects changes, all objects that depend on it will be notified.
- The publish subscription model often appears in our work scenarios, such as: when you bind an event to the DOM, you have already used the publish subscription model. By subscribing to the click event on the DOM, a message will be posted to the subscriber when it is clicked.
advantage
- The observer and the observer are abstractly coupled. And a trigger mechanism was established.
shortcoming
- When there are many subscribers, notifying all subscribers at the same time may cause performance problems.
- If a circular reference is executed between the subscriber and the subscription target, it will cause a crash.
- The publish subscription model has no way to provide subscribers with how the goal of subscribers has changed, just knowing that it has changed.
example
- It is a metaphor for the Winter Olympics some time ago. You can book in advance when the event has not started. When the project is about to start, the APP will send us notifications to us in advance that the project will be started, and there will be no notification if there is no time. In addition, when the project has not started, you can cancel the subscription to avoid receiving notifications. Based on this requirement, let's write an example
class Subject { constructor(){ = {} = '' } add(observer){ const key = if (![key]) { [key] = [] } [key].push(observer) } remove(observer){ const _observers = [] (_observers,11) if(_observers.length){ _observers.forEach((item,index)=&gt;{ if(item === observer){ _observers.splice(index,1) } }) } } setObserver(subject){ = subject () } notifyAllObservers(){ [].forEach((item,index)=&gt;{ () }) } } class Observer { constructor(project,name) { = project = name } update() { (`Dear:${} The project you booked:【${}】 It's about to start`) } } const subject = new Subject() const xiaoming = new Observer('ski','xiaoming') const A = new Observer('Big Jump','A') const B = new Observer('Big Jump','B') const C = new Observer('Big Jump','C') (xiaoming) (A) (B) (C) (B) // Unsubscribe('Big Jump') /** Execution results * Dear: A The project you booked: [Big Jump] Starts soon * Dear: C The project you booked: [Big Jump] Starts soon */
10. Iterator mode
- Iterator pattern refers to providing a method to access each element in an aggregate object in sequence without exposing the inside of the object.
advantage
- It supports traversing an aggregate object in different ways.
- Iterators simplify the aggregation class. There can be multiple traversals on the same aggregation.
- In the iterator mode, it is convenient to add new aggregation classes and iterator classes, without modifying the original code.
shortcoming
- Since the iterator pattern separates the responsibilities of storing data and traversing data, adding new aggregation classes requires adding new iterator classes accordingly, and the number of classes increases in pairs, which to a certain extent increases the complexity of the system.
example
Iterators are divided into internal iterators and external iterators, and they have their own applicable scenarios.
- Internal iterator
// The internal iterator means that the iteration rules have been defined internally, it completely accepts the entire iteration process, and the external only requires one initial call. = function(fn){ for(let i = 0;i<;i++){ fn(this[i],i,this) } } = function(fn){ for(let i = 0;i<;i++){ fn(this[i],i,this) } } [1,2,3,4].MyEach((item,index)=>{ (item,index) })
- External iterator
// The external iterator must display the iteration next element. It increases the complexity of the call, but also increases the flexibility of the iterator, allowing manual control of the iteration process.class Iterator{ constructor(arr){ = 0 = = arr } next(){ return () } isDone(){ return >= } getCurrItem(){ return { done:(), value:[++] } } } let iterator =new Iterator([1,2,3]) while(!(item=()).done) { (item) } () /* Are the following data formats a little familiar {done: false, value: 1} {done: false, value: 2} {done: false, value: 3} {done: true, value: undefined} */
11. Status Mode
- It allows an object to change its behavior when its internal state changes. The object seems to have modified its class. In a simple way, it will record a set of states, each state corresponds to an implementation, and the implementation will be run according to the state when it is implemented.
advantage
- Put all behaviors related to a certain state into a class, and new states can be easily added, and the behavior of the object is changed only by changing the object state.
- Allows state transition logic to be integrated with state objects instead of a large conditional statement block.
- Multiple environment objects can be allowed to share a state object, thereby reducing the number of objects in the system.
shortcoming
- The use of state mode will inevitably increase the number of system classes and objects.
- The structure and implementation of the state mode are relatively complex, and if used improperly, it will lead to confusion in the program structure and code.
- The state mode does not support the "open and shutdown principle". To add a new state class to the state mode of switching state, you need to modify the source code responsible for state transition, otherwise you cannot switch to the new state. Moreover, modifying the behavior of a certain state class also requires modifying the source code of the corresponding class.
example
- Raven's Q in lol has three attacks. The same key press, and the attack behavior is different under different states. Usually, we can also achieve it through if...else, but this is obviously not conducive to expansion and violates the principle of openness and closure. Next, use code to describe this scenario.
class State { constructor(attack){ = attack } handle(context){ () (this) } } class Context { constructor(){ = null } getState(){ return } setState(state){ = state } } const q1 = new State('q1 1st strike'), q2 = new State('q2 2nd strike'), q3 = new State('q3 3rd strike'), context = new Context() (context)//q1 1st strike(context)//q2 2nd strike(context)//q3 3rd strike
12. Policy Mode
- The strategy pattern refers to defining a series of algorithms and encapsulating them one by one, with the purpose of separating the use of algorithms and the implementation of algorithms. At the same time, it can also be used to encapsulate a series of rules, such as common form verification rules. As long as the targets pointed to by these rules are consistent and can be used instead, they can be encapsulated with policy patterns.
advantage
- The algorithm can be switched freely, avoiding the use of multi-layer conditional judgment and increasing scalability
shortcoming
- The number of policy classes increases, and all policy classes need to be exposed to the outside world.
example
- When I first entered this industry, I often wrote the form verification method endlessly. I realized that this method was unreliable, so I put the verification rules in an object, controlled them in the function, separated the rules from the implementation, and only needed to modify the configuration in the encapsulated rules at a time. This method is used in the subsequent scenarios, which solves the problem of frequent use of if...else. When you first come into contact with the inverted strategy mode, you will know that this writing method is also considered a strategy mode.
const rules = { cover_img: { must: false, msg: 'Please upload the cover image', val: '' }, name: { must: true, msg: 'The name cannot be empty', val: '' }, sex: { must: true, msg: 'Please fill in the gender', val: '' }, birthday: { must: false, msg: 'Please select a birthday', val: '' }, } function verify(){ for(const key in rules){ if(rules[key].must&amp;&amp;!rules[key].val){ (rules[key].msg) } } } verify() // The name cannot be empty// Please fill in the gender
- The above example is written in JS. In JavaScript's language where functions are used as first-class citizens, the policy pattern is invisible. It has been integrated into the JavaScript language, so the policy pattern in JavaScript will appear simple and direct. However, we still need to understand the traditional strategy model. Let’s take a look at the examples of the traditional strategy model.
//html----------------- <form action="http:// /register" method="post"> Please enter your username:<input type="text" name="userName" /> Please enter your password:<input type="text" name="password" /> Please enter your mobile phone number:<input type="text" name="phoneNumber" /> <button>submit</button> </form> // js------------------ class Strategies { constructor() { = {} } add(key, rule) { [key] = rule return this } } class Validator { constructor(strategies) { = [] // Save the inspection rules = strategies } add(dom, rules) { ((rule) => { const strategyAry = (':') (() => { const strategy = () () () ([strategy]) return [strategy].apply(dom, strategyAry) }) }); } start() { for (let i = 0,validatorFunc; validatorFunc =[i++]; ) { const msg = validatorFunc() if (msg) { return msg } } } } const registerForm = ('registerForm') // Get formDom nodeconst strategies = new Strategies() ('isNonEmpty', function(value, errorMsg) { if (!value) { return errorMsg } }).add('minLength', function(value, length, errorMsg) { if ( < length) { return errorMsg } }).add('isMobile', function(value, errorMsg) { if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg } }) function validataFunc() { const validator = new Validator() // Multiple verification rules (, [ { strategy: 'isNonEmpty', errorMsg: 'Username cannot be empty' }, { strategy: 'minLength:10', errorMsg: 'The username must not be less than 10 digits' } ]) (, [{ strategy: 'minLength:6', errorMsg: 'The password length cannot be less than 6 digits' }]) (, [{ strategy: 'isMobile', errorMsg: 'The mobile phone number format is incorrect' }]) const errorMsg = () return errorMsg // Return an error message.} = function () { const errorMsg = validataFunc() if (errorMsg) { // If there is an error message, the error message will be displayed and the onsubmit default event will be blocked (errorMsg) return false } }
13. Command mode
- Commands in command mode refer to an instruction that performs certain things.
- The most common application scenarios of the command mode are: Sometimes you need to send a request to certain objects, but you don’t know who the request is, nor what the requested operation is. At this time, the program can be designed in a loosely coupled manner so that the request sender and the request recipient eliminate the coupling relationship between each other.
advantage
- Reduces the coupling degree of code and is easy to expand. New commands can be added easily.
shortcoming
- Excessive use of command mode can lead to excessive specific commands in the code.
example
- Suppose you develop a page in a project, where a programmer is responsible for drawing static pages, including certain buttons, and another programmer is responsible for developing the specific behavior of these buttons. The programmer responsible for static pages does not know what will happen to these buttons in the future. Without knowing what the specific behavior is and what, through the help of the command mode, untie the coupling between the buttons and the specific behavior objects responsible for the specific behavior.
// html------------------- <button >Click the button 1</button> <button >Click the button 2</button> <button >Click the button 3</button> // js--------------------- const button1 = ('button1'), button2 = ('button2'), button3 = ('button3'); const MenBar = { refresh:function(){ ('Refresh menu directory') } } const SubMenu = { add:function(){ ('Add submenu') }, del:function(){ ('Delete submenu') } } function setCommand(el,command){ = function(){ () } } class MenuBarCommand{ constructor(receiver,key){ = receiver = key } execute(){ []() } } setCommand(button1,new MenuBarCommand(MenBar,'refresh')) setCommand(button2,new MenuBarCommand(SubMenu,'add')) setCommand(button3,new MenuBarCommand(SubMenu,'del'))
14. Combination mode
- The combination pattern is a larger object constructed from some small sub-objects, and these small sub-objects themselves may also be composed of multiple grand objects.
- Combination mode combines objects into a tree structure to represent a "part-total" hierarchy. In addition to representing tree structures, another advantage of combining patterns is that they make users consistent in their use of individual objects and combined objects through the polymorphism of objects.
advantage
- High-level module calls are simple, and nodes can be added freely
shortcoming
- Its leaf object and child object declaration are both implementation classes, not interfaces, which violates the principle of dependency inversion.
example
- Taking our most common relationship between folders and files, it is very suitable to be described in combination mode. Folders can include subfolders and files, and files cannot include any files. This relationship will eventually form a tree. The following is to add files, scan the files in the file, and delete the files.
// Folder classclass Folder { constructor(name) { = name = null; = [] } // Add files add(file) { = this (file) return this } // Scan the file scan() { (`Start scanning folders:${}`) (file =&gt; { () }); } // Delete the specified file remove() { if (!) { return } for (let files = , i = - 1; i &gt;= 0; i--) { const file = files[i] if (file === this) { (i, 1) break } } } } // File classclass File { constructor(name) { = name = null } add() { throw new Error('No file can be added below the file') } scan() { (`Start scanning files:${}`) } remove() { if (!) { return } for (let files = , i = - 1; i &gt;= 0; i++) { const file = files[i] if (file === this) { (i, 1) } } } } const book = new Folder('E-book') const js = new Folder('js') const node = new Folder('node') const vue = new Folder('vue') const js_file1 = new File('Javascript Advanced Programming') const js_file2 = new File('javascript ninja cheats') const node_file1 = new File('nodejs easy to understand') const vue_file1 = new File('Vue') const designMode = new File('Javascript design pattern practical combat') (js_file1).add(js_file2) (node_file1) (vue_file1) (js).add(node).add(vue).add(designMode) () ()
15. Module method mode
- The module method pattern is an inheritance-based design pattern. There is no real inheritance in JavaScript. All inheritance comes from inheritance on the prototype. With the arrival of ES6 class, the "concept" of inheritance is implemented, allowing us to inherit in a very convenient and concise way, but it is essentially prototype inheritance.
- The template method pattern consists of two parts, the first part is the abstract parent class, and the second part is the concrete implementation subclass. The abstract parent class mainly encapsulates the algorithm framework of subclasses and implements some common methods and other methods execution order. The subclass inherits the parent class's algorithm framework and rewrites it.
advantage
- Provide public code for easy maintenance. The behavior is controlled by the parent class and implemented by the subclass.
shortcoming
- Each of its specific implementations requires inherited subclasses to implement, which undoubtedly leads to an increase in the number of classes, making the system huge.
example
- Take the example of coffee and tea. To make coffee and tea, boiling water requires boiling water. Boiling water is a public method. How to brew it later, what to pour into the cup, and what ingredients to add, they may be different. Based on the above characteristics, let us start our example.
// Abstract parent classclass Beverage { boilWater(){ ('Bring the water') } brew(){ throw new Error('The brew method must be overridden') } pourInCup(){ throw new Error('The word class must override the pourInCup method') } addCondiments(){ throw new Error('The character class must override the addCondiments method') } init(){ () () () () } } // Coffeeclass Coffee extends Beverage { brew(){ ('Burn coffee with boiling water') } pourInCup(){ ('Populate coffee into a cup') } addCondiments(){ ('Add sugar and milk') } } // Teaclass Tea extends Beverage { brew(){ ('Brew tea leaves with boiling water') } pourInCup(){ ('Populate the tea into the cup') } addCondiments(){ ('Add lemon') } } const coffee = new Coffee() () const tea = new Tea() ()
16. Enjoy the Yuan mode
- The Xiangyuan mode is a model for performance optimization, with the core of which is to use sharing technology to effectively support a large number of fine-grained objects. If a large number of similar objects are created in the system, it will cause excessive memory consumption. Reusing similar objects through the enjoy mode will reduce memory consumption and achieve a performance optimization solution.
- The key to the Enjoy mode is how to distinguish between internal and external states
- Internal state: It can be shared by objects and is usually not changed.
- External state: Depend on the specific scene, change according to the specific scene, and cannot be shared, is called an external state
advantage
- Reduces the creation of large batches of objects and reduces the system memory.
shortcoming
- The complexity of the system is improved, and the external state and internal state need to be separated. Moreover, the external state has an inherent nature and should not change with the changes in the internal state, otherwise it will cause system chaos.
example
let id = 0 // Define internal stateclass Upload { constructor(uploadType) { = uploadType } // When clicking to delete, it will be deleted directly if it is less than 3000, and if it is greater than 3000, it will be deleted through the confirm prompt pop-up window. delFile(id) { (id,this) if( &lt; 3000){ return () } if((`Are you sure you want to delete the file??${}`)){ return () } } } // External statusclass uploadManager { static uploadDatabase = {} static add(id, uploadType, fileName, fileSize) { const filWeightObj = (uploadType) const dom = (fileName, fileSize, () =&gt; { (id) }) [id] = { fileName, fileSize, dom } } // Create DOM and bind to delete events for button. static createDom(fileName, fileSize, fn) { const dom = ('div') = ` &lt;span&gt;File name:${fileName},File size:${fileSize}&lt;/span&gt; &lt;button class="delFile"&gt;delete&lt;/button&gt; ` ('.delFile').onclick = fn (dom) return dom } static setExternalState(id, flyWeightObj) { const uploadData = [id] for (const key in uploadData) { if ((uploadData, key)) { flyWeightObj[key] = uploadData[key] } } } } // Define a factory to create upload object. If its internal state instance object exists, it will be directly returned, otherwise it will be created and saved and returned.class UploadFactory { static createFlyWeightObjs = {} static create(uploadType) { if ([uploadType]) { return [uploadType] } return [uploadType] = new Upload(uploadType) } } // Start loadingconst startUpload = (uploadType, files)=&gt;{ for (let i = 0, file; file = files[i++];) { (++id, uploadType, , ) } } startUpload('plugin', [ {fileName: '',fileSize: 1000}, {fileName: '',fileSize: 3000}, {fileName: '',fileSize: 5000} ]); startUpload('flash', [ {fileName: '',fileSize: 1000}, {fileName: '',fileSize: 3000}, {fileName: '',fileSize: 5000} ]);
17. Responsibility Chain Model
- The definition of the chain of responsibilities is that using multiple objects has the opportunity to process the request, thereby avoiding the coupling relationship between the sender and the receiver of the request, chaining these objects into a chain, and passing the request along the chain, knowing that an object has processed it.
advantage
- Reduces the coupling degree, which decouples the sender and receiver of the request.
- The object is simplified so that the object does not need to know the structure of the chain.
- Enhance the flexibility to assign responsibilities to the target. By changing the members in the chain or mobilizing their order, it allows dynamic addition or deletion of responsibilities.
shortcoming
- There is no guarantee that every request will be received.
- System performance will be affected to a certain extent, and it is not convenient when debugging code, which may cause loop calls.
- It may not be easy to observe the characteristics of the runtime, which is to prevent the problem from being eliminated.
example
- Suppose we are responsible for an e-commerce website that sells mobile phones. After paying two rounds of deposits of 500 yuan and 200 yuan, we will receive coupons of 100 yuan and 50 yuan respectively. Those who do not pay the deposit will be considered ordinary purchases, no coupons, and there is no guarantee that they can be purchased under limited inventory.
class Order500 { constructor(){ = 1 } handle(orderType, pay, stock){ if(orderType === &&pay){ ('Appointment of 500 yuan deposit, get a coupon of 100 yuan') }else { return 'nextSuccessor' } } } class Order200 { constructor(){ = 2 } handle(orderType, pay, stock){ if(orderType === &&pay){ ('Appointment for 200 yuan deposit, get a coupon of 50 yuan') }else { return 'nextSuccessor' } } } class OrderNormal { constructor(){ = 0 } handle(orderType, pay, stock){ if (stock > ) { ('Ordinary purchase, no discount coupons') } else { ('Insufficient mobile phone inventory') } } } class Chain { constructor(order){ = order = null } setNextSuccessor(successor){ return = successor } passRequest(...val){ const ret = (,val) if(ret === 'nextSuccessor'){ return &&(,val) } return ret } } (new Order500()) var chainOrder500 = new Chain( new Order500() ); var chainOrder200 = new Chain( new Order200() ); var chainOrderNormal = new Chain( new OrderNormal() ); ( chainOrder200 ); ( chainOrderNormal ); ( 1, true, 500 ); // Output: 500 yuan deposit pre-order, get 100 coupons( 2, true, 500 ); // Output: 200 yuan deposit pre-order, get 50 coupons( 3, true, 500 ); // Output: Normal purchase, no coupons( 1, false, 0 ); // Output: Insufficient mobile phone inventory
18. Intermediary mode
- The role of the intermediary model is to undo the tight coupling relationship between objects. After adding an intermediary object, all relevant objects communicate through the intermediary object, rather than referring to each other, so when an object changes, you only need to pass through the intermediary object. The mediator loosens the coupling between objects and can independently change their interactions. The intermediary model turns the mesh many-to-many relationship into a relatively simple one-to-many relationship.
advantage
- Reduces the complexity of the class and converts one-to-many into one-to-one. Decoupling between classes.
shortcoming
- When intermediaries become large and complex, it makes it difficult to maintain.
example
// html----------- Select a color:<select name="" > <option value="">Please select</option> <option value="red">red</option> <option value="blue">blue</option> </select> <br /> Select Memory:<select name="" > <option value="">Please select</option> <option value="32G">32G</option> <option value="63G">64G</option> </select> <br /> Enter the purchase quantity:<input type="text" /> <br /> <div>You selected the color:<span ></span></div> <div>You selected memory:<span ></span></div> <div>You selected the quantity:<span ></span></div> <button disabled="true">Please select手机颜色和购买数量</button> // js ------------------- const goods = { "red|32G": 3, "red|16G": 0, "blue|32G": 1, "blue|16G": 6 }, colorSelect = ('colorSelect'), memorySelect = ('memorySelect'), numberInput = ('numberInput'), colorInfo = ('colorInfo'), memoryInfo = ('memoryInfo'), numberInfo = ('numberInfo'), nextBtn = ('nextBtn'), mediator = (function () { return { changed(obj) { const color = , memory = , number = , stock = goods[`${color}|${memory}`] if (obj === colorSelect) { = color } else if (obj === memorySelect) { = memory } else if (obj === numberInput) { = number } if (!color) { = true = 'Please select the color of the phone' return } if (!memory) { = true = 'Please select the memory size' return } if ((number - 0) && number < 1) { = true = 'Please enter the correct purchase quantity' return } = false = 'Put into the shopping cart' } } })() = function () { (this) } = function () { (this) } = function () { (this) }
19. Prototype mode
- Prototype pattern refers to the type of objects that are created by copying these prototypes to create new objects. To put it bluntly, cloning yourself and generating a new object.
advantage
- No longer relying on constructors or classes to create objects, you can use this object as a template to generate more new objects.
shortcoming
- For attributes containing reference type values, all instances will get the same attribute value by default.
example
const user = { name:'Xiao Ming', age:'30', getInfo(){ (`Name:${},age:${}`) } } const xiaozhang = (user) = 'Xiao Zhang' = 18 () // Name: Xiao Zhang, Age: 18() // Name: Xiao Ming, Age: 30
20. Memorandum Mode
- The memo mode is to capture the internal state of an object without destroying the encapsulation and save this state outside the object to ensure that the object can be restored to its original state in the future.
advantage
- It provides a mechanism for users to restore state, which allows users to return to a certain historical state more conveniently.
- The information is encapsulated so that the user does not need to care about the details of the state saving.
shortcoming
- If there are too many member variables in the class, it will inevitably occupy a relatively large resource, and each save will consume a certain amount of memory.
example
// Chess piececlass ChessPieces { constructor(){ = {} } // Get chess pieces getChess(){ return } } // Record the chess pathclass Record { constructor(){ = [] // Record the chess path } recordTallyBook(chess){ // ((chess)) const isLoadtion = ( item=> === ) if(isLoadtion){ (`${},${}Other pieces already exist`) }else { (chess) } // (item=> === ) } getTallyBook(){ return () } } // Chess rulesclass ChessRule { constructor(){ = {} } playChess(chess){ = chess } getChess(){ return } // Record the chess path recordTallyBook(){ return new ChessPieces() } // Regret chess repentanceChess(chess){ = () } } const chessRule = new ChessRule() const record = new Record() ({ type:'Black Chess', location:'X10,Y10' }) (())//Record the chess path({ type:'White Chess', location:'X11,Y10' }) (())//Record the chess path({ type:'Black Chess', location:'X11,Y11' }) (())//Record the chess path({ type:'White Chess', location:'X12,Y10' }) (())//{type:'White Chess',location:'X12,Y10'}(record) // Regret chess(())//{type:'Black Chess',location:'X11,Y11'}(record) // Regret chess(())//{type:'White Chess',location:'X11,Y10'}
21. Bridge mode
- The bridge pattern refers to separating the abstract part from its implementation part, making them independently change, and reducing the coupling degree of abstraction and realizing the two variable dimensions by using a combination relationship instead of the inheritance relationship.
advantage
- The separation of abstraction and implementation. Excellent expansion ability. Implement details to customers transparently.
shortcoming
- The introduction of the bridge pattern will increase the difficulty of understanding and design of the system. Since the aggregation relationship is built on the abstract layer, developers are required to design and program abstractions.
example
- For example, the mobile phones we use, Apple's iPhoneX, and Huawei's mate40, the brand and model are their common abstract parts, and they can be extracted separately.
class Phone { constructor(brand,modle){ = brand = modle } showPhone(){ return `Mobile phone brand:${()},model${()}` } } class Brand { constructor(brandName){ = brandName } getBrand(){ return } } class Modle { constructor(modleName){ = modleName } getModle(){ return } } const phone = new Phone(new Brand('Huawei'),new Modle('mate 40')) (())
22. Visitor mode
- The visitor mode separates the operation of data from the structure of data, encapsulating independent classes for the operations of each element in the data, so that it expands new data without changing the data structure.
advantage
- Comply with the principle of single responsibility. Excellent scalability and flexibility.
shortcoming
- Violating the principle of dependency inversion, relying on concrete classes, and not relying on abstractions.
example
class Phone { accept() { throw new Error('The subclass's accept must be rewritten') } } class Mata40Pro extends Phone { accept() { const phoneVisitor = new PhoneVisitor() return (this) } } class IPhone13 extends Phone { accept() { const phoneVisitor = new PhoneVisitor() return (this) } } // Visitor class class PhoneVisitor { visit(phone) { if ( === IPhone13) { return { os: 'ios', chip: 'A15 Bionic Chip', screen: 'Capacitor screen' } } else if ( === Mata40Pro) { return { os: 'HarmonyOS', chip: 'Kirin 9000', GPUType: 'Mali-G78', port: 'type-c' } } } } const mata40Pro = new Mata40Pro() (())
23. Interpreter mode
- The interpreter pattern provides a way to evaluate the syntax or expression of a language, and it belongs to the behavioral pattern. This pattern implements an expression interface that interprets a specific context.
advantage
- It has better scalability and flexibility. Added new ways to interpret expressions.
shortcoming
- There are few available scenarios and are almost invisible in web development. It is difficult to maintain complex environments.
- Interpreter mode causes class bloating. It also uses recursive calling methods, which may cause crashes if not controlled properly.
example
class TerminalExpression { constructor(data) { = data } interpret(context) { if (() &gt; -1) { return true; } return false; } } class OrExpression { constructor(expr1, expr2) { this.expr1 = expr1; this.expr2 = expr2; } interpret(context) { return this.(context) || this.(context); } } class AndExpression { constructor(expr1, expr2) { this.expr1 = expr1; this.expr2 = expr2; } interpret(context) { return this.(context) &amp;&amp; this.(context); } } class InterpreterPatternDemo { static getMaleExpression() { const robert = new TerminalExpression("Xiao Ming"); const john = new TerminalExpression("Little Dragon"); return new OrExpression(robert, john); } static getMarriedWomanExpression() { const julie = new TerminalExpression("Zhang San"); const married = new TerminalExpression("Little Red"); return new AndExpression(julie, married); } static init(args) { const isMale = (); const isMarriedWoman = (); (`Xiaolong is a male?${("Little Dragon")}`) (`Xiaohong is a married woman?${("Little Red Zhang San")}`) } } ()
Summarize
- The above is my study summary that took me nearly a month, but one month is far from enough. After writing this article, I still lack understanding of the application scenarios of certain design patterns. Design patterns are knowledge points that require in-depth research for a long time. They need to practice imitation based on actual scenarios and constantly think about them. In addition, due to the characteristics of js, many design patterns are incomplete and incomplete in js. It is a bit useless to imitate traditional design patterns by force. However, with the emergence of typescript, design patterns can be infinitely close to traditional design patterns in ts. We plan to write a ts version of the design pattern blog.
- Source of the article in this film:
- Book "JavaScript Design Patterns and Development Practice"
- Teacher Shuangyue: "System Explanation and Application of Javascript Design Patterns"
- And various cases borrowed from Baidu Search
The above is the detailed content of the 23 JavaScript design patterns summary. For more information about JavaScript design patterns, please pay attention to my other related articles!