时间:2023-10-07 18:18:01 | 来源:网站运营
时间:2023-10-07 18:18:01 来源:网站运营
前端进阶之路:1.5w字整理23种前端设计模式:我们开发人员经常会说:"Talk is cheap, show me the code"。要想写出令人赏心悦目的代码,我觉得是否使用了合理的设计模式起了至关重要的作用。class GetUser { constructor(id) { this.id = id } getInfo() { const params = {id: this.id} //...code here }}class GetVipUser extends GetUser { constructor(id, vipLevel) { super(id) this.id = id this.level = vipLevel } getInfo() { const params = {id: this.id} if (this.level != void 0) { params.level = this.level } super.getInfo(params) }}class Demo { getUser(user) { console.log(user.getInfo()) }}// 里式替换原则const u = new Demo()u.getUser(new GetUser())u.getUser(new GetVipUser())
我们看到GetVipUser的设计是符合里式替换原则的,其可以替换父类出现的任何位置,并且原来代码的逻辑行为不变且正确性也没有被破坏。class GetUser { constructor(id) { this.id = id } getInfo() { const params = {id: this.id} //...code here }}class GetVipUser extends GetUser { constructor(id, vipLevel) { super(id) this.id = id this.level = vipLevel } getInfo() { const params = {id: this.id} if (this.level == void 0) { throw new Error('level should not undefind') } super.getInfo(params) }}class Demo { getUser(user) { console.log(user.getInfo()) }}// 里式替换原则const u = new Demo()u.getUser(new GetUser())u.getUser(new GetVipUser())
改动之后我们可以很清晰的看到,父类在运行时是不会出错的,但是子类当没有接受level的时候回抛出错误,整个程序的逻辑和父类产生了区别,所以是不符合里式替换原则的。//方法一class GetSeetingConfig { static instance = null constructor() { console.log('new') } getConfig() { //... } static getInstance () { if (this.instance == void 0) { this.instance = new GetSeetingConfig() } return this.instance }}const seeting1 = GetSeetingConfig.getInstance()const seeting2 = GetSeetingConfig.getInstance()//两次只打印一次newseeting1 === seeting2 // true//方法二class GetSeetingConfig { constructor() { console.log('new') } getConfig() { //... }}GetSeetingConfig.getInstance = (function() { let instance return function() { if (!instance){ instance = new GetSeetingConfig() } return instance }})()const seeting1 = GetSeetingConfig.getInstance()const seeting2 = GetSeetingConfig.getInstance()//两次只打印一次newseeting1 === seeting2 // true
优点://简单工厂模式class User { constructor(role, name) { this.name = name; this.role = role }}class Admin { constructor(role, name) { this.name = name; this.role = role }}class SuperAdmin { constructor(role, name) { this.name = name; this.role = role }}class RoleFactory { static createUser(role) { if (role === 'user') { return new User(role,'用户') } else if (role === 'admin') { return new Admin(role, '管理员') } else if (role === 'superadmin') { return new SuperAdmin(role, '超级管理员') } }}const user = RoleFactory.createUser('user'')
简单工厂的优点在于,你只需要一个正确的参数,就可以获取到你所需要的对象,而无需知道其创建的具体细节。但是当内部逻辑变得很复杂这个函数将会变得很庞大并且难以维护。class UserFactory { constructor(role, name) { this.name = name; this.role = role; } init() { //我们可以把简单工厂中复杂的代码都拆分到每个具体的类中 // code here //... return new User(this.role, this.name) }}class AdminFactory { constructor(role, name) { this.name = name; this.role = role; } init() { //我们可以把简单工厂中复杂的代码都拆分到每个具体的类中 // code here //... return new Admin(this.role, this.name) }}class SuperAdminFactory { constructor(role, name) { this.name = name; this.role = role; } init() { //我们可以把简单工厂中复杂的代码都拆分到每个具体的类中 // code here //... return new SuperAdmin(this.role, this.name) }}class RoleFactory { static createUser(role) { if (role === 'user') { return new UserFactory(role,'用户') } else if (role === 'admin') { return new AdminFactory(role, '管理员') } else if (role === 'superadmin') { return new SuperAdminFactory(role, '超级管理员') } }}const user = RoleFactory.createUser('user'')
那什么时候该用工厂方法模式,而非简单工厂模式呢?class Factory { createUserParser(){ thorw new Error('抽象类只能继承,不能实现') } createLoginParser(){ thorw new Error('抽象类只能继承,不能实现') }}class UserParser extends Factory { createUserParser(role, name) { return new UserFactory(role, name) } createLoginParser(type) { if (type === 'email'){ return new UserEmail() } else if (type === 'phone') { return new UserPhone() } }}class AdminParser extends Factory { createUserParser(role, name) { return new AdminFactory(role, name) } createLoginParser(type) { if (type === 'email'){ return new AdminEmail() } else if (type === 'phone') { return new AdminPhone() } }}class SuperAdminParser extends Factory { createUserParser(role, name) { return new SuperAdminFactory(role, name) } createLoginParser(type) { if (type === 'email'){ return new SuperAdminEmail() } else if (type === 'phone') { return new SuperAdminPhone() } }}
总结class Cake { constructor(name, color, shape, suger) { this.name = name; this.color = color; this.shape = shape; this.suger = suger; }}new Cake('cake', 'white', 'circle', '30%')
现在,Cake 只有 4 个可配置项,对应到构造函数中,也只有 4 个参数,参数的个数不多。但是,如果可配置项逐渐增多,变成了 8 个、10 个,甚至更多,那继续沿用现在的设计思路,构造函数的参数列表会变得很长,代码在可读性和易用性上都会变差。在使用构造函数的时候,我们就容易搞错各参数的顺序,传递进错误的参数值,导致非常隐蔽的 bug。class Cake { consotructor(name, color) { this.name = name; this.color = color; } validName() { if(this.name == void 0) { console.log('name should not empty') return false } return true } validColor() { if (this.color == void 0) { console.log('color should not empty') true } return true } setShape(shape) { if (this.validName() && this.validColor()) { this.shape = shape; } } setSugar(sugar) { if (this.validName() && this.validColor()) { this.sugar = sugar; } } //...}
至此,我们仍然没有用到建造者模式,通过构造函数设置必填项,通过 set() 方法设置可选配置项,就能实现我们的设计需求。 但是我们再增加一下难度class Cake { constructor(name, color, shape, suger) { this.name = name; this.color = color; this.shape = shape; this.suger = suger; }}class CakeBuilder { valid() { //valid all params... } setName() { this.valid() //... return this; } setColor() { this.valid() //... return this; } setShage() { this.valid() //... return this; } setSuger() { this.valid() //... return this; } build() { const cake = new Cake() cake.shape = this.setShape() cake.suger = this.setSuger() cake.name = this.setName() cake.color = this.setColor() return cake }}const cake1 = new CakeBuilder() .setName('cake') .setColor('yellow') .setShape('heart') .setSugar('70%') .builder()//我们还可以把这长长的链式调用封装起来,也就是指导者function diractor(builder) { return builder .setName('cake') .setColor('yellow') .setShape('heart') .setSugar('70%') .builder()}const cakeBuilder = new CakeBuilder()const cake2 = diractor(cakeBuilder)
Cake类中的成员变量,要在 Builder 类中重新再定义一遍,可以看出,建造者模式的使用有且只适合创建极为复杂的对象。在前端的实际业务中,在没有这类极为复杂的对象的创建时,还是应该直接使用对象字面或工厂模式等方式创建对象。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()
对于前端程序员来说,原型模式是一种比较常用的开发模式。这是因为,有别于 Java、C++ 等基于类的面向对象编程语言,JavaScript 是一种基于原型的面向对象编程语言。即便 JavaScript 现在也引入了类的概念,但它也只是基于原型的语法糖而已。class MyImg { static imgNode = document.createElement("img") constructor(selector) { selector.appendChild(this.imgNode); } setSrc(src) { this.imgNode = src }}const img = new MyImg(document.body)img.setSrc('xxx')
先看一下上面这段代码,定义了一个MyImg类,接收一个选择器,然后在这个选择器下面创建一个img标签并且暴露一个setSrc方法。class ProxyMyImg { static src = 'xxx本地预览图地址loading.gif' constructor(selector) { this.img = new Image this.myImg = new MyImg(selector) this.myImg.setSrc(this.src) } setSrc(src) { this.img.src = src this.img.onload = () => { this.myImg.setSrc(src) } }}const img = new ProxyMyImg(document.body)img.setSrc('xxx')
ProxyMyImg控制了客户对MyImg的访问,并且在此过程中加入一些额外的操作,比如在图片加载好之前,先把img节点的src设置为一张本地的loading图片。const mult = (...args) => { console.log('multing...') let res = 1 args.forEach(item => { res*=item }) return item}mult(2,3) //6mult(2,3,5)//30
加入缓存代理函数const mult = (...args) => { console.log('multing...') let res = 1 args.forEach(item => { res*=item }) return res }const proxyMult = (() => { const cache = {} return (...args) => { const key = [].join.call(args, ',') if (key in cache) { return cache[args] } return cache[key] = mult.apply(null, args) }})()proxyMult(1,2,3,4)// multing... 24proxyMult(1,2,3,4)//24
const p = new Proxy(target, handler)
target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
const mult = (args) => { console.log('multing...') let res = 1 args.forEach(item => { res*=item }) return res}const handler = { cache: {}, apply: function(target, thisArg, args) { const key = [].join.call(args, ',') if(key in this.cache) { return this.cache[key] } return this.cache[key] = target(args) }}const proxyMult = new Proxy(mult, handler)proxyMult(1,2,3,4)//multing...//24proxyMult(1,2,3,4)//24
class Plan { fire() { console.log('发射子弹') }}class PlanDecorator { constructor(plan) { this.plan = plan } fire() { this.plan.fire() console.log('发射导弹') }}const plan = new Plan()const newPlan = new PlanDecorator(plan)newPlan.fire() //发射子弹 发射导弹
如果你熟悉TypeScript,那么他的代码结构就是这样的interface IA { init: () => void}class A implements IA { public init() { //... }}class ADecorator implements IA { constructor (a: IA) { this.a = a } init() { // 功能增强代码 a.init() // 功能增强代码 }}
著名的AOP就是通过装饰者模式来实现的Function.prototype.before = function(beforeFn) { const _this = this //保存原函数的引用 return function() {// 返回包含了原函数和新函数的"代理函数" beforeFn.apply(this, arguments)// 执行新函数,修正this return _this.apply(this, arguments) // 执行原函数并返回原函数的执行结果,this不被劫持 }}
afterFunction.prototype.after = function(afterFn) { const _this = this return function() { const res = _this.apply(this, arguments) afterFn.apply(this, arguments) return res }}
around(环绕通知)Function.prototype.around = function(beforeFn, aroundFn) { const _this = this return function () { return _this.before(beforeFn).after(aroundFn).apply(this, arguments)// 利用之前写的before 和after 来实现around }}
测试const log = (val) => { console.log(`日志输出${val}`)}const beforFn = () => { console.log(`日志输出之前先输出${new Date().getTime()}`)}const afterFn = () => { console.log(`日志输出之前再输出${new Date().getTime()}`)}const preLog = log.before(beforFn)const lastLog = log.after(afterFn)const aroundLog = log.around(beforeFn, afterFn)preLog(11)lastLog(22)aroundLog(33)
class User { @checkLogin getUserInfo() { console.log('获取用户信息') }}// 检查用户是否登录function checkLogin(target, name, descriptor) { let method = descriptor.value descriptor.value = function (...args) { // 校验方法,假设这里可以获取到用户名/密码 if (validate(args)) { method.apply(this, args) } else { console.log('没有登录,即将跳转到登录页面...') } }}let user = new User()user.getUserInfo()
还有比较典型的在React中的高阶组件function HOCDecorator(WrappedComponent){ return class HOC extends Component { render(){ const newProps = {param: 'HOC'}; return <div> <WrappedComponent {...this.props} {...newProps}/> </div> } }}@HOCDecoratorclass OriginComponent extends Component { render(){ return <div>{this.props.param}</div> }}
如果你熟悉mobx的话,你会发现里面的功能都支持装饰器,当然我们也可以在Redux中使用装饰器来实现connect函数,这都是很方便的。 具体可以看阮一峰老师的讲解 es6.ruanyifeng.com/#docs/decor…class GooleMap { show() { console.log('渲染地图') }}class BaiduMap { display() { console.log('渲染地图') }}class GaodeMap { show() { console.log('渲染地图') }}// 上面三个类,如果我们用多态的思想去开发的话是很难受的,所以我们通过适配器来统一接口class BaiduAdaapterMap { show() { return new BaiduMap().display() }}
以适配器模式应用场景一般为class Car { constructor(brand) { this.brand = brand } speed() { //... }}class Transmission { constructor(trans) { this.trans = trans } action() { ... }}class AbstractCar { constructor(car, transmission) { this.car = car this.transmission } run () { this.car.speed() this.traansmission.action() //... }}
门面模式const myEvent = { // ... stop: e => { e.stopPropagation(); e.preventDefault(); }}
合模式class FileSystemNode { constructor(path) { this.path = path } countNumOfFiles() {} countSizeOfFiles() {} getPath() { return this.path }}class File extends FileSystemNode { constructor(path) { super(path) } countNumOfFiles () { return 1 } countSizeOfFiles() { //利用NodejsApi通过路径获取文件... }}class Directory extends FileSystemNode{ constructor(path) { super(path) this.fileList = [] } countNumOfFiles () { //... } countSizeOfFiles() { //... } addSubNode(fileOrDir) { this.fileList.push(fileOrDir) } removeSubNode(fileOrDir) { return this.fileList.filter(item => item !== fileOrDir) } }//如果我们要表示/** */ */leon */leon/aa.txt */leon/bb */leon/bb/cc.js const root = new Directory('/')const dir_leon = new Directory('/leon/')root.addSubNode(leon)const file_aa = new File('/leon/aa.txt')const dir_bb = new Directory('leon/bb')dir_leon.addSubNode(file_aa)dir_leon.addSubNode(dir_bb)const file_cc = new File('/leon/bb/cc.js')dir_bb.addSubNode(file_cc)
组合模式不好的一点是有可能一不小心就创建了大量的对象而降低性能或难以维护,所以我们接下来讲的享元模式就是用来解决这个问题的。class CharacterStyle{ constructor(font, size, color) { this.font = font this.size = size this.color = color } equals(obj) { return this.font === obj.font && this.size === obj.size && this.color = obj.color }}class CharacterStyleFactory { static styleList = [] getStyle(font, size, color) { const newStyle = new CharacterStyle(font, size, color) for(let i = 0, style; style = this.styleList[i++];) { if (style.equals(newStyle)) { return style } } CharacterStyleFactory.styleList.push(newStyle) return newStyle }}class Character { constructor(c, style) { this.c = c this.style = style }}class Editor { static chars = [] appendCharacter(c, font, size, color) { const style = CharacterStyleFactory.getStyle(font, size, color) const character = new Character(c, style) Editor.chars.push(character) }}
如果我们不把样式提取出来,我们每敲一个文字,都会调用 Editor 类中的 appendCharacter() 方法,创建一个新的 Character 对象,保存到 chars 数组中。如果一个文本文件中,有上万、十几万、几十万的文字,那我们就要在内存中存储这么多 Character 对象。那有没有办法可以节省一点内存呢?class Subject { registerObserver() { throw new Error('子类需重写父类的方法') } removeObserver() { throw new Error('子类需重写父类的方法') } notifyObservers() { throw new Error('子类需重写父类的方法') }}class Observer{ update() { throw new Error('子类需重写父类的方法') }}
然后根据模板来具体的实现class ConcreteSubject extends Subject { static observers = [] registerObserver(observer) { ConcreteSubject.observers.push(observer) } removeObserver(observer) { ConcreteSubject.observers = ConcreteSubject.observers.filter(item => item !== oberser) } notifyObservers(message) { for(let i = 0,observer; observer = ConcreteSubject.observers[i++];) { observer.update(message) } }}class ConcreteObserverOne extends Observer { update(message) { //TODO 获得消息通知,执行自己的逻辑 console.log(message) //... }}class ConcreteObserverTwo extends Observer { update(message) { //TODO 获得消息通知,执行自己的逻辑 console.log(message) //... }}class Demo { constructor() { const subject = new ConcreteSubject() subject.registerObserver(new ConcreteObserverOne()) subject.registerObserver(new ConcreteObserverTwo()) subject.notifyObservers('copy that') }}const demo = new Demo()
事实上,上面只是给出了一个大体的原理和思路,实际中的观察者模式要复杂的多,比如你要考虑同步阻塞或异步非阻塞的问题,命名空间的问题,还有必须要先注册再发布吗? 而且项目中用了大量的观察者模式的话会导致增加耦合性,降低内聚性,使项目变得难以维护。class Tea { addCoffee() { console.log('加入咖啡') } addSuger() { throw new Error('子类需重写父类的方法') } addMilk() { throw new Error('子类需重写父类的方法') } addIce() { console.log('加入冰块') } isIce() { return false // 默认不加冰 } addWater() { console.log('加水') } init() { this.addCoffee() this.addSuger() this.addMilk() if (this.isIce()) { this.addIce() } }}
模板写好了,我们接下来做一杯拿铁class Latte extends Tea { addSuger() { console.log('加糖') } addMilk() { console.log('加奶') } isIce() { return true }}const ice_latte = new Latte()ice_latte.init()
我们可以看到,我们不仅在父类中封装了子类的算法框架,还将一些不会变化的方法在父类中实现,这样子类就直接继承就可以了。其实,之前讲组合模式的时候,也有用到了模板方法模式,你可以回头看一眼。// 因为所有的策略类都实现相同的接口,所以我们可以通过模板来定义class Strategy { algorithmInterface() {}}class ConcreteStrategyA extends Strategy { algorithmInterface() { //具体的实现 //... }}class ConcreteStrategyB extends Strategy { algorithmInterface() { //具体的实现 //... }}//...
class StrategyFactory { strategies = new Map() constructor () { this.strategies.set("A", new ConcreteStrategyA()) this.strategies.set("B", new ConcreteStrategyB()) //... } getStrategy(type) { return type && this.strategies.get(type) } }
在实际的项目开发中,这个模式比较常用。最常见的应用场景是,利用它来避免冗长的 if-else 或 switch 分支判断。不过,它的作用还不止如此。它也可以像模板模式那样,提供框架的扩展点等等。class IHandler { handle() { throw new Error('子类需重写这个方法') }}class HandlerA extends IHandler { handle() { let handled = false //... return handled }}class HandlerB extends IHandler { handle() { let handled = false //... return handled }}class HandlerChain { handles = [] addHandle(handle) { this.handles.push(handle) } handle() { this.handles.for(let i= 0, handler; handler = this.handles[i++];) { handled = handler.handle() if (handle) { break } } }}const chain = new HandlerChain()chain.addHandler(new HandlerA())chain.addHandler(new HandlerB())chain.handle()
第二种方法用到了链表的实现方式class Handler { successor = null setSuccessor(successor) { this.successor = successor } handle() { const isHandle = this.doHandle() if (!isHandle && !!this.successor) { this.successor.handle } } doHandle() { throw new Error('子类需重写这个方法') }}class HandlerA extends Handler { doHandle() { let handle = false //... return handle }}class HandlerB extends Handler { doHandle() { let handle = false //... return handle }}class HandlerChain { head = null tail = null addHandler(handler) { handler.setSuccessor(null) if (head === null) { head = handler tail = handler return } tail.setSuccessor(handler) tail = handler } handle() { if (!!head) { head.handle() } }}const chain = new HandlerChain()chain.addHandler(new HandlerA())chain.addHandler(new HandlerB())chain.handle()
还记得我们之前说装饰者模式说到的AOP吗,我们可以把它稍微改造一下,也可以变成职责链的方式。Function.prototype.after = function(afterFn) { let self = this return function() { let res = self.apply(this, arguments) if (res === false) { afterFn.apply(this, arguments) } return ret }}const res = fn.after(fn1).after(fn2).after(fn3)
可以看出我们传进去的afterFn函数如果返回false的话,会连着这条链一直传下去,直到最后一个,一旦返回true,就不会将请求往后传递了。class ArrayIterator { constructor( arrayList) { this.cursor = 0 this.arrayList = arrayList } hasNext() { return this.cursor !== this.arrayList.length } next() { this.cursor++ } currentItem() { if(this.cursor > this.arrayList.length) { throw new Error('no such ele') } return this.arrayList[this.cursor] } }
在上面的代码实现中,我们需要将待遍历的容器对象,通过构造函数传递给迭代器类。实际上,为了封装迭代器的创建细节,我们可以在容器中定义一个 iterator() 方法,来创建对应的迭代器。为了能实现基于接口而非实现编程,我们还需要将这个方法定义在 ArrayList 接口中。具体的代码实现和使用示例如下所示:class ArrayList { constructor(arrayList) { this.arrayList = arrayList } iterator() { return new ArrayIterator(this.arrayList) }}const names = ['lee', 'leon','qing','quene']const arr = new ArrayList(names)const iterator = arr.iterator()iterator.hasNext()iterator.currentItem()iterator.next()iterator.currentItem()
上面我们只实现了数组的迭代器,关于对象的迭代器,他们的原理差不多,你可以自己去实现以下。class MarioStateMachine { constructor() { this.score = 0 this.currentState = new SmallMario(this) } obtainMushRoom() { this.currentState.obtainMushRoom() } obtainCape() { this.currentState.obtainCape() } obtainFireFlower() { this.currentState.obtainFireFlower() } meetMonster() { this.currentState.meetMonster() } getScore() { return this.score } getCurrentState() { return this.currentState } setScore(score) { this.score = score } setCurrentState(currentState) { this.currentState = currentState }}class Mario { getName() {} obtainMushRoom() {} obtainCape(){} obtainFireFlower(){} meetMonster(){}}class SmallMario extends Mario { constructor(stateMachine) { super() this.stateMachine = stateMachine } obtainMushRoom() { this.stateMachine.setCurrentState(new SuperMario(this.stateMachine)) this.stateMachine.setScore(this.stateMachine.getScore() + 100) } obtainCape() { this.stateMachine.setCurrentState(new CapeMario(this.stateMachine)) this.stateMachine.setScore(this.stateMachine.getScore() + 200) } obtainFireFlower() { this.stateMachine.setCurrentState(new FireMario(this.stateMachine)) this.stateMachine.setScore(this.stateMachine.getScore() + 300) } meetMonster() { // do something }}class SuperMario extends Mario { constructor(stateMachine) { super() this.stateMachine = stateMachine } obtainMushRoom() { // do nothing... } obtainCape() { this.stateMachine.setCurrentState(new CapeMario(this.stateMachine)) this.stateMachine.setScore(this.stateMachine.getScore() + 200) } obtainFireFlower() { this.stateMachine.setCurrentState(new FireMario(this.stateMachine)) this.stateMachine.setScore(this.stateMachine.getScore() + 300) } meetMonster() { this.stateMachine.setCurrentState(new SmallMario(this.stateMachine)) this.stateMachine.setScore(this.stateMachine.getScore() - 100) }}//CapeMario FireMario格式相同//使用const mario = new MarioStateMachine()mario.obtainMushRoom()mario.getScore()
MarioStateMachine 和各个状态类之间是双向依赖关系。MarioStateMachine 依赖各个状态类是理所当然的,但是,反过来,各个状态类为什么要依赖 MarioStateMachine 呢?这是因为,各个状态类需要更新 MarioStateMachine 中的两个变量,score 和 currentState。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)
public interface Visitor { void visit(Engine engine); void visit(Body body); void visit(Car car);}public class PrintCar implements Visitor { public void visit(Engine engine) { System.out.println("Visiting engine"); } public void visit(Body body) { System.out.println("Visiting body"); } public void visit(Car car) { System.out.println("Visiting car"); }}public class CheckCar implements Visitor { public void visit(Engine engine) { System.out.println("Check engine"); } public void visit(Body body) { System.out.println("Check body"); } public void visit(Car car) { System.out.println("Check car"); }}public interface Visitable { void accept(Visitor visitor);}public class Body implements Visitable { @Override public void accept(Visitor visitor) { visitor.visit(this); }}public class Engine implements Visitable { @Override public void accept(Visitor visitor) { visitor.visit(this); }}public class Car { private List<Visitable> visit = new ArrayList<>(); public void addVisit(Visitable visitable) { visit.add(visitable); } public void show(Visitor visitor) { for (Visitable visitable: visit) { visitable.accept(visitor); } }}public class Client { static public void main(String[] args) { Car car = new Car(); car.addVisit(new Body()); car.addVisit(new Engine()); Visitor print = new PrintCar(); car.show(print); }}
一般来说,访问者模式针对的是一组类型不同的对象。不过,尽管这组对象的类型是不同的,但是,它们继承相同的父类或者实现相同的接口。//备忘类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()) //444editor.getContentFromMemento(careTaker.get(1))console.log(editor.getContent()) //333editor.getContentFromMemento(careTaker.get(0))console.log(editor.getContent()) //222
备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。这个模式的定义表达了两部分内容:一部分是,存储副本以便后期恢复;另一部分是,要在不违背封装原则的前提下,进行对象的备份和恢复。// 接收者类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 developer = new Receiver(); // 售楼处 const order = new Command(developer); // 买房const client = new Invoker(order); client.invoke()
在一些面向对象的语言中,函数不能当做参数被传递给其他对象,也没法赋值给变量,借助命令模式,我们将函数封装成对象,这样就可以实现把函数像对象一样使用。但是在js中函数当成参数被传递是再简单不过的事情了,所以上面的代码我们也可以直接用函数来实现。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; } } class MultiplicationExpression { interpret(context) { if (!(context instanceof Context)) { throw new Error("TypeError") } context.sum *= context.sum } } class DivisionExpression { interpret(context) { if (!(context instanceof Context)) { throw new Error("TypeError") } context.sum /= context.sum } } // MultiplicationExpression和DivisionExpression省略 /** 以下是测试代码 **/ const context = new Context(); // 依次添加: 加法 | 加法 | 减法 表达式 context.add(new PlusExpression()); context.add(new PlusExpression()); context.add(new MinusExpression()); context.add(new MultiplicationExpression()); context.add(new MultiplicationExpression()); context.add(new DivisionExpression()); // 依次执行: 加法 | 加法 | 减法 表达式 context.list.forEach(expression => expression.interpret(context)); console.log(context.sum);
解释器模式的代码实现比较灵活,没有固定的模板。我们前面说过,应用设计模式主要是应对代码的复杂性,解释器模式也不例外。它的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。关键词:设计,模式,整理