Краткое изложение шаблонов проектирования JavaScript

JavaScript

Я уже читал книгу "JavaScript Design Patterns and Development Practice", и у меня также есть определенное понимание шаблонов проектирования и некоторых связанных с ними случаев в книге.В то же время применение этих шаблонов проектирования соответствует некоторым другим Проекты. Упорядочено, следующее только для справки:

Дополнение: Если что-то не так со следующим содержанием, пожалуйста, поправьте меня.

Назначение шаблона проектирования

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

Шесть принципов дизайна

1) Принцип единой ответственности
2) принцип подстановки Лискова
3) Принцип инверсии зависимости
4) Принцип изоляции интерфейса
5) Принцип наименьшего знания (Закон Деметры)
6) Принцип открытого и закрытого

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

Вообще говоря, шаблоны проектирования делятся на три категории:

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

Существует семь структурных шаблонов: шаблон адаптера, шаблон декоратора, шаблон прокси, шаблон внешнего вида, шаблон моста, шаблон композиции и шаблон легковеса.

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

На самом деле существует два типа: параллельный режим и режим пула потоков.

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

Шаблоны проектирования в JS

Общие шаблоны проектирования:

1. Заводской режим

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

    // 某个需要创建的具体对象
    class Product {
        constructor (name) {
            this.name = name;
        }
        init () {}
    }
    // 工厂对象
    class Creator {
        create (name) {
            return new Product(name);
        }
    }
    const creator = new Creator();
    const p = creator.create(); // 通过工厂对象创建出来的具体对象

Сценарии применения: $ в JQuery, асинхронные компоненты Vue.component, React.createElement и т. д.

2. Одноэлементный режим

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

    // 单例对象
    class SingleObject {
        login () {}
    }
    // 访问方法
    SingleObject.getInstance = (function () {
        let instance;
        return function () {
            if (!instance) {
                instance = new SingleObject();
            }
            return instance;
        }
    })()
    const obj1 = SingleObject.getInstance();
    const obj2 = SingleObject.getInstance();
    console.log(obj1 === obj2); // true

Сценарии применения: $ в JQuery, Store в Vuex, Store в Redux и т. д.

3. Режим адаптера

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

    class Adapter {
        specificRequest () {
            return '德国标准插头';
        }
    }
    // 适配器对象,对原来不兼容对象进行包装处理
    class Target {
        constructor () {
            this.adapter = new Adapter();
        }
        request () {
            const info = this.adapter.specificRequest();
            console.log(`${info} - 转换器 - 中国标准插头`)
        }
    }
    const target = new Target();
    console.log(target.request()); // 德国标准插头 - 转换器 - 中国标准插头

Сценарии применения: вычисление Vue, преобразование старого формата JSON в новый формат и т. д.

4. Режим декоратора

На основе неизменения самого объекта динамически добавлять новые функции к объекту без изменения его интерфейса

    class Plane {
        fire () {
            console.log('发送普通子弹');
        }
    }
    // 装饰过的对象
    class Missile {
        constructor (plane) {
            this.plane = plane;
        }
        fire () {
            this.plane.fire();
            console.log('发射导弹');
        }
    }
    let plane = new Plane();
    plane = new Missile(plane);
    console.log(plane.fire()); // 依次打印 发送普通子弹 发射导弹

Используйте АОП для динамического добавления функций к функциям, то есть после или до функции.

Function.prototype.before = function (beforeFn) {
  const _self = this;
  return function () {
    beforeFn.apply(this, arguments);
    return _self.apply(this, arguments);
  }
}

Function.prototype.after = function (afterFn) {
  const _self = this;
  return function () {
    const ret = _self.apply(this, arguments);
    afterFn.apply(this, arguments);
    return ret;
  }
}

let func = function () {
  console.log('2');
}

func = func.before(function() {
  console.log('1');
}).after(function() {
  console.log('3');
})

func();
console.log(func()); // 依次打印 1 2 3

Сценарии применения: декораторы ES7, когда версия 1.0 в Vuex подмешана во Vue, переписать метод init, реализовать метод мутации массива во Vue и т.д.

5. Прокси-режим

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

class Flower {}
// 源对象
class Jack {
    constructor (target) {
        this.target = target;
    }
    sendFlower (target) {
        const flower = new Flower();
        this.target.receiveFlower(flower)
    }
}
// 目标对象
class Rose {
    receiveFlower (flower) {
        console.log('收到花: ' + flower)
    }
}
// 代理对象
class ProxyObj {
    constructor () {
        this.target = new Rose();
    }
    receiveFlower (flower) {
        this.sendFlower(flower)
    }
    sendFlower (flower) {
        this.target.receiveFlower(flower)
    }
}
const proxyObj = new ProxyObj();
const jack = new Jack(proxyObj);
jack.sendFlower(proxyObj); // 收到花:[object Object]

Сценарии применения: ES6 Proxy, доступ к геттерам в Vuex, предварительная загрузка изображений и т. д.

6. Режим внешнего вида

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

 class A {
    eat () {}
}
class  B {
    eat () {}
}
class C {
    eat () {
        const a = new A();
        const b = new B();
        a.eat();
        b.eat();
    }
}
// 跨浏览器事件侦听器
function addEvent(el, type, fn) {
    if (window.addEventListener) {
        el.addEventListener(type, fn, false);
    } else if (window.attachEvent) {
        el.attachEvent('on' + type, fn);
    } else {
        el['on' + type] = fn;
    }
}

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

7. Режим наблюдателя

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

   class Subject {
  constructor () {
    this.state = 0;
    this.observers = [];
  }
  getState () {
    return this.state;
  }
  setState (state) {
    this.state = state;
    this.notify();
  }
  notify () {
    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 sub = new Subject();
let observer1 = new Observer('o1', sub);
let observer2 = new Observer('o2', sub);

sub.setState(1);

Разница между режимом наблюдателя и режимом публикации/подписки: существенная разница заключается в разнице в планировании.

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

--- Шаблон наблюдателя: цель и наблюдатель являются базовыми классами, цель предоставляет ряд методов для поддержки наблюдателя, а наблюдатель предоставляет интерфейс обновления. Конкретный наблюдатель и конкретная цель наследуют свои соответствующие базовые классы, а затем конкретный наблюдатель регистрируется в конкретной цели, и когда конкретная цель изменяется, отправляется метод обновления наблюдателя.
Например, есть конкретная цель A «метеоцентра», которая специально следит за изменениями погоды, и наблюдатель B, у которого есть интерфейс, отображающий погоду, B регистрирует себя в A, и когда A инициирует изменение погоды, он составляет расписание. Метод обновления B. и привнесите свой собственный контекст.

---Режим публикации/подписки: подписчик регистрирует событие, на которое он хочет подписаться, в диспетчерском центре.Когда событие срабатывает, издатель публикует событие в диспетчерском центре (кстати, контекст), и диспетчерский центр отправляет абоненту зарегистрироваться в диспетчерском центре код центральной обработки.
Например, если есть интерфейс, отображающий погоду в режиме реального времени, он подписывается на погодные события (регистрируется в диспетчерском центре, включая обработчик), а при изменении погоды (регулярно получает данные) выступает издателем для публиковать информацию о погоде в диспетчерский центр, а диспетчерский центр отправляет обработчик погоды абонента.

Сценарии приложений: события JS, JS Promise, JQuery.$CallBack, Vue watch, пользовательские события NodeJS, файловые потоки и т. д.

8. Режим итератора

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

Можно разделить на: внутренний итератор и внешний итератор

Внутренний итератор: правила итерации определены внутри, а внешний нужно вызывать только один раз.

const each = (args, fn) => {
  for (let i = 0, len = args.length; i < len; i++) {
    const value = fn(args[i], i, args);

    if (value === false) break;
  }
}

Сценарий приложения: метод JQuery.each

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

// 迭代器
class Iterator {
  constructor (list) {
    this.list = 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;
  }
}
const arr = [1, 2, 3, 4, 5, 6];
const ite = new Iterator();

while(ite.hasNext()) {
  console.log(ite.next()); // 依次打印 1 2 3 4 5 6
}

Сценарии применения: JS Iterator, JS Generator

9. Режим статуса

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

// 红灯
class RedLight {
    constructor (state) {
        this.state = state;
    }
    light () {
        console.log('turn to red light');
        this.state.setState(this.state.greenLight)
    }
}
// 绿灯
class greenLight {
    constructor (state) {
        this.state = state;
    }
    light () {
        console.log('turn to green light');
        this.state.setState(this.state.yellowLight)
    }
}
// 黄灯
class yellowLight {
    constructor (state) {
        this.state = state;
    }
    light () {
        console.log('turn to yellow light');
        this.state.setState(this.state.redLight)
    }
}
class State {
    constructor () {
        this.redLight = new RedLight(this)
        this.greenLight = new greenLight(this)
        this.yellowLight = new yellowLight(this)
        this.setState(this.redLight) // 初始化为红灯
    }
    setState (state) {
        this.currState = state;
    }
}
const state = new State();
state.currState.light() // turn to red light
setInterval(() => {
    state.currState.light() // 每隔3秒依次打印红灯、绿灯、黄灯
}, 3000)

Сценарии применения: состояние лампочки, переключение светофора и т.д.

Другие шаблоны дизайна:

10. Командный режим
11. Комбинированный режим
12. Режим наилегчайшего веса
13. Режим стратегии
14. Модель цепочки ответственности
15. Шаблон шаблонного метода
16. Промежуточная модель
17. Режим заметок
18. Режим посетителя
19. Режим переводчика
20. Режим моста

Пожалуйста, перейдите к другим шаблонам проектирования:github.com/jefferyE
Для получения дополнительных шаблонов проектирования см.:www.runoob.com