fi3ework / blog

📝

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

设计模式之「行为型模式」

fi3ework opened this issue · comments

commented
  • 策略模式(Strategy)
  • 状态模式(State)
  • 责任链模式(Chain of Responsibility)
  • 解释器模式(Interpreter)
  • 命令模式(Command)
  • 观察者模式(Observer)
  • 备忘录模式(Memento)
  • 迭代器模式(Iterator)
  • 模板方法模式(Template Method)
  • 访问者模式(Visitor)
  • 中介者模式(Mediator)
commented

策略模式

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。

策略模式的核心是将每个算法封装起来,与每个算法的调用隔离开。对于策略模式能够抽象的算法来说,不变的是接口(每个算法调用的方式),变的是算法的实现(包括有多少个算法,和每个算法是如何实现的)。

所以,针对使用策略模式抽象的代码,有两个部分需要实现:一个是策略类(Strategy),封装了具体的算法,并负责具体的实现过程。第二个是环境类(Context),环境类负责将策略类封装的代码插入到当前环境中。

策略模式将算法的责任和算法本身分离开,这样既可以提高了策略类的复用性(因为策略类中算法的实现与环境无关),又符合了单一职责原则,又支持了开闭原则(只需在策略类中增加算法的实现即可)。

实现

代码实现:

var strategies = {
  'S': (salary)=> salary * 4,
  'A': (salary) => salary * 3,
  'B': (salary) => salary * 2
}

var calcBonus = (level, salary) => strategies[level](salary)

console.log(calcBonus('S', 20000))
console.log(calcBonus('A', 10000))

分析:

  1. 本来第一直觉的思路实现一个不同等级的绩效评级对应不同年终奖的函数会使用 if 或者 switch,但是使用策略模式,就是将每个 if 的判断条件转换为了对策略类中算法的调用,而算法的实现是独立的,满足了开闭原则。
  2. 环境类通过组合和委托作为实现调用与策略类中的桥梁。
commented

迭代器模式

其实迭代器模式我们已经很熟悉了,ES5 带来的 forEach 就是迭代器模式的一种实现,包括 map filter every some 也是迭代器模式的实现。准确的说以上几种实现都是内部迭代器的实现,迭代器模式还有另一种 —— 外部迭代器。

内部迭代器在函数的内部已经定义好了迭代规则,外部只需要调用一次,传入迭代的参数,之后整个迭代规则将由内部迭代器完成。

外部迭代器则必须显示的请求迭代下一请求,相当于只是完成了读取下一个迭代元素的任务,其他的均由外部负责。

可以看到,内部迭代器来的要方便的多,只需传入一次参数即可。但是也因为内部迭代器封装了迭代的规则。导致拓展性不够,就需要使用外部迭代器。

实现

内部迭代器

实现一个简易版的 forEach

Array.prototype.forEach2 = function(callback, that) {
  let T
  
  if (this == null) {
    throw new TypeError(' this is null or not defined');
  }

  let O = Object(this)
  let len = O.length;

  if (typeof callback !== "function") {
    throw new TypeError(callback + ' is not a function');
  }

  if (arguments.length > 1) {
    T = thisArg; // 如果未提供 T,则为 undefined
  }

  for (let i = 0; i < len; i++) {
    if (i in O) {

      callback.call(T, O[i], i, O)
    }

  }
}

;
['a', 'b', 'c'].forEach2((item, index) => {
  console.log(`item - ${item}, index - ${index}`)
})

外部迭代器

其实外部迭代器实现起来更简单,ES6 也添加了 Iterator 接口,我们可以通过 obj[Symbol.iterator] 来拿到 Array, Map, Set, String, TypedArray, 函数的 arguments 对象, NodeList 对象的 Iterator 接口。

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}
commented

责任链模式

在我们写写业务逻辑的时候,经常会遇到多个 if,elseif,else 嵌套的分支语句,并且每个逻辑判断都会是一大长串,并且当后来再去修改时会发现试图理清多个嵌套的判断逻辑是多么的困难。

设计模式的作用就是分离变化与不变化的部分。其中变化的部分就是判断的逻辑,有点像策略模式中的每个策略算法,每个分支语句就是变化的部分,他们可以随业务逻辑添加删除或修改,所以必须将他们抽象出来,这样才符合开闭原则。

像策略模式使用字符串的 key 代替 if else。责任链模式在每个责任链节点中调用下一个节点并返回标志位的返回值标识是否需要下一个节点继续执行来形成所谓的“链”。

实现

逻辑耦合的责任链

const order1 = function(orderType, pay, stock) {
  if ( /* logic */ ) {
    /* do something */
  } else {
    order2(orderType, pay, stock)
  }
}

const order2 = function(orderType, pay, stock) {
  if ( /* logic */ ) {
    /* do something */
  } else {
    orderNormal(orderType, pay, stock)
  }
}

这种责任链模式其实还是没有将业务函数与责任链的传递分离,责任链的节点顺序被硬编码进了业务函数中,但是好处是实现的简单粗暴,至少在一定程度上将 if else 给分割开了。

封装一个责任链类

为了将责任链的传递与业务函数分离开,每个业务函数返回一个特定的值(字符串)来标志需要调用下一个责任链节点,如果在某节点返回的不是该标志,那么后面的节点将以此被跳过。

const order1 = function(orderType, pay, stock) {
  if ( /* logic */ ) {
    /* do something */
  } else {
    return 'NEXT_SUCCEWSSOR'
  }
}

接下来,创建一个责任链类来将整个链串起来

const order1 = function(orderType, pay, stock) {
  if (orderType === 1 && pay === true) {
    console.log('111')
  } else {
    return 'NEXT_SUCCESSOR'
  }
}

const order2 = function(orderType, pay, stock) {
  if (orderType === 2 && pay === true) {
    console.log('222')
  } else {
    orderNormal(orderType, pay, stock)
  }
}

const orderNormal = function(orderType, pay, stock) {
  if (stock > 0) {
    console.log('ok')
  } else {
    console.log('nope')
  }
}

class Chain {
  constructor(fn) {
    this.fn = fn
    this.successor = null
  }
  setNextSuccessor(successor) {
    return this.successor = successor
  }

  passRequest() {
    let ret = this.fn.apply(this, arguments)

    if (ret === 'NEXT_SUCCESSOR') {
      return this.successor && this.successor.passRequest.apply(this.successor, arguments)
    }
    return ret
  }
}

let c1 = new Chain(order1)
let c2 = new Chain(order2)
let cn = new Chain(orderNormal)
c1.setNextSuccessor(c2)
c2.setNextSuccessor(cn)
c1.passRequest(2, true, 500)

更简洁的责任链类

上面的责任链类略显繁琐了,需要将每个责任链函数包装,然后再添加到上一个节点后面,下面的封装更简洁,一次成型。但是上面个的责任链类也有好处,就是可以随意打散和添加节点,而保持其他的节点关系不受影响。

class Chain {
  constructor(...fns) {
    this.fns = fns
    this.successor = null
  }

  passRequest() {
    let ret
    const fns = this.fns
    for (let i = 0; i < fns.length; i++) {
      if (typeof fns[i] === 'function') {
        ret = fns[i].apply(fns[i], arguments)
      }
      if (ret !== 'NEXT_SUCCESSOR') {
        break;
      }
    }
    return ret
  }
}

let c = new Chain(order1, order2, orderNormal)
c.passRequest(2, true, 500)

修改 Function 原型链的 AOP

Function.prototype.after = function(fn) {
  let self = this
  return function() {
    ret ret = self.apply(this, arguments) // this 为调用时的 this
    if (ret === 'nextSuccessor') {
      return fn.apply(this, arguments) // this 为调用时的 this
    }
    return ret
  }
}

调用时先用 after 来创建一个 AOP 化的函数

const order = order1.after(order2).after(orderNormal)
order(1, 200)

但是这种方法最大问题是直接修改了 Function 的原型链,除非万不得已,尽量不要修改任何的内置对象。

使用第三方库来实现 AOP

meld 库是用 js 来实现的 AOP 库,demo 如下,可以看到整个 API 基本无侵入,但是一般使用责任链模式都是用来简化逻辑,如果不是大量需要的话,为此专门引入一个第三方库,还是显得有点重了。

var myObject = {
	doSomething: function(a, b) {
		return a + b;
	}
};

// Call a function after myObject.doSomething returns
var remover = meld.after(myObject, 'doSomething', function(result) {
	console.log('myObject.doSomething returned: ' + result);
});

myObject.doSomething(1, 2); // Logs: "myObject.doSomething returned: 3"

remover.remove();

myObject.doSomething(1, 2); // Nothing logged