Код написать легко, но сложно написать элегантный код. Написание кода, который легко поддерживать, легко расширять и иметь четкую структуру, должно быть целью каждого разработчика. Изучение шаблонов проектирования и их разумное использование может сделать нас ближе к этой цели дальше. Недавно я прочитал книгу "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 есть понятие двух состояний — внутреннего и внешнего.
Внутреннее состояние хранится внутри объекта и может разделяться некоторыми объектами, независимо от конкретной сцены, и обычно не меняется
Внешние изменения состояния в соответствии со сценой
Объект, лишенный внешнего состояния, становится общим объектом, а внешнее состояние передается общему объекту, когда это необходимо для формирования полного объекта.
Несколько шагов для использования паттерна легковеса:
Описано на примере загрузки файла в книге
-
Разделите внешнее состояние
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)