Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What are the design patterns that the web front end needs to know?

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly explains "what are the design patterns that the web front end needs to know?" the content in the article is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn "what are the design patterns you need to know about the web front end".

What is a design pattern?

Design pattern is a general solution to some kinds of problems that occur repeatedly in the process of software design and development. Design patterns are more of a guiding ideology and methodology than ready-made code, and of course each design pattern has a specific implementation in each language. Learning design patterns is more about understanding the inner ideas of various patterns and solving problems. After all, this is an excellent practice summed up by countless previous experiences, while code implementation is an aid to deepening understanding.

Types of design patterns

Design patterns can be divided into three main categories:

Structural pattern (Structural Patterns): simplifies the design of a system by identifying simple relationships between components in the system.

Creative mode (Creational Patterns): handles the creation of objects, creating objects in an appropriate way according to the actual situation. Conventional object creation may cause design problems or increase the complexity of the design. The creative pattern solves the problem by controlling the creation of objects in some way.

Behavioral patterns (Behavioral Patterns): used to identify and implement common interaction patterns between objects, thus increasing the flexibility of these interactions.

The above definition is very abstract and obscure, and it is not very helpful for us beginners. To understand the real role and value of these design patterns, we need to understand them through practice.

one。 Structural mode (Structural Patterns)

1. Appearance mode (Facade Pattern)

Appearance pattern is one of the most common design patterns, which provides a unified high-level interface for a group of interfaces in the subsystem, making the subsystem easier to use. In short, the design pattern is to abstract the complex logic in multiple subsystems, so as to provide a more unified, more concise, easier-to-use API. Many of our commonly used frameworks and libraries basically follow the design pattern. For example, JQuery abstracts and encapsulates complex native DOM operations and eliminates the compatibility problems between browsers, thus providing a more advanced and easier-to-use version. In fact, in our daily work, we often use appearance patterns for development, but we just don't know it.

For example, we can apply the appearance pattern to encapsulate a unified DOM element event binding / unbinding method for compatibility with different versions of browsers and more convenient calls:

/ bind event function addEvent (element, event, handler) {if (element.addEventListener) {element.addEventListener (event, handler, false);} else if (element.attachEvent) {element.attachEvent ('on' + event, handler);} else {element [' on' + event] = fn;}} / / unbind function removeEvent (element, event, handler) {if (element.removeEventListener) {element.removeEventListener (event, handler, false) } else if (element.detachEvent) {element.detachEvent ('on' + event, handler);} else {element [' on' + event] = null;}}

two。 Agent mode (Proxy Pattern)

First of all, everything can be represented, whether in the realization world or the computer world. In the real world, there are agents for buying a house, lawyers for litigation, and brokers for investors. they are all agents who help you deal with things that cannot be done due to your lack of time or professional skills. Analogously to the computer world, proxies play the same role. Proxies can be used when the cost of accessing an object itself is too high (such as too much memory, too long initialization time, etc.) or when you need to add additional logic without modifying the object itself. The function of Proxy is also added to ES6.

To sum up, the agent model can solve the following problems:

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

Add access control to an object

Additional logic is needed when accessing an object

To implement the proxy model, you need three parts:

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

Real Subject: real object

Proxy: proxy object

Subject interface: an interface that both Real Subject and Proxy need to implement, so that Proxy can be used as a "stand-in" for Real Subject

For example, there is an API for querying stock prices. It takes a long time to call this API (use setTimeout to simulate the call time of 2s):

StockPriceAPI:

Function StockPriceAPI () {/ / Subject Interface implements this.getValue = function (stock, callback) {console.log ('Calling external API...'); setTimeout () = > {switch (stock) {case 'GOOGL': callback (' $1265.23'); break; case 'AAPL': callback (' $287.05'); break Case 'MSFT': callback (' $173.70'); break; default: callback ('');}}, 2000);}}

Instead of requesting the remote interface every time, we add a caching mechanism to get it directly from the cache when there is a cache, otherwise we will request the remote interface again. We can do this through a proxy:

StockPriceAPIProxy:

Function StockPriceAPIProxy () {/ / cached object this.cache = {}; / / Real API object this.realAPI = new StockPriceAPI (); / / Subject Interface implements this.getValue = function (stock, callback) {const cachedPrice = this.cache [stock]; if (cachedPrice) {console.log ('Got price from cache'); callback (cachedPrice) } else {this.realAPI.getValue (stock, (price) = > {this.cache [stock] = price; callback (price);});}

Note that Proxy needs to implement the getValue () method just like the real object, and getValue () belongs to the Subject interface.

Test it:

Const api = new StockPriceAPIProxy (); api.getValue ('GOOGL', (price) = > {console.log (price)}); api.getValue (' AAPL', (price) = > {console.log (price)}); api.getValue ('MSFT', (price) = > {console.log (price)}); setTimeout () = > {api.getValue (' GOOGL', (price) = > {console.log (price)}) Api.getValue ('AAPL', (price) = > {console.log (price)}); api.getValue (' MSFT', (price) = > {console.log (price)});}, 3000)

Output:

Calling external API... Calling external API... Calling external API... $1265.23 $287.05 $173.70 Got price from cache $1265.23 Got price from cache $287.05 Got price from cache $173.70 II. Creative mode (Creational Patterns)

1. Factory mode (Factory Pattern)

In real life, factories make products according to established procedures, and the products produced by different raw materials and processes will be different. When applied to the field of software engineering, the factory can be regarded as an object that makes other objects, and the manufactured objects will vary according to the parameters of the incoming factory objects.

What scenario is appropriate to apply the factory pattern instead of directly new an object? When there are too many constructors that are not easy to manage, and there are some associations between the objects you need to create (have the same parent class, implement the same interface, and so on), you might as well use the factory pattern. The factory pattern provides a centralized and unified way to avoid the problems of code duplication and poor flexibility caused by decentralized creation of objects.

In the above figure, for example, we build a simple car factory to produce cars:

/ / Automobile constructor function SuzukiCar (color) {this.color = color; this.brand = 'Suzuki';} / / Automobile constructor function HondaCar (color) {this.color = color; this.brand =' Honda';} / / Automobile constructor function BMWCar (color) {this.color = color; this.brand = 'BMW' } / / Automobile Brand enumeration const BRANDS = {suzuki: 1, honda: 2, bmw: 3} / * * Automobile Factory * / function CarFactory () {this.create = function (brand, color) {switch (brand) {case BRANDS.suzuki: return new SuzukiCar (color); case BRANDS.honda: return new HondaCar (color); case BRANDS.bmw: return new BMWCar (color) Default: break;}

Test it:

Const carFactory = new CarFactory (); const cars = []; cars.push (carFactory.create (BRANDS.suzuki, 'brown')); cars.push (carFactory.create (BRANDS.honda,' grey')); cars.push (carFactory.create (BRANDS.bmw, 'red')); function say () {console.log (`Hi, I am a ${this.color} ${this.brand} car`);} for (const car of cars) {say.call (car);}

Output:

Hi, I am a brown Suzuki car Hi, I am a grey Honda car Hi, I am a red BMW car

After using the factory pattern, there is no need to repeat the introduction of constructors, only the introduction of factory objects can easily create all kinds of objects.

two。 Singleton mode (Singleton Pattern)

As the name implies, the maximum number of instances of Class in the singleton pattern is 1. The singleton pattern comes in handy when an object is needed to perform certain tasks throughout the system. In other scenarios, try to avoid the use of singleton mode, because singleton mode will introduce global state, and a healthy system should avoid introducing too much global state.

To implement the singleton pattern, you need to solve the following problems:

How can I be sure that there is only one instance of Class?

How can I easily access the only instance of Class?

How does Class control the instantiation process?

How to limit the number of Class instances to 1?

We generally solve the above problems by implementing the following two points:

Hide the constructor of Class to avoid multiple instantiations

Create / get a unique instance by exposing a getInstance () method

The singleton pattern in Javascript can be implemented in the following ways:

/ / Singleton constructor const FooServiceSingleton = (function () {/ / hidden Class constructor function FooService () {} / / uninitialized singleton object let fooService; return {/ / create / get singleton object function getInstance: function () {if (! fooService) {fooService = new FooService ();} return fooService }) ()

The key points are as follows: 1. Use IIFE to create a local scope and execute it immediately; 2. GetInstance () is a closure, using closures to save singletons in the local scope and return.

We can verify whether the following singleton object is created successfully:

Const fooService1 = FooServiceSingleton.getInstance (); const fooService2 = FooServiceSingleton.getInstance (); console.log (fooService1 = fooService2); / / true III. Behavioral pattern (Behavioral Patterns)

1. Policy Mode (Strategy Pattern)

The simple description of the policy pattern is that the object has a behavior, but in different scenarios, the behavior has different implementation algorithms. For example, everyone has to "pay personal income tax", but there are different calculation methods for "paying personal income tax in the United States" and "paying personal income tax in China". The most common scenarios that use policy mode, such as login authentication, the authentication algorithm depends on whether the user's login method is mobile phone, mailbox or a third-party Wechat login, etc., and the login method can only be obtained at runtime. After getting the login method, configure the authentication policy dynamically. All these strategies should implement a unified interface, or a unified behavior pattern. The design of Passport.js API, a famous authentication library in Node ecology, applies the strategy pattern.

In the case of login authentication, we follow the idea of passport.js to understand the policy mode through the code:

/ * login controller * / function LoginController () {this.strategy = undefined; this.setStrategy = function (strategy) {this.strategy = strategy; this.login = this.strategy.login;}} / * * username, password login policy * / function LocalStragegy () {this.login = ({username, password}) = > {console.log (username, password); / / authenticating with username and password... }} / * Login policy for mobile number and verification code * / function PhoneStragety () {this.login = ({phone, verifyCode}) = > {console.log (phone, verifyCode); / / authenticating with hone and verifyCode... }} / * third-party social login policy * / function SocialStragety () {this.login = ({id, secret}) = > {console.log (id, secret); / / authenticating with id and secret... } const loginController = new LoginController (); / / call username and password login API, use LocalStrategy app.use ('/ login/local', function (req, res) {loginController.setStrategy (new LocalStragegy ()); loginController.login (req.body);}); / / call mobile phone and verification code login interface, use PhoneStrategy app.use ('/ login/phone', function (req, res) {loginController.setStrategy (new PhoneStragety () LoginController.login (req.body);}); / / call the social login interface, using SocialStrategy app.use ('/ login/social', function (req, res) {loginController.setStrategy (new SocialStragety ()); loginController.login (req.body);})

From the above example, we can see that using policy mode has the following advantages:

Facilitate switching algorithms and policies at run time

The code is more concise and avoids using a large number of conditional judgments

Attention separation, each strategy class controls its own algorithmic logic, and strategy and its users are independent of each other.

two。 Iterator mode (Iterator Pattern)

Iterator, an iterator in ES6, is no stranger to us. Iterators are used to traverse containers (collections) and access elements in the container, and no matter what the container's data structure is (Array, Set, Map, etc.), the interface of the iterator should be the same and need to follow the iterator protocol.

The iterator pattern solves the following problems:

Provides a consistent way to traverse various data structures without understanding the internal structure of the data

Provides the ability to traverse containers (collections) without changing the container's interface

An iterator usually needs to implement the following interfaces:

HasNext (): determines whether the iteration is over and returns Boolean

Next (): find and return the next element

Implementing an iterator for an array of Javascript can be written as follows:

Const item = [1, 'red', false, 3.14]; function Iterator (items) {this.items = items; this.index = 0;} Iterator.prototype = {hasNext: function () {return this.index

< this.items.length; }, next: function () { return this.items[this.index++]; } } 验证一下迭代器是否工作: const iterator = new Iterator(item); while(iterator.hasNext()){ console.log(iterator.next()); } 输出: 1, red, false, 3.14 ES6提供了更简单的迭代循环语法 for...of,使用该语法的前提是操作对象需要实现 可迭代协议(The iterable protocol),简单说就是该对象有个Key为 Symbol.iterator 的方法,该方法返回一个iterator对象。 比如我们实现一个 Range 类用于在某个数字区间进行迭代: function Range(start, end) { return { [Symbol.iterator]: function () { return { next() { if (start < end) { return { value: start++, done: false }; } return { done: true, value: end }; } } } } } 验证一下: for (num of Range(1, 5)) { console.log(num); } 输出: 1, 2, 3, 4 3. 观察者模式(Observer Pattern) 观察者模式又称发布订阅模式(Publish/Subscribe Pattern),是我们经常接触到的设计模式,日常生活中的应用也比比皆是,比如你订阅了某个博主的频道,当有内容更新时会收到推送;又比如JavaScript中的事件订阅响应机制。观察者模式的思想用一句话描述就是:被观察对象(subject)维护一组观察者(observer),当被观察对象状态改变时,通过调用观察者的某个方法将这些变化通知到观察者。 比如给DOM元素绑定事件的 addEventListener() 方法: target.addEventListener(type, listener [, options]); Target就是被观察对象Subject,listener就是观察者Observer。 观察者模式中Subject对象一般需要实现以下API: subscribe(): 接收一个观察者observer对象,使其订阅自己 unsubscribe(): 接收一个观察者observer对象,使其取消订阅自己 fire(): 触发事件,通知到所有观察者 用JavaScript手动实现观察者模式: // 被观察者 function Subject() { this.observers = []; } Subject.prototype = { // 订阅 subscribe: function (observer) { this.observers.push(observer); }, // 取消订阅 unsubscribe: function (observerToRemove) { this.observers = this.observers.filter(observer =>

{return observer! = = observerToRemove;})}, / / event triggered fire: function () {this.observers.forEach (observer = > {observer.call ();});}}

Verify that the subscription was successful:

Const subject = new Subject (); function observer1 () {console.log ('Observer 1 fixing');} function observer2 () {console.log ('Observer 2 fixing');} subject.subscribe (observer1); subject.subscribe (observer2); subject.fire ()

Output:

Observer 1 Firing! Observer 2 Firing!

Verify that the unsubscription was successful:

Subject.unsubscribe (observer2); subject.fire ()

Output:

Observer 1 Firing!

4. Intermediary mode (Mediator Pattern)

In the intermediary pattern, the Mediator wraps a series of ways in which objects interact with each other, so that these objects do not have to interact directly, but the intermediaries coordinate their interaction, so that they can be loosely coupled. When the interaction between some objects changes, it will not immediately affect the interaction between other objects, ensuring that these functions can change independently of each other.

There are some similarities between the intermediary pattern and the observer pattern, which are both one-to-many relationships and centralized communication. The difference is that the intermediary pattern deals with the interaction between peer objects, while the Observer pattern deals with the interaction between Observer and Subject. The intermediary model is a bit like a dating agency, where dating partners can't communicate directly at first, but they have to screen matches through intermediaries and then decide who meets whom. The more common applications of the intermediary model, such as chat rooms, where people can not talk directly, but are forwarded through the medium of chat rooms. A simple chat room model can be implemented as follows:

Chat room member class:

Function Member (name) {this.name = name; this.chatroom = null;} Member.prototype = {/ / send message send: function (message, toMember) {this.chatroom.send (message, this, toMember);}, / / receive message receive: function (message, fromMember) {console.log (`${fromMember.name} to ${this.name}: ${message}`);}}

Chat room category:

Function Chatroom () {this.members = {};} Chatroom.prototype = {/ / add member addMember: function (member) {this.members [member.name] = member; member.chatroom = this;}, / / send message send: function (message, fromMember, toMember) {toMember.receive (message, fromMember);}}

Test it:

Const chatroom = new Chatroom (); const bruce = new Member ('bruce'); const frank = new Member (' frank'); chatroom.addMember (bruce); chatroom.addMember (frank); bruce.send ('Hey frank', frank)

Output:

Bruce to frank: hello frank

This is only the simplest chat room model, a real chat room can also add more features, such as sensitive information interception, one-to-many chat, broadcast and so on. Thanks to the intermediary mode, Member does not need to deal with the complex logic related to chat, but gives it all to Chatroom, effectively realizing the separation of concerns.

5. Visitor Mode (Visitor Pattern)

The Visitor pattern is a design pattern that separates the algorithm from the object structure. in popular terms, the Visitor pattern enables us to add new logic to an object without changing the structure of the object. the new logic is stored in a separate visitor object. The Visitor pattern is often used to expand third-party libraries and tools.

The implementation of the visitor pattern has the following elements:

Visitor Object: visitor object with a visit () method

Receiving Object: receive object with an accept () method

Visit (receivingObj): used for Visitor to receive a Receiving Object

Accept (visitor): for Receving Object to receive a Visitor and provide it with the ability to obtain Receiving Object data by calling Visitor's visit ()

The simple code implementation is as follows:

Receiving Object:

Function Employee (name, salary) {this.name = name; this.salary = salary;} Employee.prototype = {getSalary: function () {return this.salary;}, setSalary: function (salary) {this.salary = salary;}, accept: function (visitor) {visitor.visit (this);}}

Visitor Object:

Function Visitor () {} Visitor.prototype = {visit: function (employee) {employee.setSalary (employee.getSalary () * 2);}}

Verify:

Const employee = new Employee ('bruce', 1000); const visitor = new Visitor (); employee.accept (visitor); console.log (employee.getSalary ())

Output:

2000 Thank you for your reading, the above is the content of "what design patterns the web front end needs to know". After the study of this article, I believe you have a deeper understanding of the design patterns that the web front end needs to understand, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report