JavaScript设计模式之行为型模式(下)

907 阅读6分钟

Github 源码地址

观察者模式

简介

  • 解决:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
  • 使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
  • 方式:使用面向对象技术,将这种依赖关系弱化。
  • 场景:拍卖,拍卖师观察最高标价,然后通知给其他竞价者竞价。

优缺点

  • 优点:
    1. 观察者和被观察者是抽象耦合的。
    2. 建立一套触发机制。
  • 缺点:
    1. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
    2. 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
    3. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

举例

  • 根据数据变化获取数据的2816进制。
class Observer {
    constructor() {
        this.subject = null;
    }
    update() { }
}
// 2进制
class BinaryObserver extends Observer {
    constructor(subject) {
        super();
        this.subject = subject;
        this.subject.attach(this)
    }
    update() {
        console.log("Binary String: " + this.subject.getState().toString(2));
    }
}
// 8进制
class OctalObserver extends Observer {
    constructor(subject) {
        super();
        this.subject = subject;
        this.subject.attach(this)
    }
    update() {
        console.log("Octal String: " + this.subject.getState().toString(8));
    }
}
// 16进制
class HexaObserver extends Observer {
    constructor(subject) {
        super();
        this.subject = subject;
        this.subject.attach(this)
    }
    update() {
        console.log("Hexa String: " + this.subject.getState().toString(16));
    }
}
class Subject {
    constructor(state) {
        this.state = state;
        this.list = [];
    }
    getState() {
        return this.state;
    }
    setState(state) {
        this.state = state;
        this.notifyAllObserver();
    }
    // 存入
    attach(observer) {
        this.list.push(observer);
    }
    // 更新所有
    notifyAllObserver() {
        for (let observer of this.list) {
            observer.update();
        }
    }
}
function demo() {

    const subject = new Subject();

    new HexaObserver(subject);
    new OctalObserver(subject);
    new BinaryObserver(subject);

    console.log("First state change: 15");
    subject.setState(15);
    console.log("Second state change: 10");
    subject.setState(10);
}

demo()

image.png

状态模式

简介

  • 解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。。
  • 使用:代码中包含大量与对象状态有关的条件语句。
  • 方式:将各种具体的状态类抽象出来。
  • 场景:某个服务的状态(、未启动、运行中、停止)。

优缺点

  • 优点:
    1. 封装了转换规则。
    2. 枚举可能的状态,在枚举状态之前需要确定状态种类。
    3. 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
    4. 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
    5. 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
  • 缺点:
    1. 状态模式的使用必然会增加系统类和对象的个数。
    2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
    3. 状态模式对"开闭原则"的支持并不太好。

举例

  • 服务的启动、停止状态。
class State {
    doAction(context) { }
}

class StartState extends State {
    doAction(context) {
        console.log('Player is in start state.');
        context.setState(this);
    }
    toString() {
        return "Start State";
    }
}

class StopState extends State {
    doAction(context) {
        console.log('Player is in stop state.');
        context.setState(this);
    }
    toString() {
        return "Stop State";
    }
}
class Context {
    constructor() {
        this.state = null;
    }
    getState() {
        return this.state;
    }
    setState(state) {
        this.state = state;
    }
}

function demo() {
    const context = new Context();

    console.log('context state:', context.getState());
    const startState = new StartState();
    startState.doAction(context);
    console.log('context state:', context.getState().toString());
    const stopState = new StopState(context);
    stopState.doAction(context);
    console.log('context state:', context.getState().toString());
}

demo()

image.png

策略模式

简介

  • 解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
  • 使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
  • 方式:将这些算法封装成一个一个的类,任意地替换。
  • 场景:表单验证、旅游出行交通选择。

优缺点

  • 优点:
    1. 算法可以自由切换。
    2. 避免使用多重条件判断。
    3. 扩展性良好。
  • 缺点:
    1. 策略类会增多。
    2. 所有策略类都需要对外暴露。

举例

  • 调用加法/减法。
class Strategy {
    doOperation(num1, num2) { }
}

class OperationAdd extends Strategy {
    constructor() {
        super();
    }
    doOperation(num1, num2) {
        return num1 + num2;
    }
}
class OperationSubtract extends Strategy {
    constructor() {
        super();
    }
    doOperation(num1, num2) {
        return num1 - num2;
    }
}

class Context {
    constructor(strategy) {
        this.strategy = strategy;
    }
    execStrategy(num1, num2) {
        return this.strategy.doOperation(num1, num2)
    }
}

function demo() {
    let context = new Context(new OperationAdd());
    console.log('10 + 7 = ', context.execStrategy(10, 7));
    context = new Context(new OperationSubtract());
    console.log('10 - 7 = ', context.execStrategy(10, 7));
}

demo()

image.png

模板模式

简介

  • 解决:一些方法通用,却在每一个子类都重新写了这一方法。
  • 使用:有一些通用的方法。
  • 方式:将这些通用算法抽象出来。
  • 场景:请求处理中成功、失败的通用处理;具有相似性的几种游戏。

优缺点

  • 优点:
    1. 封装不变部分,扩展可变部分。
    2. 提取公共代码,便于维护。
    3. 行为由父类控制,子类实现。
  • 缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

举例

  • 实现一个姓名的迭代打印。
class Game {
    initialize() { };
    startPlay() { };
    endPlay() { };

    //模板
    play() {

        //初始化游戏
        this.initialize();

        //开始游戏
        this.startPlay();

        //结束游戏
        this.endPlay();
    }
}

class Cricket extends Game {
    constructor() {
        super();
    }
    endPlay() {
        console.log("Cricket Game Finished!");
    }

    initialize() {
        console.log("Cricket Game Initialized! Start playing.");
    }
    startPlay() {
        console.log("Cricket Game Started. Enjoy the game!");
    }
}

class Football extends Game {
    constructor() {
        super();
    }
    endPlay() {
        console.log("Football Game Finished!");
    }

    initialize() {
        console.log("Football Game Initialized! Start playing.");
    }
    startPlay() {
        console.log("Football Game Started. Enjoy the game!");
    }
}

function demo() {
    const cricket = new Cricket();
    cricket.play();
    const football = new Football();
    football.play();
}

demo()

image.png

访问者模式

简介

  • 解决:稳定的数据结构和易变的操作耦合问题。
  • 使用:需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。
  • 方式:在被访问的类里面加一个对外提供接待访问者的接口。
  • 场景:您去拜访朋友,您通过朋友的描述作出判断。

优缺点

  • 优点:
    1. 符合单一职责原则。
    2. 优秀的扩展性。
    3. 灵活性。
  • 缺点:
    1. 具体元素对访问者公布细节,违反了迪米特原则。
    2. 具体元素变更比较困难。
    3. 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

举例

  • 电脑、键盘、鼠标的访问操作。
class computerPart {
    accept(computerPartVisitor) { }
}
class KeyBoard extends computerPart {
    accept(computerPartVisitor) {
        computerPartVisitor.visitKeyBoard(this);
    }
}
class Mouse extends computerPart {
    accept(computerPartVisitor) {
        computerPartVisitor.visitMouse(this);
    }
}

class Computer extends computerPart {
    constructor() {
        super();
        this.parts = [new KeyBoard(), new Mouse()];
    }

    accept(computerPartVisitor) {
        for (let item of this.parts) {
            item.accept(computerPartVisitor);
        }
        computerPartVisitor.visitComputer(this);
    }
}

class ComputerPartVisitor {

    visitComputer(computer) { };
    visitMouse(mouse) { };
    visitKeyBoard(keyboard) { };
}

class ComputerPartDisplayVisitor extends ComputerPartVisitor {
    visitComputer(computer) {
        console.log("Displaying Computer.");
    }
    visitMouse(mouse) {
        console.log("Displaying Mouse.");
    }
    visitKeyBoard(keyboard) {
        console.log("Displaying Keyboard.");
    }
}

function demo() {
    const computer = new Computer();
    computer.accept(new ComputerPartDisplayVisitor());
}

demo()

image.png

总结

  • 行为型模式描述程序在运行时复杂的流程控制,即多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
  • 行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。

更多优质文章

「点赞、收藏和评论」

❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章,谢谢🙏大家。