设计模式消失之谜 - 责任链模式

设计模式消失之谜 - 责任链模式

sjmyuan 83 2022-10-12

责任链模式是面向对象编程中的一种经典设计模式,但它却在函数式编程中消失了,为什么?要回答这个问题,最简单的方法是看函数式编程如何解决同样的问题。模式也许会消失,但其要解决的问题不会消失。

以日志打印器为例,我们希望不同级别的日志由不同的打印器进行打印,且打印器的具体细节对调用方是不可见的。

面向对象编程的解决方案

enum LogLevel {
    DEBUG, INFO, ERROR
}


abstract class Logger {
    protected Logger nextLogger;

    public void setNextLogger(Logger nextLogger) {
        this.nextLogger = nextLogger;
    }

    public void logMessage(LogLevel level, String message) {
        if (isDefinedAt(level)) {
            writeMessage(message);
        }

        if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }

    protected abstract boolean isDefinedAt(LogLevel level);

    protected abstract void writeMessage(String message);
}


class DebugLogger extends Logger {
    public boolean isDefinedAt(LogLevel level) {
        return LogLevel.DEBUG == level;
    }

    public void writeMessage(String message) {
        System.out.println("[DEBUG] - " + message);
    }
}


class InfoLogger extends Logger {
    public boolean isDefinedAt(LogLevel level) {
        return LogLevel.INFO == level;
    }

    public void writeMessage(String message) {
        System.out.println("[INFO] - " + message);
    }
}


class ErrorLogger extends Logger {
    public boolean isDefinedAt(LogLevel level) {
        return LogLevel.ERROR == level;
    }

    public void writeMessage(String message) {
        System.out.println("[ERROR] - " + message);
    }
}


class ChainOfResponsibility {
    public static void main(String[] args) {
        Logger debugLogger = new DebugLogger();
        Logger infoLogger = new InfoLogger();
        Logger errorLogger = new ErrorLogger();

        infoLogger.setNextLogger(errorLogger);
        debugLogger.setNextLogger(infoLogger);

        Logger logger = debugLogger;

        logger.logMessage(LogLevel.DEBUG, "This is a debug message.");
        logger.logMessage(LogLevel.INFO, "This is an information.");
        logger.logMessage(LogLevel.ERROR, "This is a error message.");
    }
}

输出结果为

[DEBUG] - This is a debug message.
[INFO] - This is an information.
[ERROR] - This is a error message.

函数式编程的解决方案

enum LogLevel:
  case DEBUG, INFO, ERROR

case class Message(level: LogLevel, content: String)

object ChainOfResponsibility extends App {

  val debugLogger: PartialFunction[Message, Unit] = {
    case Message(LogLevel.DEBUG, content) =>
      println(s"[DEBUG] - ${content}")
  }

  val infoLogger: PartialFunction[Message, Unit] = {
    case Message(LogLevel.INFO, content) =>
      println(s"[INFO] - ${content}")
  }

  val errorLogger: PartialFunction[Message, Unit] = {
    case Message(LogLevel.ERROR, content) =>
      println(s"[ERROR] - ${content}")
  }

  val logger = debugLogger orElse infoLogger orElse errorLogger

  logger(Message(LogLevel.DEBUG, "This is a debug message"))
  logger(Message(LogLevel.INFO, "This is an information"))
  logger(Message(LogLevel.ERROR, "This is a error message"))
}

输出结果为

[DEBUG] - This is a debug message.
[INFO] - This is an information.
[ERROR] - This is a error message.

谜底

函数式编程使用了PartialFunction​来解决该问题,它只对部分输入有响应。其定义为

trait PartialFunction[-A, +B] extends (A => B) {
  def isDefinedAt(x: A): Boolean
  def apply(x: A): B
  def applyOrElse[A1 <: A, B1 >: B](x: A1, default: A1 => B1): B1 =
    if (isDefinedAt(x)) apply(x) else default(x)
}

{ case _ => ???}​是创建PartialFunction的一个语法糖。

我们看到isDefinedAt​和apply​可以与Logger​中的isDefinedAt​和writeMessage​一一对应。其不同点是PartialFunction​只能接收一个参数,所以我们使用Message​对LogLevel​和content​进行了封装。

Logger​使用setNextLogger​来组装下一个Logger​,而PartialFunction​使用orElse​来组装下一个PartialFunction​。

def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]): PartialFunction[A1, B1] =
  new OrElse[A1, B1] (this, that)

其中OrElse​是PartialFunction​的子类,它存储了两个PartialFunction​并定义了其调用顺序。

Logger​通过logMessage​来控制责任的传递,而OrElse​通过apply​来控制责任传递

override def apply(x: A): B = f1.applyOrElse(x, f2)

现在我们可以揭开谜底了,PartialFunction​是对责任链模式的进一步抽象,是函数式编程语言的基本特性,不再需要我们自己搭建模式框架。

PartialFunction​对责任链模式添加了一些限制,例如

  1. 每个输入只能由一个PartialFunction​来处理,且越靠前优先级越高,而在Logger​中我们可以自由控制是否将责任传递给下一个Logger​。不过我们也可以实现自己的OrElse​子类来达到相同的目的。这里我们更应该思考的是,如果一个输入有多个处理者,它适合使用责任链模式来解决么?是否应该合并处理逻辑或使用订阅者模式呢?
  2. PartialFunction​只能有一个入参,而我们自己搭建的模式框架可以支持多个入参。不过我们可以将所有参数封装到一起来达到相同的目的。
  3. 如果不能处理输入,PartialFunction​会直接报错,而在Logger​中我们可以控制是报错还是直接返回。不过我们可以通过PartialFunction.lift​将PartialFunction​转换为一个类型为A => Option[B]​的Function​,这样返回None​就意味着输入无法处理,调用者可以自己决定是报错还是忽略。

总结

责任链模式在函数式编程中并没有消失,而是变成了一个原生特性PartialFunction​,我们不需要自己搭建模式框架,直接使用就好。文中的代码都已上传到design-pattern-in-fp,欢迎大家批评指正。