- 职责链模式
职责链的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。一系列可能会处理请求的对象被连成一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象,我们把这些对象称为链中的节点。职责链模式的最大的优点是:请求发送者只需要知道链中的第一个节点,从而弱化了发送者和一组接收者之间的强联系。
- 灵活可拆分的职责链节点
我们约定,如果某个节点不能处理请求,则返回一个特定的字符串 'nextSuccessor'来表示该请求需要继续传递:
let order500 = function(orderType, pay, stock) {
if (orderType == 1 && pay == true) {
} else return 'nextSuccessor'
}
let order200 = function(orderType, pay, stock) {
if (orderType == 2 && pay == true) {
} else return 'nextSuccessor'
}
let orderNormal = function(orderType, pay, stock) {
if (stock > 0) {
} else {}
}
接下来需要把函数包装进职责链节点,我们定义一个构造函数Chain,在new Chain的时候传递的参数即为需要被包装的函数,同时它还拥有一个实例属性this.successor,表示在链中的下一个节点。
let Chain = function(fn) {
this.fn = fn
this.successor = null
}
Chain.prototype.setNextSuccessor = function(successor) {
return this.successor = successor
}
Chain.prototype.passRequest = function() {
let ret = this.fn.apply(this, arguments)
return ret
}
-
异步的职责链
每个节点函数同步返回一个特定的值'nextSuccessor', 来表示是否把请求传递到下一个节点。异步请求返回的结果才能决定是否继续在职责链中passRequest。 -
中介者模式
function Player(name, teamColor) {
this.name = name
this.teamColor = teamColor
this.status = 'alive'
}
Player.prototype.win = function() {}
Player.prototype.loose = function() {}
Player.prototype.die = function() {
this.state = 'die'
playerDirector.receiveMessage('playerDead', this)
}
Player.prototype.loose = function() {
playerDirector.receiveMessage('removePLayer', this)
}
Player.prototype.changeTeam = function() {
playerDirector.receiveMessage('changeTeam', this, color)
}
let playerFac = function(name, teamColor) {
let newPlayer = new Player(name, teamColor)
playerDirector.receiveMessage('addPlayer', newPlayer)
return newPlayer
}
最后,我们需要实现这个中介者对象,一般有两种方式
- 利用发布订阅模式。将playerDirector实现为订阅者,各player作为发布者,一旦player的状态改变,便推送消息给playerDirector
- 在playerDirector中开放一些接收消息的接口,各player可以直接调用这些接口来给
playerDirector发送消息,player只需传递一个参数给playerDirector
let playerDirector = (function() {
let player = {}, // 保存所有玩家
operations = {} // 中介者可以进行的操作
operations.addPlayer = fucntion(player) {
let teamColor = player.teamColor // 玩家队伍颜色
players[teamColor] = players[teamColor] || [] 不存在则创建一个团队
players[teamColor].push(player)
}
operations.removePlayer = fucntion(player) {
let teamColor = player.teamColor, // 玩家队伍颜色
teamPlayers = players[teamColor] || [] 不存在则创建一个团队
for (i=teamPlayers.length-1;i>=0;i--) {
if (teamPlayers[i] == player) teamPlayers.splice(i, 1)
}
}
let receiveMessage = function(...args) {
let message = args[0]
operations[message].apply(this, args)
}
return {
receiveMessage: receiveMessage
}
})()
- 装饰者模式
装饰者模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。在传统的面向对象的语言中,给对象添加功能常常使用继承的方式,但继承的方式并不灵活,一方面会导致超类和子类的强耦合,当超类改变时,子类也会随之改变;给对象动态地增加职责的方式被称为装饰者模式。装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地增加职责。
let Plane = function(){}
Plane.prototype.fire = () => {发射普通子弹}
MissileDecorator = function(plane) {
this.plane = plane
}
MissileDecorator.prototype.fire = function() {
this.plane.fire()
console.log('发射导弹')
}
导弹类和原子弹类的构造函数都接受参数plane对象,并且保存好这个参数,在它们的fire方法中,除了执行自身的操作,还调用plane对象的fire方法。
这种给对象动态增加职责的方式,并没有真正地改动对象自身,而是将对象放入另一个对象之中,这些对象以一条链的方式进行引用,形成一个聚合对象。这些对象拥有相同的方法。
- 通过aop装饰函数,给函数动态的添加功能
Function.prototype.before = function(beforefn) {
let self = this
return function(...args) {
beforefn.apply(this, args)
return self.apply(this, args)
}
}
// 使用不污染原型的方式
-
aop的应用实例
我们可以把行为依照职责分成粒度更细的函数,随后通过装饰把它们合并到一起
箭头函数的this不是函数调用的对象,而是父级的this -
状态模式
let Light = function() {
this.state = 'off'
this.button = null
}
通常我们谈到封装,一般都会优先封装对象的行为,而不是状态。状态模式是把事物的状态封装成单独的类,和此种状态有关的行为都被封装在类的内部.
let OffLightstate = function(light) {
this.light = light
}
OffLightstate.prototype.buttonWasPressed = function() {
this.light.setState(this.light.WeakLightState)
}
let WeakLightstate = function(light) {
this.light = light
}
WeakLightstate.prototype.buttonWasPressed = function() {
this.light.setState(this.light.StrongLightState)
}
let StrongLightState = function(light) {
this.light = light
}
StrongLightState.prototype.buttonWasPressed = function() {
this.light.setState(this.light.OffLightState)
}
let Light = function() {
this.offLightstate = new OffLightstate(this)
this.WeakLightstate = new WeakLightstate(this)
this.StrongLightState = new StrongLightState(this)
this.button=null
}
Light.prototype.init = function() {
this.currentState = this.offLightstate
this.button.onClick = function() {
self.currentState.buttonWasPressed()
}
}
- 缺少抽象类的变通方式
在状态类中定义一些共同的行为方法,Context会将请求委托给状态对象的实现方法,无论增加多少个状态类都必须实现buttonWasPressed方法。
在java中,所有的状态类都必须继承一个State抽象父类,这样做的原因一方面是向上转型,另一方面是保证所有状态子类都实现buttonWasPressed方法,子类的原型指向抽象父类的实例实现继承,在父类的原型上定义一个公共方法抛出错误
let State = function() {}
State.prototype.buttonWasPressed = () => { throw new Error('error') }
- 文件上传
let plugin = (() => {
let plugin = document.createElement('embed')
plugin.style.display = 'none'
plugin.sign = () => { 扫描文件 }
plugin.plause = () => {}
plugin.uoploading = () => {}
plugin.del = () => {}
plugin.done = () => {}
document.body.appendChild(plugin)
return plugin
})()
let Upload = function(fileName) {
this.plugin=plugin
this.fileName = fileName
this.button1 = null
this.button2 = null
this.state = 'sign'
}
Upload.prototype.init = function() {
let that = this
this.dom = document.createElement('div')
this.dom.innerHTML = '<button data-action="button1">扫描中</button>'
document.body.appendChild(this.dom)
this.button1 = this.dom.querySelector('[data-action="button1"]')
this.bindEvent()
}
Upload.prototype.bindEvent = function() {
let self = this
this.button1.onClick = function() {
if (self.state === 'sign') {} else if (){
self.changeStatus('pause')
}
}
}
- 状态模式重构上传程序
将按钮的吗每一个状态都当做类,每个状态都有自己的行为,为构造函数每种状态子类都创建一个实例对象:
let Upload = function(fileName) {
this.plugin = plugin
this.fileName = fileName
this.button1 = null
this.button2 = null
this.signState = new SignState(this)
this.uploadingState = new UploadingaState(this)
this.pauseState = new PauseState(this)
this.doneState = new DoneState(this)
this.errorState = new ErrorState(this)
this.currState = new this.signState
}
Upload.prototype.bindEvent = function() {
let self = this
this.button1.onClick = function() {
self.curState.clickHander1()
}
this.button2.onClick = function() {
self.curState.clickHander2()
}
}
Upload.prototype.sign = function(){
this.plugin.sign()
this.currState = this.signState
}
Upload.prototype.uploading = function(){
this.plugin.uploading()
this.currState = this.uploadingState
}
let StateFactory = (() => {
let State = function() {}
State.prototype.clickHandler1 = function() {
throw new Error('子类必须重写父类的clickHandler1方法')
}
return function(param) {
let F = function(uploadObj) {
this.uploadObj = uploadObj;
}
F.prototype = new State()
for (var i in param) {
F.prototype[i] = param[i] // 将传入对象的方法拷贝到原型上
}
return F
}
})()
let SignState = StateFactory({
clickHandler1: function() { 扫描中,点击无效 },
clickHandler2: function() { 文件正在扫描不能删除 }
})
- 状态模式的优缺点
- 状态模式定义了状态与行为的关系,并将它们封装在一个类里。通过增加新的状态类,很容易增加新的状态和转换。
- 避免Context的无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了context中过多的条件分支
- 用对象代替字符串记录当前状态,更加一目了然
- 状态模式中的性能优化点
- 有两种选择来管理state对象的创建和销毁。第一种是当state对象被需要时裁创建并随后销毁,另一种是一开始就创建好所有状态不销毁。
- 本章,我们为每个context对象都创建了一组state对象,实际上这些state对象是可以共享的,各context对象可以共享一个state对象。
- js版本的状态机
let Light = function() {
this.curState = FSM.off
this.button = null
}
let deletgate = function(client, delegation) {
return {
buttonWasPressed: function(...args) {
return delegation.buttonWasPressed.apply(client, args)
}
}
}
let FSM = {
off: {
buttonWasPressed: function() {
}
}
}
- 表驱动的有限状态机
下一个状态由当前状态和行为共同决定,这样一来我们就可以在表中查找状态。
适配器模式
适配器模式是解决两个软件实体间接口不兼容问题。
- 单一职责原则
- 代理模式
把img标签添加到页面的功能和预加载图片的功能分开放到两个对象中去
let myImage = (function() {
let imgNode= document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc: function(src) { imgNode.src = src }
}
})()
// 此时已经创建好img标签放入页面中,proxyImg则添加预加载的功能并且放入一张//默认图片
let proxyImage = (() => {
// 制造一个假节点预加载图片
let img = new Image()
img.onload = () => {
myImage.setSrc(this.src)
}
return {
setSrc: function() {
myImage.setSrc('default gif') //设置一张本地已经加载好的图片
myImage.src= src
}
}
})()
- 单例模式
我们把管理单例的职责和创建浮窗的职责分别封装在两个方法里
let getSingle = function(fn) {
let result
return function(...args) {
return result || (result = fn.apply(this, args))
}
}
// 函数执行的时候将弹框保存在单例函数的变量里
let createLoginLayer = () => {
let div = document.createElement('div')
div.innerHTML = 'login'
document.body.appendChild(div)
return div
}
let createSingleLoginLayer = getSingle(createLoginLayer)
let loginLayer1 = createSingleLoginLayer()
let loginLayer2 = createSingleLoginLayer()
- 装饰者模式
使用装饰者的时候,我们通常让类或者对象一开始只具有一些基础的职责,更多的职责在代码运行时被动态装饰到对象上面,单一职责的原则会增加代码的复杂度。
Function.prototype.after = function(fn) {
let self = this
return function(...args) {
let ret = self.apply(this, args)
afterfn.apply(this, args)
return ret
}
}
- 最少知识原则
单一职责原则指导我们把对象划分成较小的粒度,这可以提高对象的可复用性。但越来越多的对象间可能会产生错综复杂的联系。最少知识原则要求我们尽量减少对象和对象之间的通信。
- 中介者模式
当一个对象发生改变,只需要和中介者通信即可,对象和对象间不通信。 - 外观模式
外观模式主要为子系统的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得子系统更加使用。
外观模式对客户提供一个简单易用的高层接口,高层接口会把客户请求转发给子系统实现具体功能。请求外观接口不是强制的,客户可以选择越过外观接口直接访问子系统。
- 开放封闭原则
- 找出变化
- 放置挂钩,我们在程序可能发生变化的地方放置挂钩,挂钩的返回结果决定下一步的走向。
- 封闭原则
-
发布订阅模式
发布订阅用来降低多个对象之间的依赖关系,它可以取代对象之间硬编码的通知机制,一个对象不用再显示的调用另一个对象的某个接口。当有新的订阅者出现时,发布者代码不需要任何修改。同样发布者需要修改时,也不会影响之前的订阅者。 -
模板方法模式
在一个运用模板方法的程序中,子类的方法种类和执行顺序都是不变的,所以我们把这部分逻辑抽离出来放到父类的模板方法里面; -
策略模式
策略模式将各种算法封装成单独的策略类,这些策略类可以被交换使用。 -
代理模式
-
职责链模式
-
先假设变化不会发生,完成需求,当修改来的时候再重构,前期不要过度封装
-
接口和面向接口编程
java中的interface,interface关键字可以产生一个完全抽象的类,这个类专门负责建立类与类之间的联系。 -
回到java的抽象类
目前我们有一个鸭子类Duck, 还有一个让鸭子发出叫声的AnimalSound类,该类有一个makeSound方法接收Duck类型的对象作为参数
public class Duck {
public void makeSound(){}
}
public class AnimalSound {
public void makeSound(Duck duck) {duck.makeSound()}
}
在享受静态语言类型检查带来的安全性的同时,我们也失去了一些编写代码的自由
所以我们需要通过抽象类实现向上转型
public abstract class Animal {
abstract void makeSound()
}
抽象类的两个作用
- 契约作用,继承抽象类的具体类都会继承抽象类中的具体方法,并要求覆盖
- 实现向上转型
- interface
除了用抽象类完成面向接口编程,interface也可以达到同样的效果。虽然很多人在实际中刻意区分抽象类和interface,但interface实际上也是一种继承的方式,叫接口继承。
相对于单继承的抽象类,一个类可以实现多个interface。抽象类中除了abstract方法之外,还可以有一些供子类使用的具体方法。interface使抽象更进一步,它产生一个完全抽象的类,不提供任何具体的实现和方法体,但允许该interface的创建者确定方法名、参数列表、返回类型,这相当于提供一些行为上的约定,但不关心该行为的具体实现过程。
抽象类是基于单继承的,就是说我们不可能让Duck和Chicken再继承另一个家禽类。如果使用interface,可以仅仅针对叫声这个行为编写程序,同时一个类也可以实现多个interface。
public interface Animal {
abstract void makeSound();
}
public class Duck implements Animal {
public void makeSound() {}
}
抽象类和interface的主要作用:
- 通过向上转型来隐藏对象的真正类型,以表现对象的多态性。
- 约定类与类之间的一些契约关系。
- 使用typescript
interface Command {
execute: Function
}
class RefreshCommand implements Command {
constructor() {}
execute() {}
}
- 重构
- 提炼函数
- 合并重复条件片段
- 把条件分支语句提炼成函数
- 提前让函数退出条件分支
- 传递对象参数代替过长的参数列表
- 尽量减少参数数量
- 少用三目运算符
- 合理使用链式调用
- 使用return退出多重循环
网友评论