Creation pattern
The factory pattern
In factory mode, we create objects without exposing the creation logic to the client, and we use factory methods instead of new operations by using a common interface to point to newly created objects.
class Creator {
create(name) {
return new Animal(name)
}
}
class Animal {
constructor(name) {
this.name = name
}
}
var creator = new Creator()
var duck = creator.create('Duck')
console.log(duck.name) // Duck
var chicken = creator.create('Chicken')
console.log(chicken.name) // Chicken
Copy the code
Summary:
- The constructor is separated from the creator, encapsulating the new operation
- In line with the principle of openness and closure
The singleton pattern
The singleton pattern is defined by guaranteeing that a class has only one instance and providing a global method to access it.
The singleton pattern is a common pattern, and there are some objects that we usually need only one, such as thread pool, global cache, window object in browser, and so on. Singleton patterns are also very useful in JavaScript development. Imagine that when we click the login button, a login float window appears on the page, and the login float window is unique. No matter how many times we click the login button, the float window will only be created once. The name login float window is suitable for singleton mode.
For an example of a login box, the code looks like this:
<! DOCTYPE html><html lang="en">
<body>
<button id="btn">The login</button>
</body>
<script>
class Login {
createLayout() {
var oDiv = document.createElement('div')
oDiv.innerHTML = 'I'm the login box'
document.body.appendChild(oDiv)
oDiv.style.display = 'none'
return oDiv
}
}
class Single {
getSingle(fn) {
var result;
return function() {
return result || (result = fn.apply(this.arguments))}}}var oBtn = document.getElementById('btn')
var single = new Single()
var login = new Login()
// Due to the closure, createLoginLayer refers to result, so when single.getSingle is done, result is not destroyed in memory.
// When the button is clicked after the second time, result is returned according to the scope chain of the createLoginLayer function
// Decouple the methods of obtaining singletons and creating login boxes, in line with the open and closed principle
var createLoginLayer = single.getSingle(login.createLayout)
oBtn.onclick = function() {
var layout = createLoginLayer()
layout.style.display = 'block'
}
</script>
</html>
Copy the code
Summary:
1. The main idea of the singleton pattern is that the instance is returned if it has already been created.
function createSingleton() {
let obj = null
// If the instance has already been created, return it directly
if(! obj) { obj = xxx }return obj
}
Copy the code
2. In line with the principle of openness and closure
Structural mode
Adapter mode
The role of the adapter pattern is to solve the problem of interface incompatibilities between two software entities. With the adapter pattern, two software entities that previously could not work due to incompatible interfaces can work together.
The alias for the adapter is wrapper, which is a relatively simple pattern. There are many scenarios in program development where we try to use an interface of a module or object, only to find that the interface is not in the right format. There are two solutions. The first is to modify the original interface implementation, but the original module is very complicated, or the module we get is a piece of compressed code written by someone else, and modifying the original interface is not practical. The second approach is to create an adapter that converts the original interface into another interface that the customer wants, and the customer only needs to deal with the adapter.
Here’s an example of rendering a map:
class GoogleMap {
show() {
console.info('Render Google Maps')}}class BaiduMap {
show() {
console.info('Render Baidu Map')}}function render(map) {
if (map.show instanceof Function) {
map.show()
}
}
render(new GoogleMap()) // Render Google Maps
render(new BaiduMap()) // Render Baidu Map
Copy the code
But if the prototype method of BaiduMap is called display instead of show, then adapter mode can be used, because we cannot easily change the content of third parties. Encapsulate a layer on the basis of BaiduMap to expose the show method.
class GooleMap {
show() {
console.log('Render Google Maps')}}class BaiduMap {
display() {
console.log('Render Baidu Map')}}// Define an adapter class to encapsulate the BaiduMap class
class BaiduMapAdapter {
show() {
const baiduMap = new BaiduMap()
return baiduMap.display()
}
}
function render(map) {
if (map.show instanceof Function) {
map.show()
}
}
render(new GooleMap()) // Render Google Maps
render(new BaiduMapAdapter()) // Render Baidu Map
Copy the code
Summary:
- The adapter pattern solves the problem of mismatches between two interfaces. Instead of changing the existing interface, one object wraps another object.
- The adapter pattern conforms to the open closed principle
The proxy pattern
The proxy pattern provides a proxy or placeholder for an object to control access to it.
The proxy pattern is a very meaningful pattern, you can find many proxy pattern scenarios in life. For example, celebrities are represented by agents. If you want a star to do a commercial show, you have to contact his agent. The agent will negotiate the details of the commercial performance and the payment before handing the contract to the star to sign.
In this paper, a proxy for the image object using the example to understand the proxy pattern, when the network is bad, the loading of the images will take a period of time, it will produce gap, affect the user experience, we can really finish loading in the pictures, before using a loading to take pictures, the picture such as real load to set the SRC attribute to the picture.
class MyImage {
constructor() {
this.img = new Image()
document.body.appendChild(this.img)
}
setSrc(src) {
this.img.src = src
}
}
class ProxyImage {
constructor() {
this.proxyImage = new Image()
}
setSrc(src) {
let myImageObj = new MyImage()
myImageObj.img.src = 'loading.png'
this.proxyImage.src = src
this.proxyImage.onload = function() {
myImageObj.img.src = src
}
}
}
const proxyImage = new ProxyImage()
proxyImage.setSrc('pic.png')
Copy the code
In this case, the ontology class has its own setSrc method. If one day the network speed does not need to be preloaded, we can directly use the setSrc method of the ontology object, and do not need to change the code of the ontology class, and can delete the proxy class.
// Can still meet the demand
var myImage = new MyImage()
myImage.setSrc('https://images2.alphacoders.com/103/thumbbig-1031920.webp')
Copy the code
Summary:
- The proxy mode conforms to the open and closed principle
- Ontology objects and proxy objects have the same methods, and the user does not know whether the requested ontology object is a proxy object or not.
Behavioral pattern
The strategy pattern
Define a set of algorithms, encapsulate them, and make them fungible
var fnA = function(val) {
return val * 1
}
var fnB = function(val) {
return val * 2
}
var fnC = function (val) {
return val * 3
}
var calculate = function(fn, val) {
return fn(val)
}
console.log(calculate(fnA, 100))/ / 100
console.log(calculate(fnB, 100))/ / 200
console.log(calculate(fnC, 100))/ / 300
Copy the code
Observer mode (subscription-publish mode)
The publisk-subscribe pattern, also known as the observer pattern, defines a one-to-many dependency between objects. When an object’s state changes, all dependent objects are notified.
Start by implementing a simple publish-subscribe model, as follows:
class Event {
constructor() {
this.eventTypeObj = {}
}
on(eventType, fn) {
if (!this.eventTypeObj[eventType]) {
// Store different subscription callbacks according to different subscription event types
this.eventTypeObj[eventType] = []
}
this.eventTypeObj[eventType].push(fn)
}
emit() {
// Arguments borrow the shift method
let eventType = Array.prototype.shift.call(arguments)
let eventList = this.eventTypeObj[eventType]
for (let i = 0; i < eventList.length; i++) {
eventList[i].apply(eventList[i], arguments)}}remove(eventType, fn) {
// If remove is used, fn is the function name and cannot be an anonymous function
let eventTypeList = this.eventTypeObj[eventType]
if(! eventTypeList) {// If no one subscribed to the event, return it directly
return false
}
if(! fn) {// All events of this subscription type are cancelled if no unsubscribe callback is passed in
eventTypeList && (eventTypeList.length = 0)}else {
for (let i = 0; i < eventTypeList.length; i++) {
if (eventTypeList[i] === fn) {
eventTypeList.splice(i, 1)
// After deletion, I -- ensures that the next loop does not miss any function names that have not been traversed
i--
}
}
}
}
}
var handleFn = function(data) {
console.log(data)
}
var event = new Event()
event.on('click', handleFn)
event.emit('click'.'1') / / 1
event.remove('click', handleFn)
event.emit('click'.'2') / / not print at all
Copy the code
The above code is sufficient for subscribe-and-publish, but not for publish and subscribe. At this point, we can modify it a little bit to meet the publish-and-subscribe requirement. When we publish the message, we can cache the event until there are subscribers. The code is as follows:
class Event {
constructor() {
this.eventTypeObj = {}
this.cacheObj = {}
}
on(eventType, fn) {
if (!this.eventTypeObj[eventType]) {
// Store different subscription callbacks according to different subscription event types
this.eventTypeObj[eventType] = []
}
this.eventTypeObj[eventType].push(fn)
// If it is published first, the subscriber's callback is executed after the subscriber subscribes, based on the event type and parameters cached after publication
if (this.cacheObj[eventType]) {
var cacheList = this.cacheObj[eventType]
for (var i = 0; i < cacheList.length; i++) {
cacheList[i]()
}
}
}
emit() {
// Arguments borrow the shift method
var eventType = Array.prototype.shift.call(arguments)
var args = arguments
var that = this
function cache() {
if (that.eventTypeObj[eventType]) {
var eventList = that.eventTypeObj[eventType]
for (var i = 0; i < eventList.length; i++) {
eventList[i].apply(eventList[i], args)
}
}
}
if (!this.cacheObj[eventType]) {
this.cacheObj[eventType] = []
}
// If you subscribe first, you can subscribe and publish directly
cache(args)
// If you publish first and then subscribe, the event types and parameters of the publication are saved, and the subscription is executed when there is a subscription
this.cacheObj[eventType].push(cache)
}
}
Copy the code
Summary:
- The publish-subscribe model decouples code to meet the open and closed principle
- When the publish-subscribe model is used too much, the subscriber is kept in memory if the subscription message is never triggered.
Command mode
In software systems, there is usually a tight coupling between the behavior requester and the behavior implementer. However, in some cases, such as for recording, undo/redo, transaction processing, such as behavior, this tight coupling that cannot resist change is not appropriate. In this case, how do you decouple the behavior requester from the behavior implementor? Abstract a set of behaviors as objects to achieve loose coupling between them. This is the Command Pattern.
A command object is defined between the command publisher and the command receiver. The command object exposes a unified interface to the command publisher, and the command publisher does not care how the command recipient executes the command, thus decoupling the command publisher and the command receiver.
For example, if there are three buttons on the page, add different functions to each button. The code is as follows:
<! DOCTYPE html><html lang="en">
<head>
<meta charset="UTF-8">
<title>cmd-demo</title>
</head>
<body>
<div>
<button id="btn1">Button 1</button>
<button id="btn2">Button 2</button>
<button id="btn3">Button 3</button>
</div>
<script>
var btn1 = document.getElementById('btn1')
var btn2 = document.getElementById('btn2')
var btn3 = document.getElementById('btn3')
// Define a command publisher class
class Executor {
setCommand(btn, command) {
btn.onclick = function() {
command.execute()
}
}
}
// Define a command receiver
class Menu {
refresh() {
console.info('Refresh menu')}addSubMenu() {
console.info('Add submenu')}}// Define a class of command objects that refresh the menu
class RefreshMenu {
constructor(receiver) {
The command object is associated with the receiver
this.receiver = receiver
}
// Expose the unified interface to the command publisher Executor
execute() {
this.receiver.refresh()
}
}
// Define a class to add a submenu command object
class AddSubMenu {
constructor(receiver) {
// The receiver association of the command object
this.receiver = receiver
}
// Expose the unified interface to the command publisher Executor
execute() {
this.receiver.addSubMenu()
}
}
var menu = new Menu()
var executor = new Executor()
var refreshMenu = new RefreshMenu(menu)
// Add refresh function to button 1
executor.setCommand(btn1, refreshMenu)
var addSubMenu = new AddSubMenu(menu)
// Add submenu function to button 2
executor.setCommand(btn2, addSubMenu)
// If you want to add the ability to delete the menu to button 3, you can continue to add the command object to delete the menu and the specific deletion method of the receiver without modifying the command object
</script>
</body>
</html>
Copy the code