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

1,349 阅读3分钟

Github 源码地址

责任链模式

简介

  • 解决:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
  • 使用:在处理消息的时候以过滤很多道。
  • 方式:拦截的类都实现统一接口。
  • 场景:JS 中的事件冒泡;红楼梦中的"击鼓传花"。

优缺点

  • 优点:
    1. 降低耦合度。它将请求的发送者和接收者解耦。
    2. 简化了对象。使得对象不需要知道链的结构。
    3. 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
    4. 增加新的请求处理类很方便。
  • 缺点:
    1. 不能保证请求一定被接收。
    2. 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
    3. 可能不容易观察运行时的特征,有碍于除错。

举例

  • 日志记录级别。
const INFO = 1;
const DEBUG = 2;
const ERROR = 3;
class AbstractLogger {
    constructor() {
        this.nextLogger = null;
    }

    setNextLogger(nextLogger) {
        this.nextLogger = nextLogger;
    }

    logMessage(level, message) {
        if (this.level <= level) {
            this.write(message);
        }
        if (this.nextLogger !== null) {
            this.nextLogger.logMessage(level, message);
        }
    }
    write(message) {
        console.log('message', message);
    }
}

class ConsoleLogger extends AbstractLogger {
    constructor(level) {
        super();
        this.level = level;
    }
    write(message) {
        console.log("Console Logger: " + message);
    }
}
class ErrorLogger extends AbstractLogger {
    constructor(level) {
        super();
        this.level = level;
    }
    write(message) {
        console.log("Error Logger: " + message);
    }
}
class FileLogger extends AbstractLogger {
    constructor(level) {
        super();
        this.level = level;
    }
    write(message) {
        console.log("File Logger: " + message);
    }
}

function getChainOfLoggers() {
    const errorLogger = new ErrorLogger(ERROR);
    const fileLogger = new FileLogger(DEBUG);
    const consoleLogger = new ConsoleLogger(INFO);

    errorLogger.setNextLogger(fileLogger);
    fileLogger.setNextLogger(consoleLogger);

    return errorLogger;
}

function demo() {
    const loggerChain = getChainOfLoggers();
    loggerChain.logMessage(INFO, "This is an information.");

    loggerChain.logMessage(DEBUG,
        "This is a debug level information.");

    loggerChain.logMessage(ERROR,
        "This is an error information.");
}

demo()

image.png

命令模式

简介

  • 解决:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
  • 使用:对行为进行"记录、撤销/重做、事务"等处理。
  • 方式:通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。
  • 场景:认为是命令的地方。

优缺点

  • 优点:
    1. 降低了系统耦合度。
    2. 新的命令可以很容易添加到系统中去。
  • 缺点:使用命令模式可能会导致某些系统有过多的具体命令类。

举例

  • 下不同的订单。
class Order {
    execute() { }
}
class Stock {
    constructor(name, quantity) {
        this.name = name;
        this.quantity = quantity;
    }

    buy() {
        console.log("Stock [ Name: " + this.name + ", Quantity: " + this.quantity + " ] bought");
    }

    sell() {
        console.log("Stock [ Name: " + this.name + ", Quantity: " + this.quantity + " ] sold");
    }
}

class BuyStock extends Order {
    constructor(abcStock) {
        super();
        this.abcStock = abcStock;
    }
    execute() {
        this.abcStock.buy();
    }
}
class SellStock extends Order {
    constructor(abcStock) {
        super();
        this.abcStock = abcStock;
    }
    execute() {
        this.abcStock.sell();
    }
}
class Broker {
    constructor() {
        this.orderList = [];
    }
    takeOrder(order) {
        this.orderList.unshift(order);
    }
    placeOrders() {
        for (let order of this.orderList) {
            order.execute();
        }
        this.orderList = [];
    }
}

function demo() {
    const _17Stock = new Stock('xxx17', 17);
    const _7Stock = new Stock('x7', 7);
    const broker = new Broker();
    const sell17Stock = new SellStock(_17Stock);
    const sell7Stock = new SellStock(_7Stock);
    const buy7Stock = new BuyStock(_7Stock);
    const buy17Stock = new BuyStock(_17Stock);
    broker.takeOrder(sell17Stock);
    broker.takeOrder(sell7Stock);
    broker.takeOrder(buy17Stock);
    broker.takeOrder(buy7Stock);

    broker.placeOrders();

}

demo()

image.png

解释器模式

简介

  • 解决:对于一些固定文法构建一个解释句子的解释器。
  • 使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。
  • 方式:构建语法树,定义终结符与非终结符。
  • 场景:编译器、运算表达式计算。

优缺点

  • 优点:
    1. 可扩展性比较好,灵活。
    2. 增加了新的解释表达式的方式。
    3. 易于实现简单文法。
  • 缺点:
    1. 可利用场景比较少。
    2. 对于复杂的文法比较难维护。
    3. 解释器模式会引起类膨胀。
    4. 解释器模式采用递归调用方法。

举例

  • 对一个人的性别、是否已婚等辨别。
class Expression {
    interpret(context) { }
}

class TerminalExpression extends Expression {
    constructor(data) {
        super();
        this.data = data;
    }
    interpret(context) {
        if (context.includes(this.data)) {
            return true
        }
        return false;
    }
}
class OrExpression extends Expression {
    constructor(expr1, expr2) {
        super();
        this.expr1 = expr1;
        this.expr2 = expr2;
    }
    interpret(context) {
        return this.expr1.interpret(context) || this.expr2.interpret(context);
    }
}
class AndExpression extends Expression {
    constructor(expr1, expr2) {
        super();
        this.expr1 = expr1;
        this.expr2 = expr2;
    }
    interpret(context) {
        return this.expr1.interpret(context) && this.expr2.interpret(context);
    }
}
function demo() {
    //规则:Robert 和 John 是男性
    function getMaleExpression() {
        const robert = new TerminalExpression("Robert");
        const john = new TerminalExpression("John");
        return new OrExpression(robert, john);
    }

    //规则:Julie 是一个已婚的女性
    function getMarriedWomanExpression() {
        const julie = new TerminalExpression("Julie");
        const married = new TerminalExpression("Married");
        return new AndExpression(julie, married);
    }

    const isMale = getMaleExpression();
    const isMarriedWoman = getMarriedWomanExpression();

    console.log("John is male? " + isMale.interpret("John"));
    console.log("Julie is a married women? " + isMarriedWoman.interpret("Married Julie"));
}

demo()

image.png

迭代器模式

简介

  • 解决:不同的方式来遍历整个整合对象。
  • 使用:遍历一个聚合对象。
  • 方式:把在元素之间游走的责任交给迭代器,而不是聚合对象。
  • 场景:访问一个聚合对象的内容而无须暴露它的内部表示;需要为聚合对象提供多种遍历方式;为遍历不同的聚合结构提供一个统一的接口。

优缺点

  • 优点:
    1. 它支持以不同的方式遍历一个聚合对象。
    2. 迭代器简化了聚合类。
    3. 在同一个聚合上可以有多个遍历。
    4. 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
  • 缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

举例

  • 实现一个姓名的迭代打印。
class Iterator {
    hasNext() { }
    next() { }
}

class Container {
    getIterator() { }
}

// 获取姓名
class NameRepository extends Container {
    constructor(names) {
        super();
        this.names = names;
    }
    getIterator() {
        return new NameIterator(this.names);
    }
}

// 姓名迭代器
class NameIterator extends Iterator {
    constructor(names) {
        super();
        this.index = 0;
        this.names = names;
    }
    hasNext() {
        if (this.index < this.names.length) {
            return true
        }
        return false;
    }
    next() {
        if (this.hasNext()) {
            return this.names[this.index++];
        }
        return null
    }
}
function demo() {
    const NAMES = ['detanx', '17', 'xxx17', 'x17'];
    const nameRepository = new NameRepository(NAMES);
    for (let item = nameRepository.getIterator(); item.hasNext();) {
        console.log('name: ', item.next());
    }
}

demo()

image.png

中介者模式

简介

  • 解决:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
  • 使用:多个类相互耦合,形成了网状结构。
  • 方式:将上述网状结构分离为星型结构。
  • 场景:机场调度系统;MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。

优缺点

  • 优点:
    1. 降低了类的复杂度,将一对多转化成了一对一。
    2. 各个类之间的解耦。
    3. 符合迪米特原则。
  • 缺点:中介者会庞大,变得复杂难以维护。

举例

  • 聊天室
class ChatRoom {
    showMessage(user, message) {
        console.log(new Date().toString() + " [" + user.getName() + "] : " + message);
    }
}

class User {
    constructor(name, message) {
        this.name = name;
    }
    setName(name) {
        this.name = name;
    }
    getName(name) {
        return this.name;
    }
    sendMessage(message) {
        new ChatRoom().showMessage(this, message);
    }
}

function demo() {
    const robert = new User("Robert");
    const john = new User("John");

    robert.sendMessage("Hi! John!");
    setTimeout(() => {
        john.sendMessage("Hello! Robert!");
    }, 2000)
}

demo()

image.png

备忘录模式

简介

  • 解决:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
  • 使用:很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态。
  • 方式:通过一个备忘录类专门存储对象状态。
  • 场景:打游戏时的存档 Windows 里的 ctri + z;浏览器中的后退;“后悔药”;数据库的事务管理。

优缺点

  • 优点:
    1. 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
    2. 实现了信息的封装,使得用户不需要关心状态的保存细节。
  • 缺点:消耗资源。

举例

  • 实现一个状态的存档。
class Memento {
    constructor(state) {
        this.state = state;
    }
    getState() {
        return this.state;
    }
}

class Originator {
    constructor(state) {
        this.state = state;
    }
    getState() {
        return this.state;
    }
    setState(state) {
        this.state = state;
    }
    // 存入备忘录
    saveStateToMemento() {
        return new Memento(this.state);
    }
    // 取出
    getStateFromMemento(Memento) {
        if (!Memento) {
            this.state = null;
        } else {
            this.state = Memento.getState();
        }
    }
}
class CareTaker {
    constructor() {
        this.mementoList = [];
    }
    add(state) {
        this.mementoList.push(state);
    }

    get(index) {
        return this.mementoList[index];
    }
}

function demo() {

    const originator = new Originator();
    const careTaker = new CareTaker();
    originator.setState("State #1");
    originator.setState("State #2");
    careTaker.add(originator.saveStateToMemento());
    originator.setState("State #3");
    careTaker.add(originator.saveStateToMemento());
    originator.setState("State #4");

    console.log("Current State: " + originator.getState());
    originator.getStateFromMemento(careTaker.get(0));
    console.log("First saved State: " + originator.getState());
    originator.getStateFromMemento(careTaker.get(2));
    console.log("Third saved State: " + originator.getState());
}

demo()

image.png

总结

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

更多优质文章

「点赞、收藏和评论」

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