Article series

Javascript design pattern singleton pattern

The adaptor pattern of javascript design patterns

The Decorator pattern of javascript design patterns

Proxy mode of javascript design pattern

Comparison of javascript adaptation, proxy, and decorator patterns

State mode of javascript design mode

The iterator pattern of javascript design patterns

Policy mode of javascript design pattern

The Observer pattern of javascript design patterns

Publish subscriber mode of javascript design pattern

concept

The decorator pattern, also known as the decorator pattern, dynamically adds responsibility to an object while the program is running, without changing the object itself. Decorator is a lighter and more flexible approach than inheritance.

In this paper, the code

Just like the phone button, with the phone button will be easy to watch video, but for all the original functions of the phone, such as taking photos can still be used directly. The phone button is the icing on the cake and doesn’t have to be there.

Why use decorator pattern

Initial requirements:

Draw a circle

Implementation:

class Circle {
	draw() {
		console.info('circle')}}let c = new Circle()
c.draw()
Copy the code

Change requirements:

Let me draw a red circle

Implementation:

Maybe you just want to find the Circle draw method and change it to:

class Circle {
	draw() {
		console.info('circle')
		this.setRed()
	}
	setRed() {
		console.info('Set red border')}}let c = new Circle()
c.draw()
Copy the code

This is fine if the requirements are not changed. But if one day the manager says I want to implement a green border, does it have to be:

class Circle {
	draw() {
		console.info('circle')
		this.setGreen()
	}
	setRed() {
		console.info('Set red border')}setGreen() {
		console.info('Set green border')}}let c = new Circle()
c.draw()
Copy the code

There are two problems with this approach:

  • The Circle class handles both drawing circles and setting colors, which violates the single responsibility principle
  • Every time I want to set a different color, I move the Circle class and change the draw method, which violates the open closed principle (open for adding, closed for modifying).

To ensure that new business requirements do not affect the original functions, the old logic needs to be separated from the new logic:

class Circle {
	draw() {
		console.info('circle')}}class Decorator {
	constructor(circle) {
		this.circle = circle
	}
	draw() {
		this.circle.draw()
		this.setRedBorder()
	}
	setRedBorder() {
		console.info('Set red border')}}let c = new Circle()
let d = new Decorator(c)
d.draw()
Copy the code

If you want to draw a different colored circle, you just modify the Decorator class.

In this way, the “add, don’t modify” Decorator pattern is implemented, using the logic of the Decorator to decorate the old circular logic. Of course, you can still simply create a circle without color, using C.raw ()

AOP decorator functions

Aspect Oriented Programming (AOP) is Aspect Oriented Programming. Some functions irrelevant to the core business logic are taken out and then incorporated into the business logic module through “dynamic weaving”

Functions independent of business logic typically include log statistics, security control, exception handling, and so on.

First we implement two functions:

One for front decoration and one for back decoration:

Function.prototype.before = function(beforeFunc){
    var that = this;
    return function(){
        beforeFunc.apply(this.arguments);
        return that.apply(this.arguments); }}Function.prototype.after = function(afterFunc){
    var that = this;
    return function(){
        var ret = that.apply(this.arguments);
        afterFunc.apply(this.arguments);
        returnret; }}Copy the code

Take before as an example. When you call before, you pass in a function, which is the newly added function, and it loads the code for the newly added function.

It then saves the current this and returns a “proxy” function. In this way, the extended function is executed before the original function is called, and they share the same argument list.

The rear trim is basically similar to the front trim, but in a different order

Validation:

var foobar = function (x, y, z) {
	console.log(x, y, z)
}
var foo = function (x, y, z) {
	console.log(x / 10, y / 10, z / 10)}var bar = function (x, y, z) {
	console.log(x * 10, y * 10, z * 10)
}
foobar = foobar.before(foo).after(bar)
foobar(1.2.3)
Copy the code

Output:

0.1 0.2 0.3
1 2 3
10 20 30
Copy the code

The above method of setting before and after contaminates the prototype. It can be changed to:

var before = function (fn, beforeFunc) {
	return function () {
		beforeFunc.apply(this.arguments)
		return fn.apply(this.arguments)}}var after = function (fn, afterFunc) {
	return function () {
		var ret = fn.apply(this.arguments)
		afterFunc.apply(this.arguments)
		return ret
	}
}
var a = before(
	function () {
		alert(3)},function () {
		alert(2)
	}
)
a = before(a, function () {
	alert(1)
})
a()
Copy the code

Output:

1
2
3
Copy the code

Decorators in ES7

Configure the environment

  1. npm install babel-plugin-transform-decorators-legacy –save-dev
  2. Modify.babelrc file:
{
    "presets": ["es2015"."latest"]."plugins": ["transform-decorators-legacy"] // Add plugins to support ES7's decorative syntax
}
Copy the code

Decoration class

Decorator classes take no arguments

// A decorator function whose first argument is the target class
function classDecorator(target) {
	target.hasAdd = true
	// Return target // Optional, the default is to return this
}

// "install" the decorator on the Button class
@classDecorator
class Button {}

// Verify that the decorator is valid
alert('Button is decorated: ' + Button.hasAdd)
Copy the code

Is equivalent to

function classDecorator(target) {
	target.hasAdd = true
	return target // Use this as a function, not as a constructor
}

class Button {}

Button = classDecorator(Button)
// Verify that the decorator is valid
alert('Button is decorated: ' + Button.hasAdd)
Copy the code

Explain how decorators work:

@decorator
class A{}

/ / is equivalent to
A = decorator(A) || A
Copy the code

Indicates that the decorator in ES7 is also grammatical sugar

Decorator classes take arguments

// To receive arguments, the decorator returns a function whose first argument is the target class
function classDecorator(name) {
	return function (target) {
		target.btnName = name
	}
}

// "install" the decorator on the Button class
@classDecorator('login')
class Button {}

// Verify that the decorator is valid
alert('Button name:' + Button.btnName)

Copy the code

Is equivalent to

// To receive arguments, the decorator returns a function whose first argument is the target class
function classDecorator(name) {
	return function (target) {
		target.btnName = name
		return target
	}
}

// "install" the decorator on the Button class
class Button {}

Button = classDecorator('login')(Button)
// Verify that the decorator is valid
alert('Button name:' + Button.btnName)
Copy the code

Decorator class – Mixin example

function mixin(. list) {
	console.info(... list,'list') 
        / /... Function () {alert('foo')} function() {alert('foo')}
	return function (target) {
		Object.assign(target.prototype, ... list)console.dir(target, 'target')}}const Foo = {
	foo() {
		alert('foo')
	}
}
@mixin(Foo)
class Button {}
let d = new Button()
d.foo()
Copy the code

You can see that we added foo to the Button class prototype, so one might ask, why not just add foo to the class, i.e

function mixin(. list) {
	console.info(... list,'list') / /... Function () {alert('foo')} function() {alert('foo')}
	return function (target) {
		Object.assign(target, ... list)console.dir(target, 'target')}}const Foo = {
	foo() {
		alert('foo')
	}
}
@mixin(Foo)
class Button {}
let d = new Button()
d.foo()
Copy the code

Uncaught TypeError: D. fo is not a function error is reported

This is because the instance is generated dynamically while the code is running, whereas the decorator function is executed at compile time, so the decorator mixin function is executed before the Button instance exists.

To ensure that the instantiated method (foo) can be accessed successfully, the decorator can only decorate the prototype object of the Button class.

Adornment method

Readonly sample

function readonly(target, name, descriptor) {
	descriptor.writable = false
	return descriptor
}

class Person {
	constructor() {
		this.first = 'A'
		this.last = 'B'
	}

	@readonly
	name() {
		return `The ${this.first} The ${this.last}`}}let p = new Person()
console.info(p.name())
// p.name = function () {
// console.info(100)
//} //

Copy the code

The log sample

function log(target, name, descriptor) {
	let oldValue = descriptor.value
	descriptor.value = function () {
		console.log(`calling ${name} width`.arguments)
		return oldValue.apply(this.arguments)}return descriptor
}

class Math {
	@log
	add(a, b) {
		return a + b
	}
}
let math = new Math(a)const result = math.add(4.6)
alert(result)

Copy the code

Summary of decoration methods

Readonly and log are implemented by modifying the descriptor. What are the three parameters of the function that decorates the method?

  • Target represents the target object, just like the decorator class
  • Name is the name of the modified method, for example, the name in the log decorator above is “add”
  • Descriptor is the same as Object. DefineProperty (obj, Prop, descriptor) descriptor

There is an open source third-party library, Core-Decorators, that provides a number of useful decorators.

// import { readonly } from 'core-decorators'

// class Person {
// @readonly
// name() {
// return 'zhang'
/ /}
// }

// let p = new Person()
// alert(p.name())
// // p.name = function () { /*... */} // An error is reported here

import { deprecate } from 'core-decorators'

class Person {
	@deprecate
	facepalm() {}

	@deprecate('We stopped facepalming')
	facepalmHard() {}

	@deprecate('We stopped facepalming', {
		url: 'http://knowyourmeme.com/memes/facepalm'
	})
	facepalmHarder(){}}let person = new Person()

person.facepalm()
// DEPRECATION Person#facepalm: This function will be removed in future versions.

person.facepalmHard()
// DEPRECATION Person#facepalmHard: We stopped facepalming

person.facepalmHarder()
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
// See http://knowyourmeme.com/memes/facepalm for more details.

Copy the code

Decorator pattern uses scenarios

React higher order function

High Order Component (HOC) is an advanced technique used in React to reuse component logic. Instead of being part of the React API, HOC is a design pattern based on the React combinatorial features.

Requirements:

Implement a “display only when logged in” feature, such as displaying the logout button only when logged in, or displaying the shopping cart only when logged in

const LogoutButton = () = > {
	if (getUserId()) {
		return '... ' // Display "Log out" JSX
	} else {
		return null}}Copy the code
/ / shopping cart
const ShoppingCart = () = > {
	if (getUserId()) {
		return '... ' // Display "shopping cart" JSX
	} else {
		return null}}Copy the code

The following problems exist:

  1. Code redundancy, where two components obviously have duplicate code
  2. In violation of a single responsibility, the LogoutButton should only be responsible for logging out of the login-related business and should not contain login-determining logic

Use advanced components to fix the above problems

const withLogin = (Component) = > {
	const NewComponent = (props) = > {
		if (getUserId()) {
			return <Component {. props} / >
		} else {
			return null}}return NewComponent
}

const LoginButton = withLogin((props) = > {
	return '... ' // Display "Log out" JSX
})

const ShoppingCart = withLogin((props) = > {
	return '... ' // Display "shopping cart" JSX
})

Copy the code

As you can see, high-level components are actually the same class decorators that we mentioned above from the implementation level.

Refer to the link

Core principles and application practices of JavaScript design pattern

conclusion

Your “like” is my biggest affirmation, if you think it is helpful, please leave your praise, thank you!!