Шаблон проектирования JavaScript es6 (23 вида)

внешний интерфейс

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

Введение в шаблоны проектирования

Шаблоны проектирования представляют собой передовой опыт и обычно используются опытными разработчиками объектно-ориентированного программного обеспечения. Шаблоны проектирования — это решения общих проблем, с которыми разработчики программного обеспечения сталкиваются во время разработки программного обеспечения. Эти решения являются результатом длительного периода проб и ошибок многочисленных разработчиков программного обеспечения.

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

Принципы шаблонов проектирования

  • S – принцип единой ответственности
    • Программа делает только одно
    • Если функция слишком сложна, разделите ее и оставьте каждую часть независимой.
  • O – открытый закрытый принцип
    • Открыт для расширения, закрыт для модификации
    • Расширяйте новый код вместо изменения существующего кода при добавлении требований
  • L - принцип замены Лисков
    • Подклассы могут переопределять суперклассы
    • Там, где может появиться родительский класс, может появиться дочерний класс
  • I – Принцип разделения интерфейсов
    • Держите интерфейс единым и независимым
    • Как и в случае с принципом единой ответственности, здесь мы больше фокусируемся на интерфейсе.
  • D – Принцип инверсии зависимостей
    • Интерфейсно-ориентированное программирование, основанное на абстракциях, а не на конкретном
    • Пользователь обращает внимание только на интерфейс и не обращает внимания на реализацию конкретного класса.
ТАК размышляет больше, дайте каштан: (типа Promise)
  • Принцип единой ответственности: логика в каждом из них хорошо справляется с одной задачей.
  • Принцип открытости-закрытости (открыто для расширения, закрыто для модификации): если добавляются новые требования, то расширять
Еще один каштан: (источник этого примера-Ожидание — улучшение всех аспектов кода)
//checkType('165226226326','mobile')
//result:false
let checkType=function(str, type) {
    switch (type) {
        case 'email':
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str)
        case 'mobile':
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        case 'tel':
            return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
        default:
            return true;
    }
}

Есть две проблемы:

  • Если вы хотите добавить другие правила, вы должны добавить регистр в функцию. Добавьте правило и измените его один раз! Это нарушает принцип открытого-закрытого (открыто для расширения, закрыто для модификации). И это также сделает весь API раздутым и сложным в обслуживании.
  • Например, странице A необходимо добавить проверку суммы, а странице B — проверку даты, но проверка суммы требуется только на странице A, а проверка даты требуется только на странице B. Если вы продолжаете добавлять случай . Это необходимо для того, чтобы страница A добавила правила проверки, которые требуются только на странице B, что приводит к ненужным накладным расходам. То же самое верно и для страницы B.

Предлагаемый способ — добавить расширенный интерфейс к этому API:

let checkType=(function(){
    let rules={
        email(str){
            return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
        },
        mobile(str){
            return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
        }
    };
    //暴露接口
    return {
        //校验
        check(str, type){
            return rules[type]?rules[type](str):false;
        },
        //添加规则
        addRule(type,fn){
            rules[type]=fn;
        }
    }
})();

//调用方式
//使用mobile校验规则
console.log(checkType.check('188170239','mobile'));
//添加金额校验规则
checkType.addRule('money',function (str) {
    return /^[0-9]+(.[0-9]{2})?$/.test(str)
});
//使用金额校验规则
console.log(checkType.check('18.36','money'));

Подробнее об этом примере см. ->В ожидании i-рефакторинга — улучшения всех аспектов кода

Классификация шаблонов проектирования (23 шаблона проектирования)

  • Творчество
    • одноэлементный шаблон
    • режим прототипа
    • заводской узор
    • Абстрактный заводской узор
    • режим строителя
  • структурный
    • режим адаптера
    • Шаблон декоратора
    • прокси-режим
    • Внешний вид Режим
    • режим моста
    • Комбинированный режим
    • наилегчайший образец
  • поведенческий
    • Шаблон наблюдателя
    • шаблон итератора
    • режим стратегии
    • Шаблон метода шаблона
    • Схема цепочки ответственности
    • командный режим
    • режим заметки
    • режим состояния
    • шаблон посетителя
    • модель посредника
    • Режим переводчика

заводской узор

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

class Product {
    constructor(name) {
        this.name = name
    }
    init() {
        console.log('init')
    }
    fun() {
        console.log('fun')
    }
}

class Factory {
    create(name) {
        return new Product(name)
    }
}

// use
let factory = new Factory()
let p = factory.create('p1')
p.init()
p.fun()

Применимая сцена
  • Фабричный шаблон идеален, если вам не нужна сильная связь между подсистемой и более крупным объектом, но вы хотите выбирать из множества подсистем во время выполнения.
  • Новая операция просто инкапсулирована.
  • Необходимо создавать разные экземпляры в зависимости от конкретной среды, и эти экземпляры имеют одинаковое поведение.На данный момент мы можем использовать фабричный паттерн, чтобы упростить процесс реализации, и в то же время мы можем уменьшить количество кода требуется для каждого объекта, что способствует устранению связи между объектами. Обеспечивает большую гибкость
преимущество
  • Процесс создания объекта может быть сложным, но нам нужно заботиться только о результате создания.
  • Конструктор и создатель разделены по принципу «открыто-закрыто».
  • Вызывающий объект, который хочет создать объект, должен знать только его имя.
  • Высокая масштабируемость, если вы хотите добавить продукт, вам нужно только расширить фабричный класс.
недостаток
  • Когда добавляется новый продукт, необходимо написать новый конкретный класс продукта, что в определенной степени увеличивает сложность системы.
  • Учитывая масштабируемость системы, необходимо ввести уровень абстракции, который определяется в клиентском коде, что увеличивает абстракцию системы и сложность понимания
когда нет

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

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

пример
  • Знакомая нам $() в JQuery — это фабричная функция, которая создает элементы в соответствии с различными входящими параметрами или находит элементы в контексте и создает соответствующий объект jQuery.
class jQuery {
    constructor(selector) {
        super(selector)
    }
    add() {
        
    }
  // 此处省略若干API
}

window.$ = function(selector) {
    return new jQuery(selector)
}


  • Асинхронные компоненты для vue

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

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回调传递组件定义
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})


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

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

 class LoginForm {
    constructor() {
        this.state = 'hide'
    }
    show() {
        if (this.state === 'show') {
            alert('已经显示')
            return
        }
        this.state = 'show'
        console.log('登录框显示成功')
    }
    hide() {
        if (this.state === 'hide') {
            alert('已经隐藏')
            return
        }
        this.state = 'hide'
        console.log('登录框隐藏成功')
    }
 }
 LoginForm.getInstance = (function () {
     let instance
     return function () {
        if (!instance) {
            instance = new LoginForm()
        }
        return instance
     }
 })()

let obj1 = LoginForm.getInstance()
obj1.show()

let obj2 = LoginForm.getInstance()
obj2.hide()

console.log(obj1 === obj2)

преимущество

  • Разделите пространства имен и сократите глобальные переменные
  • Улучшите модульность, организуйте свой код под именем глобальной переменной и поместите его в одном месте для удобства обслуживания.
  • и будет создан только один раз. Упрощенная отладка и обслуживание кода

недостаток

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

Пример сценария

  • Определение пространств имен и реализация методов ветвления
  • окно входа
  • хранить в vuex и redux

режим адаптера

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

class Plug {
  getName() {
    return 'iphone充电头';
  }
}

class Target {
  constructor() {
    this.plug = new Plug();
  }
  getName() {
    return this.plug.getName() + ' 适配器Type-c充电头';
  }
}

let target = new Target();
target.getName(); // iphone充电头 适配器转Type-c充电头

преимущество

  • Любые два несвязанных класса можно запускать вместе.
  • Улучшено повторное использование классов.
  • Объект адаптации, библиотека адаптации, данные адаптации

недостаток

  • Создание дополнительных объектов, непрямых вызовов, имеет определенные накладные расходы (в отличие от режима прокси, который позволяет добиться оптимизации производительности в некоторых функциональных точках).
  • Если нет необходимости использовать режим адаптера, можно подумать о рефакторинге, а если используете, попробовать улучшить документацию

Сцены

  • Интеграция сторонних SDK
  • Инкапсулировать старый интерфейс
// 自己封装的ajax, 使用方式如下
ajax({
    url: '/getData',
    type: 'Post',
    dataType: 'json',
    data: {
        test: 111
    }
}).done(function() {})
// 因为历史原因,代码中全都是:
// $.ajax({....})

// 做一层适配器
var $ = {
    ajax: function (options) {
        return ajax(options)
    }
}
  • Vue вычисляет
<template>
    <div id="example">
        <p>Original message: "{{ message }}"</p>  <!-- Hello -->
        <p>Computed reversed message: "{{ reversedMessage }}"</p>  <!-- olleH -->
    </div>
</template>
<script type='text/javascript'>
    export default {
        name: 'demo',
        data() {
            return {
                message: 'Hello'
            }
        },
        computed: {
            reversedMessage: function() {
                return this.message.split('').reverse().join('')
            }
        }
    }
</script>

Данные в исходных данных не соответствуют текущим требованиям.Он адаптирован к нужному нам формату через правила вычисления атрибутов.Исходные данные не изменены,изменено только выражение исходных данных.

разница

Адаптер похож на шаблон прокси

  • Режим адаптера: Предоставляет другой интерфейс (например, другую версию штекера)
  • Режим прокси: Обеспечьте тот же интерфейс

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

  • Динамическое добавление некоторых дополнительных обязанностей к объекту является альтернативой реализации наследования.
  • На основе того, что исходный объект не изменяется, путем его обертывания и расширения, исходный объект может удовлетворить более сложные потребности пользователей, не затрагивая другие объекты, производные от этого класса.
class Cellphone {
    create() {
        console.log('生成一个手机')
    }
}
class Decorator {
    constructor(cellphone) {
        this.cellphone = cellphone
    }
    create() {
        this.cellphone.create()
        this.createShell(cellphone)
    }
    createShell() {
        console.log('生成手机壳')
    }
}
// 测试代码
let cellphone = new Cellphone()
cellphone.create()

console.log('------------')
let dec = new Decorator(cellphone)
dec.create()

Пример сценария

  • Например, сейчас есть 4 типа велосипедов, и мы определяем один

отдельный класс. Теперь поставьте фары, задние фонари на каждый велосипед Светильник и колокольчик эти 3 вида аксессуаров. Если вы используете наследование, чтобы дать Для создания подклассов для каждого типа велосипеда требуется 4 x 3 = 12 подклассов. Но если вы динамически группируете такие объекты, как фары, задние фонари и звонки На байке нужно всего лишь добавить 3 дополнительных класса

преимущество

  • И класс украшения, и класс украшений заботятся только о своем основном бизнесе и реализуют разделение.
  • Облегчает динамическое расширение и обеспечивает большую гибкость, чем наследование.

недостаток

  • Многослойное декорирование более сложное.
  • Часто вводится множество небольших объектов, которые выглядят одинаково, но на самом деле функционируют совершенно по-разному, что усложняет архитектуру нашего приложения.

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

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

Предполагая, что когда А получает цветы, когда он в хорошем настроении, Сяомин имеет шанс выразить успех

60%, а когда А получает цветы, когда он в плохом настроении, вероятность успеха признания Сяо Мина бесконечно близка к 0. Сяо Мин и А знакомы всего два дня, и они до сих пор не могут сказать, когда у А хорошее настроение. Если цветы подарить А не вовремя, цветы Существует высокая вероятность того, что его сразу выбросят.Этот букет цветов был куплен Сяо Мином после того, как он ел лапшу быстрого приготовления в течение 7 дней. Но друг А Б очень хорошо знает А, поэтому Сяомин просто дарит цветы Б, а Б будет следить за изменениями настроения А, а затем выбирает Выберите А, чтобы передать цветок А, когда он в хорошем настроении.Код выглядит следующим образом:

let Flower = function() {}
let xiaoming = {
  sendFlower: function(target) {
    let flower = new Flower()
    target.receiveFlower(flower)
  }
}
let B = {
  receiveFlower: function(flower) {
    A.listenGoodMood(function() {
      A.receiveFlower(flower)
    })
  }
}
let A = {
  receiveFlower: function(flower) {
    console.log('收到花'+ flower)
  },
  listenGoodMood: function(fn) {
    setTimeout(function() {
      fn()
    }, 1000)
  }
}
xiaoming.sendFlower(B)

Сцены

  • Делегат события элемента HTML
<ul id="ul">
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>
<script>
  let ul = document.querySelector('#ul');
  ul.addEventListener('click', event => {
    console.log(event.target);
  });
</script>

преимущество

  • Режим прокси может отделить прокси-объект от вызываемого объекта и уменьшить степень связанности системы. Шаблон прокси действует как посредник между клиентом и целевым объектом, который может защитить целевой объект.
  • Прокси-объект может расширить функцию целевого объекта, это можно сделать, модифицируя прокси-объект, который соответствует принципу открытия и закрытия;

недостаток

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

разница

Шаблон декоратора аналогичен шаблону прокси в реализации.

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

Внешний вид Режим

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

  1. Совместимые привязки событий браузера
let addMyEvent = function (el, ev, fn) {
    if (el.addEventListener) {
        el.addEventListener(ev, fn, false)
    } else if (el.attachEvent) {
        el.attachEvent('on' + ev, fn)
    } else {
        el['on' + ev] = fn
    }
}; 
  1. интерфейс пакета
let myEvent = {
    // ...
    stop: e => {
        e.stopPropagation();
        e.preventDefault();
    }
};

Сцены

  • На ранней стадии проектирования следует сознательно разделить два разных уровня, например, классическую трехуровневую структуру, и должен быть установлен фасад между уровнем доступа к данным и уровнем бизнес-логики, уровнем бизнес-логики и уровнем представления.
  • На этапе разработки подсистемы часто становятся все более и более сложными из-за непрерывного рефакторинга и эволюции.Добавление фасадов может обеспечить простой интерфейс и уменьшить зависимости между ними.
  • При обслуживании устаревшей крупномасштабной системы обслуживание системы может быть затруднено.В настоящее время также очень уместно использовать фасад фасада.Разработайте класс фасада фасада для системы, чтобы обеспечить более четкий дизайн для грубых и сильно сложный устаревший код Интерфейсы, которые позволяют новым системам взаимодействовать с объектами Facade, выполняющими всю сложную работу по взаимодействию с устаревшим кодом.

Ссылка: Шаблоны дизайна Dahua

преимущество

  • Уменьшить взаимозависимость системы.
  • Повышение гибкости.
  • улучшенная безопасность

недостаток

  • Это не соответствует принципу открытого-закрытого, если очень хлопотно что-то изменить, наследование и переписывание не подходят.

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

Определена связь "один ко многим", позволяющая нескольким объектам-наблюдателям одновременно отслеживать определенный объект-субъект.При изменении состояния объекта-субъекта все объекты-наблюдатели будут уведомлены, чтобы они могли автоматически обновлять себя. объект Шаблон наблюдателя следует учитывать, когда изменения должны одновременно изменить другие объекты, и он не знает, сколько объектов нужно изменить.

  • Опубликовать и подписаться
  • один ко многим
// 主题 保存状态,状态变化之后触发所有观察者对象
class Subject {
  constructor() {
    this.state = 0
    this.observers = []
  }
  getState() {
    return this.state
  }
  setState(state) {
    this.state = state
    this.notifyAllObservers()
  }
  notifyAllObservers() {
    this.observers.forEach(observer => {
      observer.update()
    })
  }
  attach(observer) {
    this.observers.push(observer)
  }
}

// 观察者
class Observer {
  constructor(name, subject) {
    this.name = name
    this.subject = subject
    this.subject.attach(this)
  }
  update() {
    console.log(`${this.name} update, state: ${this.subject.getState()}`)
  }
}

// 测试
let s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('02', s)

s.setState(12)

Сцены

  • DOM-события
document.body.addEventListener('click', function() {
    console.log('hello world!');
});
document.body.click()
  • vue отзывчивый

преимущество

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

недостаток

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


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

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

// 状态 (弱光、强光、关灯)
class State {
    constructor(state) {
        this.state = state
    }
    handle(context) {
        console.log(`this is ${this.state} light`)
        context.setState(this)
    }
}
class Context {
    constructor() {
        this.state = null
    }
    getState() {
        return this.state
    }
    setState(state) {
        this.state = state
    }
}
// test 
let context = new Context()
let weak = new State('weak')
let strong = new State('strong')
let off = new State('off')

// 弱光
weak.handle(context)
console.log(context.getState())

// 强光
strong.handle(context)
console.log(context.getState())

// 关闭
off.handle(context)
console.log(context.getState())

Сцены

  • Поведение объекта зависит от его состояния, и он должен изменять свое поведение во время выполнения в зависимости от состояния.
  • Операция содержит большое количество операторов ветвления, и эти операторы ветвления зависят от состояния объекта.

преимущество

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

недостаток

  • В системе определено множество классов состояний
  • логическая дисперсия

шаблон итератора

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

class Iterator {
    constructor(conatiner) {
        this.list = conatiner.list
        this.index = 0
    }
    next() {
        if (this.hasNext()) {
            return this.list[this.index++]
        }
        return null
    }
    hasNext() {
        if (this.index >= this.list.length) {
            return false
        }
        return true
    }
}

class Container {
    constructor(list) {
        this.list = list
    }
    getIterator() {
        return new Iterator(this)
    }
}

// 测试代码
let container = new Container([1, 2, 3, 4, 5])
let iterator = container.getIterator()
while(iterator.hasNext()) {
  console.log(iterator.next())
}

Пример сценария

  • Array.prototype.forEach
  • $.each() в jQuery
  • ES6 Iterator

Функции

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

Суммировать

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


режим моста

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

class Color {
    constructor(name){
        this.name = name
    }
}
class Shape {
    constructor(name,color){
        this.name = name
        this.color = color 
    }
    draw(){
        console.log(`${this.color.name} ${this.name}`)
    }
}

//测试
let red = new Color('red')
let yellow = new Color('yellow')
let circle = new Shape('circle', red)
circle.draw()
let triangle = new Shape('triangle', yellow)
triangle.draw()

преимущество

  • Помогает управлять компонентами независимо, отделяя абстракцию от реализации
  • Улучшить масштабируемость

недостаток

  • Большое количество классов приведет к увеличению стоимости разработки и, возможно, снижению производительности.

Комбинированный режим

  • Группирует объекты в древовидные структуры для представления иерархий целых частей.
  • Благодаря полиморфному представлению объектов пользователи могут использовать единый объект и составной объект согласованным образом.
class TrainOrder {
	create () {
		console.log('创建火车票订单')
	}
}
class HotelOrder {
	create () {
		console.log('创建酒店订单')
	}
}

class TotalOrder {
	constructor () {
		this.orderList = []
	}
	addOrder (order) {
		this.orderList.push(order)
		return this
	}
	create () {
		this.orderList.forEach(item => {
			item.create()
		})
		return this
	}
}
// 可以在购票网站买车票同时也订房间
let train = new TrainOrder()
let hotel = new HotelOrder()
let total = new TotalOrder()
total.addOrder(train).addOrder(hotel).create()

Сцены

  • Представление объектов — общая иерархия
  • Есть надежда, что пользователь проигнорирует разницу между составным объектом и отдельным объектом и будет использовать все объекты (методы) составной структуры единообразно.

недостаток

Если с помощью шаблона композиции создается слишком много объектов, эти объекты могут перегрузить систему.


режим прототипа

Шаблон прототипа (прототип) относится к использованию экземпляров прототипа для указания на тип созданных объектов и созданию новых объектов путем копирования этих прототипов.

class Person {
  constructor(name) {
    this.name = name
  }
  getName() {
    return this.name
  }
}
class Student extends Person {
  constructor(name) {
    super(name)
  }
  sayHello() {
    console.log(`Hello, My name is ${this.name}`)
  }
}

let student = new Student("xiaoming")
student.sayHello()

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


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

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

<html>
<head>
    <title>策略模式-校验表单</title>
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
</head>
<body>
    <form id = "registerForm" method="post" action="http://xxxx.com/api/register">
        用户名:<input type="text" name="userName">
        密码:<input type="text" name="password">
        手机号码:<input type="text" name="phoneNumber">
        <button type="submit">提交</button>
    </form>
    <script type="text/javascript">
        // 策略对象
        const strategies = {
          isNoEmpty: function (value, errorMsg) {
            if (value === '') {
              return errorMsg;
            }
          },
          isNoSpace: function (value, errorMsg) {
            if (value.trim() === '') {
              return errorMsg;
            }
          },
          minLength: function (value, length, errorMsg) {
            if (value.trim().length < length) {
              return errorMsg;
            }
          },
          maxLength: function (value, length, errorMsg) {
            if (value.length > length) {
              return errorMsg;
            }
          },
          isMobile: function (value, errorMsg) {
            if (!/^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|17[7]|18[0|1|2|3|5|6|7|8|9])\d{8}$/.test(value)) {
              return errorMsg;
            }                
          }
        }
        
        // 验证类
        class Validator {
          constructor() {
            this.cache = []
          }
          add(dom, rules) {
            for(let i = 0, rule; rule = rules[i++];) {
              let strategyAry = rule.strategy.split(':')
              let errorMsg = rule.errorMsg
              this.cache.push(() => {
                let strategy = strategyAry.shift()
                strategyAry.unshift(dom.value)
                strategyAry.push(errorMsg)
                return strategies[strategy].apply(dom, strategyAry)
              })
            }
          }
          start() {
            for(let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
              let errorMsg = validatorFunc()
              if (errorMsg) {
                return errorMsg
              }
            }
          }
        }

        // 调用代码
        let registerForm = document.getElementById('registerForm')

        let validataFunc = function() {
          let validator = new Validator()
          validator.add(registerForm.userName, [{
            strategy: 'isNoEmpty',
            errorMsg: '用户名不可为空'
          }, {
            strategy: 'isNoSpace',
            errorMsg: '不允许以空白字符命名'
          }, {
            strategy: 'minLength:2',
            errorMsg: '用户名长度不能小于2位'
          }])
          validator.add(registerForm.password, [ {
            strategy: 'minLength:6',
            errorMsg: '密码长度不能小于6位'
          }])
          validator.add(registerForm.phoneNumber, [{
            strategy: 'isMobile',
            errorMsg: '请输入正确的手机号码格式'
          }])
          return validator.start()
        }

        registerForm.onsubmit = function() {
          let errorMsg = validataFunc()
          if (errorMsg) {
            alert(errorMsg)
            return false
          }
        }
    </script>
</body>
</html>

Пример сценария

  • Если в системе много классов, отличающихся только своим «поведением», то использование шаблона стратегии может динамически позволить объекту выбирать одно поведение из многих.
  • Системе необходимо динамически выбирать один из нескольких алгоритмов.
  • проверка формы

преимущество

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

недостаток

  • В программу будет добавлено множество классов политик или объектов политик.
  • Чтобы использовать режим стратегии, вы должны понимать все стратегии, и вы должны понимать различия между каждой стратегией, чтобы вы могли выбрать подходящую стратегию.

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

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

let examCarNum = 0         // 驾考车总数
/* 驾考车对象 */
class ExamCar {
    constructor(carType) {
        examCarNum++
        this.carId = examCarNum
        this.carType = carType ? '手动档' : '自动档'
        this.usingState = false    // 是否正在使用
    }

    /* 在本车上考试 */
    examine(candidateId) {
        return new Promise((resolve => {
            this.usingState = true
            console.log(`考生- ${ candidateId } 开始在${ this.carType }驾考车- ${ this.carId } 上考试`)
            setTimeout(() => {
                this.usingState = false
                console.log(`%c考生- ${ candidateId } 在${ this.carType }驾考车- ${ this.carId } 上考试完毕`, 'color:#f40')
                resolve()                       // 0~2秒后考试完毕
            }, Math.random() * 2000)
        }))
    }
}

/* 手动档汽车对象池 */
ManualExamCarPool = {
    _pool: [],                  // 驾考车对象池
    _candidateQueue: [],        // 考生队列

    /* 注册考生 ID 列表 */
    registCandidates(candidateList) {
        candidateList.forEach(candidateId => this.registCandidate(candidateId))
    },

    /* 注册手动档考生 */
    registCandidate(candidateId) {
        const examCar = this.getManualExamCar()    // 找一个未被占用的手动档驾考车
        if (examCar) {
            examCar.examine(candidateId)           // 开始考试,考完了让队列中的下一个考生开始考试
              .then(() => {
                  const nextCandidateId = this._candidateQueue.length && this._candidateQueue.shift()
                  nextCandidateId && this.registCandidate(nextCandidateId)
              })
        } else this._candidateQueue.push(candidateId)
    },

    /* 注册手动档车 */
    initManualExamCar(manualExamCarNum) {
        for (let i = 1; i <= manualExamCarNum; i++) {
            this._pool.push(new ExamCar(true))
        }
    },

    /* 获取状态为未被占用的手动档车 */
    getManualExamCar() {
        return this._pool.find(car => !car.usingState)
    }
}

ManualExamCarPool.initManualExamCar(3)          // 一共有3个驾考车
ManualExamCarPool.registCandidates([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])  // 10个考生来考试

Пример сценария

  • Когда для загрузки файла необходимо создать несколько экземпляров файла
  • Если приложение использует большое количество объектов, и это большое количество объектов вызывает большие накладные расходы на хранилище, ему следует рассмотреть возможность использования шаблона Flyweight.

преимущество

  • Создание объектов значительно сокращается, память системы уменьшается, а эффективность повышается.

недостаток

  • Сложность системы увеличивается, внешнее состояние и внутреннее состояние необходимо разделить, а внешнее состояние имеет присущие ему свойства,

Не должен меняться при изменении внутреннего состояния, иначе это вызовет путаницу в системе

Шаблон метода шаблона

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

class Beverage {
    constructor({brewDrink, addCondiment}) {
        this.brewDrink = brewDrink
        this.addCondiment = addCondiment
    }
    /* 烧开水,共用方法 */
    boilWater() { console.log('水已经煮沸=== 共用') }
    /* 倒杯子里,共用方法 */
    pourCup() { console.log('倒进杯子里===共用') }
    /* 模板方法 */
    init() {
        this.boilWater()
        this.brewDrink()
        this.pourCup()
        this.addCondiment()
    }
}
/* 咖啡 */
const coffee = new Beverage({
     /* 冲泡咖啡,覆盖抽象方法 */
     brewDrink: function() { console.log('冲泡咖啡') },
     /* 加调味品,覆盖抽象方法 */
     addCondiment: function() { console.log('加点奶和糖') }
})
coffee.init() 

Пример сценария

  • Реализуйте неизменяемые части алгоритма один раз и оставьте изменяемое поведение подклассам.
  • Общее поведение в подклассах должно быть выделено и сосредоточено в общем суперклассе, чтобы избежать дублирования кода.

преимущество

  • Общие части кода извлекаются для простоты обслуживания

недостаток

  • Повышенная сложность системы, в основном из-за увеличения количества абстрактных классов и межклассовых связей.

Схема цепочки ответственности

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

// 请假审批,需要组长审批、经理审批、总监审批
class Action {
    constructor(name) {
        this.name = name
        this.nextAction = null
    }
    setNextAction(action) {
        this.nextAction = action
    }
    handle() {
        console.log( `${this.name} 审批`)
        if (this.nextAction != null) {
            this.nextAction.handle()
        }
    }
}

let a1 = new Action("组长")
let a2 = new Action("经理")
let a3 = new Action("总监")
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()

Пример сценария

  • Всплывание событий в JS
  • цепочка прицелов
  • Сеть прототипов

преимущество

  • Уменьшить сцепление. Он разделяет отправителя и получателя запросов.
  • Объекты упрощены. чтобы объекту не нужно было знать структуру цепочки
  • Повышает гибкость при назначении обязанностей объектам. Позволяет динамически добавлять или удалять обязанности, меняя участников в цепочке или переупорядочивая их.
  • Удобно добавлять новые классы обработки запросов.

недостаток

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

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

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

// 接收者类
class Receiver {
    execute() {
      console.log('接收者执行请求')
    }
  }
  
// 命令者
class Command {  
    constructor(receiver) {
        this.receiver = receiver
    }
    execute () {    
        console.log('命令');
        this.receiver.execute()
    }
}
// 触发者
class Invoker {   
    constructor(command) {
        this.command = command
    }
    invoke() {   
        console.log('开始')
        this.command.execute()
    }
}
  
// 仓库
const warehouse = new Receiver();   
// 订单    
const order = new Command(warehouse);  
// 客户
const client = new Invoker(order);      
client.invoke()

преимущество

  • Инкапсулирует команды, что упрощает их расширение и изменение.
  • Отправитель команды и получатель отделены друг от друга, поэтому отправитель может выполнить команду, не зная конкретного процесса выполнения команды.

недостаток

  • Использование командного режима может привести к тому, что в некоторых системах будет слишком много конкретных классов команд.

режим заметки

Не нарушая инкапсуляции, зафиксируйте внутреннее состояние объекта и сохраните это состояние вне объекта. Это позволяет позже восстановить объект в сохраненном состоянии.

//备忘类
class Memento{
    constructor(content){
        this.content = content
    }
    getContent(){
        return this.content
    }
}
// 备忘列表
class CareTaker {
    constructor(){
        this.list = []
    }
    add(memento){
        this.list.push(memento)
    }
    get(index){
        return this.list[index]
    }
}
// 编辑器
class Editor {
    constructor(){
        this.content = null
    }
    setContent(content){
        this.content = content
    }
    getContent(){
     return this.content
    }
    saveContentToMemento(){
        return new Memento(this.content)
    }
    getContentFromMemento(memento){
        this.content = memento.getContent()
    }
}

//测试代码

let editor = new Editor()
let careTaker = new CareTaker()

editor.setContent('111')
editor.setContent('222')
careTaker.add(editor.saveContentToMemento())
editor.setContent('333')
careTaker.add(editor.saveContentToMemento())
editor.setContent('444')

console.log(editor.getContent()) //444
editor.getContentFromMemento(careTaker.get(1))
console.log(editor.getContent()) //333

editor.getContentFromMemento(careTaker.get(0))
console.log(editor.getContent()) //222

Пример сценария

  • управление разбиением на страницы
  • компонент отмены

преимущество

  • Предоставляет пользователям механизм восстановления состояния, позволяя пользователям легко вернуться к определенному историческому состоянию.

недостаток

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

модель посредника

Освободите тесную связь между объектами. После добавления объекта-посредника все связанные объекты взаимодействуют через объект-посредник, а не ссылаются друг на друга, поэтому при изменении объекта необходимо уведомлять только объект-посредник. Медиаторы отделяют объекты друг от друга и могут независимо изменять их взаимодействие. посредник Шаблон превращает веб-отношение «многие ко многим» в относительно простое отношение «один ко многим» (похожее на шаблон Observer, но однонаправленное и единообразно управляемое посредником).

class A {
    constructor() {
        this.number = 0
    }
    setNumber(num, m) {
        this.number = num
        if (m) {
            m.setB()
        }
    }
}
class B {
    constructor() {
        this.number = 0
    }
    setNumber(num, m) {
        this.number = num
        if (m) {
            m.setA()
        }
    }
}
class Mediator {
    constructor(a, b) {
        this.a = a
        this.b = b
    }
    setA() {
        let number = this.b.number
        this.a.setNumber(number * 10)
    }
    setB() {
        let number = this.a.number
        this.b.setNumber(number / 10)
    }
}

let a = new A()
let b = new B()
let m = new Mediator(a, b)
a.setNumber(10, m)
console.log(a.number, b.number)
b.setNumber(10, m)
console.log(a.number, b.number)

Пример сценария

  • Между объектами в системе существует сложная ссылочная связь, в результате чего получается хаотичная структура зависимостей между ними и затрудняется повторное использование объекта
  • Хотите инкапсулировать поведение в нескольких классах через промежуточный класс, не создавая слишком много подклассов.

преимущество

  • Сделайте связь между объектами слабой, а взаимодействие между ними можно будет изменить независимо друг от друга.
  • Связь «один ко многим» между посредниками и объектами заменяет ячеистую связь «многие ко многим» между объектами.
  • Если сложность связи между объектами затрудняет обслуживание, а связь быстро увеличивается с изменениями проекта, требуются посредники для рефакторинга кода.

недостаток

  • В систему будет добавлен новый объект-посредник, потому что сложность взаимодействия между объектами переносится на сложность объекта-посредника, делая объект-посредник часто огромным. Сам объект-посредник часто сложно поддерживать.

Режим переводчика

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

Этот пример исходит изБлог о сердечном загаре

class Context {
    constructor() {
      this._list = []; // 存放 终结符表达式
      this._sum = 0; // 存放 非终结符表达式(运算结果)
    }
  
    get sum() {
      return this._sum;
    }
    set sum(newValue) {
      this._sum = newValue;
    }
    add(expression) {
      this._list.push(expression);
    }
    get list() {
      return [...this._list];
    }
  }
  
  class PlusExpression {
    interpret(context) {
      if (!(context instanceof Context)) {
        throw new Error("TypeError");
      }
      context.sum = ++context.sum;
    }
  }
  class MinusExpression {
    interpret(context) {
      if (!(context instanceof Context)) {
        throw new Error("TypeError");
      }
      context.sum = --context.sum;
    }
  }
  
  /** 以下是测试代码 **/
  const context = new Context();
  
  // 依次添加: 加法 | 加法 | 减法 表达式
  context.add(new PlusExpression());
  context.add(new PlusExpression());
  context.add(new MinusExpression());
  
  // 依次执行: 加法 | 加法 | 减法 表达式
  context.list.forEach(expression => expression.interpret(context));
  console.log(context.sum);

преимущество

  • Легко изменить и расширить грамматику.
  • Поскольку классы используются в режиме интерпретатора для представления правил грамматики языка, грамматика может быть изменена или расширена с помощью таких механизмов, как наследование.

недостаток

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

шаблон посетителя

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

// 访问者  
class Visitor {
    constructor() {}
    visitConcreteElement(ConcreteElement) {
        ConcreteElement.operation()
    }
}
// 元素类  
class ConcreteElement{
    constructor() {
    }
    operation() {
       console.log("ConcreteElement.operation invoked");  
    }
    accept(visitor) {
        visitor.visitConcreteElement(this)
    }
}
// client
let visitor = new Visitor()
let element = new ConcreteElement()
element.accept(visitor)

Пример сценария

  • Класс, соответствующий объекту в структуре объекта, меняется редко, но часто бывает необходимо определить новые операции над этой структурой объекта.
  • Вам нужно выполнять много разных и несвязанных операций над объектами в структуре объекта, и вам нужно избегать того, чтобы эти операции «загрязняли» классы этих объектов, и вы не хотите изменять эти классы при добавлении новых операций.

преимущество

  • Соблюдайте принцип единой ответственности
  • Отличная масштабируемость
  • гибкость

недостаток

  • Публикация сведений об определенных элементах для посетителей нарушает принцип Деметры.
  • Нарушение принципа инверсии зависимостей, полагаясь на конкретные классы, а не на абстракции.
  • Сложнее изменить отдельные элементы

Сюнтай, если тебе это поможет, поставь лайк

использованная литература