js设计模式(六)-行为型模式(状态模式/命令模式/访问者模式/中介者模式)

136 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第21天,点击查看活动详情

状态模式

状态模式:当一个对象的内部状态发生改变时,会导致其行为的改变,这看起来像是改变了对象。

优点

  • 结构相比之下清晰,避免了过多的 switch-case 或 if-else 语句的使用,避免了程序的复杂性提高系统的可维护性;
  • 符合开闭原则,每个状态都是一个子类,增加状态只需增加新的状态类即可,修改状态也只需修改对应状态类就可以了;
  • 封装性良好,状态的切换在类的内部实现,外部的调用无需知道类内部如何实现状态和行为的变换;

缺点

  • 引入了多余的类,每个状态都有对应的类,导致系统中类的个数增加。

结合策略模式理解,可以感觉到,状态模式就是策略模式的升级版!

策略模式是对于某个功能板块设计一系列策略,优化代码逻辑提高可维护性,而状态模式则是更彻底,直接声明几个同类型的类,遇到情况以后直接把实例原型给替换掉了。

状态模式是一种非同寻常的优秀模式,它也许是解决某些需求场景的最好方法。状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。

示例代码就不展示了,底部的链接里面的大佬写的非常好!

命令模式(事务模式)

命令模式的原理:将请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

命令模式用于:将请求封装成对象,将命令的发送者和接受者解耦。即:将调用对象(用户界面、API 和代理等)与实现操作的对象隔离开。

命令模式的使用场景:对行为进行"记录、撤销/重做、事务"等处理,需要行为请求者与行为实现者解耦的时候(凡是两个对象间互动方式需要有更高的模块化程度时都可以用到这种模式)

优点

  • 降低对象之间的耦合度。
  • 新的命令可以很容易地加入到系统中。
  • 可以比较容易地设计一个组合命令。
  • 调用同一方法实现不同的功能。

缺点

  • 使用命令模式可能会导致某些系统有过多的具体命令类。

命令模式是很常用的,如下一个简单的 api 接口管理器:

 
const api = {
    getUserList: async (data) => await axios.post('/user/list', data),
    getIserInfo: async (id) => await axios.get('/user/info', id)
}

当然这只是一种最简单的理解思路,我理解的命令模式的本质上就是将一些【指令】统一管理在一起,从而达到任何地方都可以通过实例化对象去调用它身上的一系列方法的效果。

访问者模式

访问者模式,针对于对象结构中的元素,定义在不改变该对象的前提下访问其结构中元素的新方法。

访问者模式由 3 部分构成:对象集合、集合元素、访问者。

访问者模式的应用场景:

  • 对象结构相对稳定,但其操作算法经常变化的程序。
  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作变化影响对象的结构。

优点:

  • 扩展性好:在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能
  • 复用性好:通过访问者来定义整个对象结构通用的功能,从而提高复用程度
  • 分离无关行为:通过访问者分离无关行为,把相关行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一

缺点

  • 被访问的类的结构是固定的,如果被访问的类的结构会发生变化,则不适合访问者模式
  • 对象结构变化很困难:在访问者模式中,每增加一个新的元素类,都要在每一个具体的访问类中增加响应的具体操作,这违背了开闭原则
  • 违反了依赖倒置原则:访问者模式依赖了具体类,而没有抽象类

简单了解一番。与其说访问模式是行为型,不如说它是结构型:

它的工作流程是这样的:

  1. 首先声明一系列的同属性类,就是很相似的一系列类。
  2. 声明一个访问者,可以是一个类也可以是一个方法,这个方法会搜集并处理上面类的实例化对象。
  3. 访问者内部通过访问 实例化对象的属性 来做跟上面的类没关系的一些操作。

所以我更喜欢说他是一个结构型,而且和如果细究的话,观察者模式完全可以胜任这个工作。只是在实现的过程中会有微微的区别。

中介者模式(调停模式)

中介者模式:用一个中介对象来封装多个对象之间的复杂交互。中介者将对象与对象之间紧密的耦合关系变得松散,从而可以独立地改变他们。

中介者模式用于解除对象与对象之间的紧耦合关系。

中介者模式的使用场景:如果对象之间的复杂耦合确实导致调用和维护出现了困难,而且这些耦合度随项目的变化呈指数增长曲线,那我们就可以考虑用中介者模式来重构代码。

优点

  • 松散耦合,降低了同事对象之间的相互依赖和耦合,不会像之前那样牵一发动全身;
  • 将同事对象间的一对多关联转变为一对一的关联,符合最少知识原则,提高系统的灵活性,使得系统易于维护和扩展;
  • 中介者在同事对象间起到了控制和协调的作用,因此可以结合代理模式那样,进行同事对象间的访问控制、功能扩展;
  • 因为同事对象间不需要相互引用,因此也可以简化同事对象的设计和实现;

缺点

  • 逻辑过度集中化,当同事对象太多时,中介者的职责将很重,逻辑变得复杂而庞大,以至于难以维护;
  • 当出现中介者可维护性变差的情况时,考虑是否在系统设计上不合理,从而简化系统设计,优化并重构,避免中介者出现职责过重的情况;

中介者模式让我想起了昨天刚看的电影《灵魂摆渡·黄泉》中的台词:黄泉路上,彼岸花开,花开一千年,花落一千年,花叶,永不见。

酸了酸了...

中介者模式很容易理解:租客、房东、黑中介。中介搜集房源和客源,在房东和租客都不知道对的情况下,实现租客把房东的房子给租下来的场景。

实际开发过程中可以理解为,两个完全不相干的类,被一个中介者有效的组合在一起,实现业务逻辑。

class Player{
  constructor(name){
    this.name = name
  }
}

class Game {
  constructor(name){
    this.name = name
  }

  play(p, o){
    console.log(p.name, o);
  }
}

class Mediator {
  constructor(players, game){
    this.players = players
    this.game = game
  }

  static createRoom(pA, pB, g){
    console.log('创建了一个游戏房间!');
    console.log('玩家为:',pA.name,'和',pB.name);
    console.log('游戏名称:', g.name);
    return new Mediator([pA, pB], g)
  }

  play(player, operate){
    this.game.play(player, operate)
  }

  over(pA){
    console.log('游戏结束:', pA.name + 'is winner!');
  }
}

const pA = new Player('小明')
const pB = new Player('小红')

const game = new Game('五子棋')
const room = Mediator.createRoom(pA, pB, game)

room.play(pA, '下了一步棋')
room.play(pB, '下了一步棋')
room.play(pA, '下了一步棋')
room.play(pB, '下了一步棋')

room.over(pA)

image.png

当然,我们可以声明好多个游戏,好多个玩家,只要满足 Game 可以 play,可以 over,那么就 都可以 Mediator 这个中介者代理。

参考文档