Общие шаблоны дизайна в интерфейне позволяют вам написать более элегантный код

Node.js Vue.js React.js Angular.js

Код написать легко, но сложно написать элегантный код. Написание кода, который легко поддерживать, легко расширять и иметь четкую структуру, должно быть целью каждого разработчика. Изучение шаблонов проектирования и их разумное использование может сделать нас ближе к этой цели дальше. Недавно я прочитал книгу "Javascript Design Patterns and Development Practice". Одним словом, это действительно хорошая книга. Здесь я резюмирую некоторые основные шаблоны проектирования, которые мы можем использовать в JavaScript. Идеи паттернов проектирования стоит пережевывать и обдумывать снова и снова, в будущей бизнес-реализации эти идеи следует комбинировать и разумно использовать.

одноэлементный шаблон

Шаблон singleton гарантирует, что существует только один экземпляр класса, и предоставляет к нему глобальную точку доступа.

реализовано на js

  function getSingle(fn){
    let result

    return function (){
        return result || (result=fn.apply(this,arguments))
    }

  }

режим стратегии

Несколько методов для решения проблемы, инкапсулируйте каждый метод независимо и заменяйте друг друга

Программа, основанная на шаблоне стратегии, состоит как минимум из двух частей: одна представляет собой набор классов стратегий, которые инкапсулируют определенные алгоритмы и отвечают за конкретный процесс расчета, а другая представляет собой класс среды, который принимает запрос клиента, а затем делегирует запрос классу политики

Сценарий использования модели политики: формы валидации, инкапсулированные в набор правил проверки различной стратегии, чтобы избежать нескольких условных операторов.

Классическая поговорка:В языках, где функции — это объекты первого класса, шаблон стратегии неявный, а стратегии — это переменные, значениями которых являются функции.

пример:

const S = (salary)=>{
        return salary * 4
    }
    const A = (salary)=>{
        return salary * 3
    }
    const B = (salary)=>{
        return salary * 2
    }

    const calculate = (fun,salary)=>{
        return fun(salary)
    }
    calculate(S,1000)

прокси-режим

Шаблон прокси предоставляет суррогат или заполнитель для объекта, чтобы контролировать доступ к нему.

Вместо прямого взаимодействия с онтологией посередине добавляется слой прокси, который выполняет некоторые операции, которые не требуют выполнения онтологией.

var myImage=function(){
    var imgNode=document.createElement('img')
    document.body.appendChild(imgNode)
    return {
        setImg(src){
            imgNode.src=src
        }
    }
}

var proxyImg=function(){
    var img =new Image()
    img.onload=function(){
        myImage.setSrc(this.src)
    }
    return {
        setImg(src){
            myImage.setSrc(‘loading.png’)
            img.src=src      
        }
    }
}

Значение агентства

Проявление принципа единой ответственности, принцип единой ответственности означает, что функция или класс должны нести ответственность только за одну вещь, а то, что у функции слишком много обязанностей, эквивалентно соединению этих обязанностей вместе. измениться, это очень сложно. Это может повлиять на другие части функции

Наблюдатели и шаблоны публикации-подписки

Шаблоны наблюдателя и публикации и подписки делают две части программы не обязательно тесно связанными друг с другом, а взаимодействуют через уведомления.

  • Шаблон наблюдателя

Объект поддерживает ряд объектов, которые зависят от него, и активно уведомляет эти зависимые объекты при изменении состояния объекта.

Обратите внимание, что объект напрямую управляет списком зависимостей, что также является основным отличием режима наблюдателя от режимов публикации и подписки.

class Subject{
        constructor(){
            this.observers=[]
        }
        add(observer){
            this.observers.push(observer)
        }
        notify(data){
            for(let observer of this.observers){
                observer.update(data)
            }
        }
    }

    class Observer{
        update(){

        }
    }

Недостаток режима наблюдателя заключается в том, что объект должен сам поддерживать список наблюдателей.При обновлении состояния объекта методы других объектов вызываются напрямую.Поэтому при использовании мы обычно используем вариантный метод, который есть режим публикации-подписки.

  • модель публикации-подписки

Этот режим добавляет уровень конвейера между субъектом и наблюдателем, чтобы субъект и наблюдатель не взаимодействовали напрямую. Издатель публикует контент в конвейере, а подписчик подписывается на контент в конвейере. Цель состоит в том, чтобы избегайте генерации между подписчиком и издателем.

class Pubsub{

        constuctor(){
            this.pubsub={}
            this.subId=-1
        }

        publish(topic,data){
            if(!this.pubsub[topic]) return
            const subs=this.pubsub[topic]
            const len=subs.length
            while(len--){
                subs[len].update(topic,data)
            }
        }

        /**
        *  topic {string}
        *  update {function}
        */
        subscribe(topic,update){
            !this.pubsub[topic] && (this.pubsub[topic]=[])
            this.subId++
            this.pubsub[topic].push({
                token:this.subId,
                update
            })
        }

        unsubscribe(token){
            for(let topic in this.pubsub){
                if(this.pubsub.hasOwnProperty(topic)){
                    const current=this.pubsub[topic]
                    for(let i=0,j=current.length;i<j;i++){
                        if(current[i].token==token){
                            current.splice(i,1)
                            return token
                        }
                    }
                }
            }
            return this
        }

    }

Режим публикации-подписки — это режим проектирования, который часто используется при проектировании фреймворка, его можно увидеть в пользовательских событиях в angularjs, Rxjs, redux для управления состоянием и т. д.

командный режим

Команда в командном режиме относится к инструкции, которая делает что-то конкретное.

Наиболее распространенный сценарий использования паттерна команда: иногда вам нужно отправить запрос к какому-то объекту, но вы не знаете, кто является получателем запроса или какая запрошенная операция. В настоящее время я надеюсь спроектировать программу слабосвязанным образом, чтобы отправитель и получатель запроса могли устранить взаимосвязь между собой.

Происхождение шаблона команды на самом деле является объектно-ориентированной альтернативой функции обратного вызова.

Одним словом, командный режим заключается в том, чтобы обернуть конкретную реализацию функцией.Эта функция единообразно определяет метод выполнения для вызова конкретного метода реализации, и запрашивающей стороне нужно только общаться с командной функцией.

наилегчайший образец

Шаблон Flyweight, как следует из названия, разделяет некоторые единицы для оптимизации кода, который является повторяющимся, медленным и неэффективным при обмене данными.

Применение: один для уровня данных, обрабатывающий общие данные большого количества подобных объектов, хранящихся в памяти, другой для уровня DOM, прокси событий

В паттерне Flyweight есть понятие двух состояний — внутреннего и внешнего.

Внутреннее состояние хранится внутри объекта и может разделяться некоторыми объектами, независимо от конкретной сцены, и обычно не меняется

Внешние изменения состояния в соответствии со сценой

Объект, лишенный внешнего состояния, становится общим объектом, а внешнее состояние передается общему объекту, когда это необходимо для формирования полного объекта.

Несколько шагов для использования паттерна легковеса:

Описано на примере загрузки файла в книге

  1. Разделите внешнее состояние

class Upload{

    constructor(type){
        this.uploadType=type
    }

    delFile(id){
        uploadManager.setExternalState(id,this) //这里就是组装外部状态来使共享对象变成一个具体的对象
        if(this.fileSize<3000){
            //直接删除
            return
        }
        //弹窗询问确认删除?
    }

 }

2. Используйте фабрики для создания объектов

    var UploadFactory=(function(){
        const flyWeightObjs={}
        return {
            create(uploadType){
                if(flyWeightObjs[uploadType]){
                    return flyWeightObjs[uploadType]
                }
                return flyWeightObjs[uploadType]=new Upload(uoloadType)
            }
        }
    })()

3. Используйте менеджеры для инкапсуляции внешнего состояния

var uploadManager=(function(){
    var uploadDatabase={}
    return {
        add(id,uploadType,fileSize,fileName){
            var flyWeightObj=UploadFactory.create(uploadType) //那个被共享的对象
            //创建结点...
            //删除操作
            dom.onclick=function(){
                flyWeightObj.delFile(id) //这个共享在步骤1中会被组合,可以看到,只有在删除操作的时候,我们才需要那些外部状态
            }
            uploadDatabase[id]={
                fileName,
                fileSize,
                dom
            }
            return flyWeightObj
        },
        setExternalState(id,flyWeight){
            var externalState=uploadDatabase[id]
            Object.assign(flyWeight,externalState)
        }
    }
})()

Модель цепочки ответственности

Передать запрос нескольким функциям.Если запрос соответствует требованиям текущей функции, текущая функция обработает его, в противном случае он будет передан следующей функции.

очень хороший, очень мощный

Шаблон цепочки ответственности может избежать многих если, иначе если, еще

if (Function.prototype.chainAfter) {
    throw new Error('the chainAfter method already exist')
} else {
    Function.prototype.chainAfter = function (fn) {
        return (...args) => {
            const ret = this.apply(this, [...args, () => {
                return fn && fn.apply(this, args)
            }])
            if (ret === 'NEXT') {
                return fn && fn.apply(this, args)
            }
            return ret
        }
    }
}

/**
 * example
 * class Test{
 *  
 *     test(...args){
 *            alert('test')
 *            return 'NEXT'
 *     }
 * 
 *     test1(...args){
 * 
 *            setTimeout(()=>{
 *                  alert('test1')
 *                  args.pop()()
 *            })   
 *     }
 * 
 *     test2(...args){
 *            alert('test2')
 *     }
 * 
 *      $onInit(){
 *          const chain = this.test.bind(this)
 *                .chainAfter(this.test1.bind(this))
 *                .chainAfter(this.test2.bind(this))
 *         chain(1,2,3)
 *      }
 * }
 * 
 */


шаблон декоратора

Добавляйте новые функции к исходным функциям или объектам без изменения их функций

Украсьте функции с помощью АОП

if (Function.prototype.before) {
    throw new Error('the before method already exist')
} else {
    Function.prototype.before = function (beforefn) {
        return () => {
            if (beforefn.apply(this, arguments)) {
                this.apply(this, arguments)
            }
        }
    }
}

if (Function.prototype.after) {
    throw new Error('the after method already exist')
} else {
    Function.prototype.after = function (afterfn) {
        return () => {
            this.apply(this, arguments)
            afterfn.apply(this, arguments)
        }
    }
}


режим состояния

Позволяет объекту изменять свое поведение при изменении его внутреннего состояния, кажется, что объект изменяет свой класс

Вывод: инкапсулируйте состояние в отдельные функции и делегируйте запросы当前的状态对象, когда внутреннее состояние объекта изменится, это приведет к различным изменениям поведения

Пример электрического света:

Кнопка включает и выключает свет, нажмите один раз, чтобы включить его, и нажмите еще раз, чтобы выключить его

Первоначальная реализация:

const S = (salary)=>{
        return salary * 4
    }
    const A = (salary)=>{
        return salary * 3
    }
    const B = (salary)=>{
        return salary * 2
    }

    const calculate = (fun,salary)=>{
        return fun(salary)
    }
    calculate(S,1000)

Недостатком этого кода является то, что его нелегко расширить.Если вы хотите добавить мигающее состояние, вы должны изменить код в btnPressed

Использовать перезапись режима состояния

const S = (salary)=>{
        return salary * 4
    }
    const A = (salary)=>{
        return salary * 3
    }
    const B = (salary)=>{
        return salary * 2
    }

    const calculate = (fun,salary)=>{
        return fun(salary)
    }
    calculate(S,1000)