JavaScript-设计模式

11次阅读

共计 6306 个字符,预计需要花费 16 分钟才能阅读完成。

作者:gauseen

单例模式(Singleton Pattern

描述:

只对外暴露一个对象

// 只创建一个实例

const mySingleton = (function () {
  let instance = null
  // 初始化方法
  let init = () => {console.log('init logic')

    return {someThing () {alert('hello world')
      },
    }
  }

  return {getInstance () {
      // 不存在 instance 创建新的,已存在则直接返回(只创建一次)if (!instance) {instance = init()
      }
      return instance
    },
  }
})()
// 每次都创建一个新的实例

const myBadSingleton = (function () {
  let instance = null
  // 初始化方法
  let init = () => {console.log('init logic')

    return {someThing () {alert('hello world')
      },
    }
  }

  return {getInstance () {
      // 每次都创建一个新的实例(创建多次)instance = init()
      return instance
    },
  }
})()

观察者模式(Observer Pattern

真实场景:

以公众号为例,有些人订阅(关注)了某公众号,当公众号发布新的消息时,订阅者就会收到该消息的推送

描述:

定义对象之间的依赖关系,每当对象更改状态时,都会通知所有依赖项

// 观察者
class Observer {constructor (name) {this.name = name}
  // 每个观察者都有一个 smile 事件
  update () {console.log(`${this.name} is updated`)
  }
}

// 主体
class Subject {constructor () {
    // 观察者列表
    this.observers = []}
  // 添加观察者到主体的观察者列表
  subscribe (observer) {this.observers.push(observer)
  }
  // 通知所有观察者
  notify () {
    this.observers.forEach(observerItem => {observerItem.update()
    })
  }
}

// 创建主体实例
const subject = new Subject()

// 有 2 个观察者 observer01、observer02
const observer01 = new Observer('observer_01')
const observer02 = new Observer('observer_02')

// 观察者订阅主体
subject.subscribe(observer01)
subject.subscribe(observer02)

// 通知所有观察者
subject.notify()

发布 - 订阅模式(Publish/Subscribe Pattern

真实场景:

报社将报纸发送给邮局,邮局按照订阅关系将报纸发送给对应的人

描述:

消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者。同样的,订阅者可以订阅一个或多个类别,只接收感兴趣的消息,无需了解哪些发布者

// 订阅者
class Subscriber {constructor (name) {this.name = name}
  // 每个观察者都有一个 smile 事件
  update () {console.log(`${this.name} is updated`)
  }
}

// 调度中心
class EventHub {constructor () {
    // 订阅主题
    this.topics = {}}

  // 订阅
  subscribe (topicType, subscriber) {if (!this.topics[topicType]) {this.topics[topicType] = []}

    this.topics[topicType].push(subscriber)
  }

  // 给对应主题下的订阅者发布内容
  publish (topicType) {let topicList = this.topics[topicType]

    if (!topicList) return

    topicList.forEach(subscriberItem => {subscriberItem.update()
    })
  }
}

// 创建一个“调度中心”let eventHub = new EventHub()

// 创建 3 个订阅者 subscriber01、subscriber02、subscriber03
let subscriber01 = new Subscriber('subscriber_01')
let subscriber02 = new Subscriber('subscriber_02')
let subscriber03 = new Subscriber('subscriber_03')

// 订阅者注册“调度中心”eventHub.subscribe('typeA', subscriber01)
eventHub.subscribe('typeA', subscriber02)
eventHub.subscribe('typeB', subscriber03)

// 发布者对 typeA 主题发布内容
eventHub.publish('typeA')
// subscriber_01 is updated
// subscriber_02 is updated

// 发布者对 typeB 主题发布内容
eventHub.publish('typeA')
// subscriber_03 is updated

观察者模式 vs 发布 - 订阅模式

如上图

  • 观察者模式中,主体(Subject)和观察者(Observer)是强耦合,有直接的联系,相互知道对方的存在。
  • 发布 - 订阅模式中,主体(Subject)和观察者(Observer)是松耦合,没有直接的联系,是通过“事件通道”(调度中心)建立联系,互相不知道对方存在。

奇葩解释:观察者模式,没中间商赚差价;发布 - 订阅模式,有中间商赚差价

工厂模式(Factory Pattern

class CarA {constructor (model, color) {console.log('CarA')
  }
}
class CarB {constructor (model, color) {console.log('CarB')
  }
}

class CarFactory {carA () {return CarA}
  carB () { return CarB}

  create (type) {let CarClass = this[type]()
    return new CarClass()}
}

let carFactory = new CarFactory()

// 通过 carFactory 创建不同的 car
let carA = carFactory.create('carA')
let carB = carFactory.create('carB')

适配器模式(Adapter Pattern

真实场景:

现在好多智能手机都去掉了 3.5mm 的耳机孔,取而代之的是 Type-C 接口耳机。但是你还想在新手机中使用 3.5mm 的老式耳机怎么办?

没错,那就需要用适配器,Type-C3.5mm 的适配器。如下图

描述:

可以将其他不兼容的对象包装在适配器中,使它与另一个类兼容

// Type-C 耳机
class TypecHeadset {typeC () {console.log('Type-C 耳机已插入')
  }
}

// 3.5 毫米耳机
class Dot35mmHeadset {dot35mm () {console.log('3.5mm 耳机已插入')
  }
}

// 适配器(3.5 毫米 --> Type-C)class Dot35mmToTypecAdapter {constructor (dot35mmHeadset) {this.dot35mmHeadset = dot35mmHeadset}

  typeC () {this.dot35mmHeadset.dot35mm()
  }
}

// 手机类(该手机只支持 Type-C 耳机)class Phone {
  // 插入耳机孔
  insertTypeC (headset) {headset.typeC()
  }
}


let phone = new Phone()
// Type-C 耳机
let typecHeadset = new TypecHeadset()

// 3.5 毫米耳机
let dot35mmHeadset = new Dot35mmHeadset()

// 耳机适配器(3.5 毫米 --> Type-C)let dot35mmToTypecAdapter = new Dot35mmToTypecAdapter(dot35mmHeadset)

phone.insertTypeC(typecHeadset) // Type-C 耳机已插入
phone.insertTypeC(dot35mmToTypecAdapter) // 3.5mm 耳机已插入

装饰者模式(Decorator Pattern

真实场景:

先建一个基础版本的手机,经过再加工对手机进行装饰润色,

描述:

装饰者模式,允许你通过将对象包装在装饰器类的对象中,来动态更改对象在运行时的表现行为

// 基础 car 类
class SimpleCar {getCost () {return 10}

  getDescription () {return 'Simple car'}
}

// 奥迪
class AudiCar {constructor (car) {this.car = car}

  getCost () {return this.car.getCost() + 6
  }

  getDescription () {return this.car.getDescription() + ', Audi'
  }
}

// 宝马
class BMWCar {constructor (car) {this.car = car}

  getCost () {return this.car.getCost() + 8
  }

  getDescription () {return this.car.getDescription() + ', BMW'
  }
}

// 基础车信息
let simpleCar = new SimpleCar()
simpleCar.getCost() // 10
simpleCar.getDescription() // Simple car

// 奥迪车信息
let audiCar = new AudiCar(simpleCar)
audiCar.getCost() // 16
audiCar.getDescription() // Simple car, Audi

// 宝马车信息
let bmwCar = new BMWCar(simpleCar)
bmwCar.getCost() // 18
bmwCar.getDescription() // Simple car, BMW

外观模式(Facade Pattern

真实场景:

如何让手机开机?很简单,“长按电源键”即可开机。实际上手机内部处理了很多逻辑才能实现它。这就是所谓的外观模式,将复杂转为简洁。

例如,开发时常用于浏览器兼容性处理的代码。

描述:

为复杂的子系统提供了简化统一的界面

// DOM 绑定事件的兼容性处理
function addEventFacade (el, eve, fn) {if (el.addEventListener) {el.addEventListener(eve, fn, false)
  } else if (el.attachEvent) {el.attachEvent('on' + eve, fn)
  } else {el['on' + eve] = fn
  }
}

代理模式(Proxy Pattern

真实场景:

你可以直接去专卖店买手机(某海外品牌),也可以通过 代购 帮你买手机(这样价格会便宜些)。让 代购 买手机这种方式,就是所谓的 代理模式

js 原生也支持代理模式 Proxy

描述:

用一个对象来表示另一个对象的功能

class Phone {constructor () {
    this.phoneName = 'Mate 90' // 手机名
    this.screen = '6.21' // 尺寸
    this.color = 'black' // 颜色
    this.chip = 'kirin' // 芯片
  }
}

class ProxyPerson {constructor(target) {this.target = target}
  buyPhone (phoneModel) {this.target.buyPhone(phoneModel)
  }
}

class Person {buyPhone (phoneModel) {console.log('哈哈,买到了:', phoneModel.phoneName)
  }
}

let proxyPerson = new ProxyPerson(new Person())
proxyPerson.buyPhone(new Phone()) // 哈哈,买到了: Meta 90

策略模式(Strategy Pattern

真实场景:

商店打折促销,某商品买 1 件原价,28 折,37

描述:

在代码运行时,根据不同情况切换不同的策略(方法)

// 策略组
const strategies = {num1 (price) {return price * 1},
  num2 (price) {return price * 0.8},
  num3 (price) {return price * 0.7},
}

// 获取折后总价
function getDiscountPrice (n, price) {let _key = `num${n}`
  let discountedForItem = strategies[_key](price)
  return discountedForItem * n
}

getDiscountPrice(1, 10) // 10
getDiscountPrice(2, 10) // 16
getDiscountPrice(3, 10) // 21

状态模式(State Pattern

描述:

在状态更改时更改类的行为(方法)

class Discount {constructor () {
    // 当前状态
    this.currentState = ''
    // 所有状态
    this.states = {typeA () {console.log('typeA 1')
      },
      typeB () {console.log('typeB 0.8')
      },
      typeC () {console.log('typeC 0.7')
      },
    }
  }
  // 更新当前状态
  setState (_currentState) {
    this.currentState = _currentState
    return this
  }
  // 开始计算
  compute () {let currentStateHandler = this.states[this.currentState]
    currentStateHandler && currentStateHandler()
    return this
  }
}

let discount = new Discount()

discount
  .setState('typeA')
  .compute() // typeA 1
  .setState('typeC')
  .compute() // typeC 0.7
  .setState('typeB')
  .compute() // typeB 0.8

欢迎关注 无广告文章 公众号:学前端

参考

  • Learning JavaScript Design Patterns
  • javascript-design-patterns-for-humans
  • 观察者模式 vs 发布 - 订阅模式
  • 观察者模式和订阅 - 发布模式的区别
  • 汤姆大叔的博客
正文完
 0

Javascript设计模式

11次阅读

共计 2561 个字符,预计需要花费 7 分钟才能阅读完成。

设计原则 (SOLID)
单一职责模式
李式替换原则
开放封闭原则
接口隔离原则
依赖倒置原则
设计模式
工厂模式

将 new 操作单独封装
遇到 new 时,就要考虑是否该使用工厂模式了

示例
你去购买汉堡,直接点餐、取餐,不会自己亲手做商店要“封装”做汉堡的工作,做好直接给买者
UML 类图:

代码示例:
class Product {
constructor(name) {
this.name = name;
}
init() {
console.log(‘init’)
}
fn1() {
console.log(‘fn1’)
}
fn2() {
console.log(‘fn2’)
}
}

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

let create = new Creator();
let p = create.create(‘p’)
p.init()
p.fn1()
p.fn2()

应用场景
1、jQuery:
$(‘div’) 和 new $(‘div’) 有何区别?

第一:书写麻烦,jQuery 的链式操作将成为噩梦
第二:一旦 jQuery 名字变化,将是灾难性的

// 仿 jQuery 代码
class jQuery {
constructor(selector) {
let slice = Array.prototype.slice;
let dom = slice.call(document.querySelectorAll(selector))
let len = dom ? dom.length : 0
for (let i = 0; i < len; i++) {
this[i] = dom[i]
}
this.length = len
this.selector = selector || ”
}
append() {
console.log(‘append’);
}
addClass() {
console.log(‘addClass’)
}

}

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

var $p = $(‘p’)
console.log($p)
console.log($p.addClass)

2、React.crateElement:
var profile = <div>
<img src=”avater.png” className=”profile”/>
<h3>{[user.firstName,user.lastName].join(”)}</h3>
</div>;
编译完之后:
var profile = React.createElement(“div”,null,
React.createElement(“img”,{src:”avater.png”,className:”profile”}),
React.createElement(“h3″,null,[user.firstName,user.lastName].join(” “))
);
// 源码实现
class vnode(tag, attrs, children) {
//… 省略内部代码 …
}

React.createElement = function(tag,attrs,children){
return new vnode(tag,attrs,children)
}
3、Vue 的异步组件:
Vue.component(‘async-example’, funciton(resolve, reject) {
setTimeout(function() => {
resolve({
template: ‘<div>I am async!</div>’
})
}, 1000);
})
设计原则验证:

构造函数和创建者分离
符合开放封闭原则

单例模式

系统中被唯一使用
一个类中只有一个实例

 
实例:
登录框、购物车
代码演示
java 版的单例模式演示
public class SingleObject{
// 注意:私有化构造函数,外部不能 new,只能内部 new!!!!
private SingleObject(){}
// 唯一被 new 出来的对象
private SingleObject getInstance(){
if(instance == null){
// 只 new 一次
instance = new SingleObject();
}
return instance;
}
// 对象方法
public void login(username,password){
System.out.println(“login…”)
}
}

public class SingletonPatternDemo{
public static void main(String[] args){
// 不合法的构造函数
// 编译时报错:构造函数 SingleObject() 是不可见的!!!
//SingleObject object = new SingleObject();
// 获取唯一可用的对象
SingleObject object = SingleObject.getInstance();
}
}
Javascript 版的单例模式演示
class SingleObject {
login() {
console.log(‘login…’)
}
}

// 静态方法
SingleObject.getInstance = (function() {
let instance
return function() {
if (!instance) {
instance = new SingleObject();
}
return instance;
}
})()

var login = SingleObject.getInstance().login();
javascript 的单例模式缺点:

如果强制 new 也不会报错:
var loginnew = new SingleObject();
loginnew.login()
测试:
// 注意这里只能用静态函数 getInstance,不能 new SingleObject()!!!
let obj1 = SingleObject.getInstance()
obj1.login()
let obj2 = SingleObject.getInstance()
obj2.login()
console.log(obj1 === obj2); // 两者必须完全相同

适配器模式
装饰器模式
代理模式
外观模式
观察者模式
迭代器模式
状态模式
未完待续,每日更新

正文完
 0