缘起

最近复习设计模式
拜读谭勇德的<<设计模式就该这样学>>
本系列笔记拟采用golang练习之

责任链模式

  1. 责任链模式(Chain of Responsibility Pattern)将链中每一个节点都看作一个对象,
  2. 每个节点处理的请求均不同,
  3. 且内部自动维护下一个节点对象。
  4. 当一个请求从链式的首端发出时,
  5. 会沿着责任链预设的路径依次传递到每一个节点对象,
  6. 直至被链中的某个对象处理为止,
  7. 属于行为型设计模式。
  8. 责任链模式主要适用于以下应用场景。
  9. 1)多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定。
  10. 2)在不明确指定接收者的情况下,向多个对象中的一个提交请求。
  11. 3)可动态指定一组对象处理请求。
  12. (摘自 谭勇德 <<设计模式就该这样学>>)

场景

  • 某业务系统, 需要将日志按严重等级(Debug/Info/Error), 分开不同文件
  • 码农王二狗, 于是设计了DebugLogger, InfoLogger, ErrorLogger三个日志类
  • 业务层根据日志输出等级, 分别调用不同的logger
  • Leader张阿蛋审阅后非常不满意
    • 张阿蛋: 狗子, 写个日志还得调用三个类, 业务team的人还不把我们骂得狗血淋头
    • 王二狗: …张哥, 那你的意见是?
    • 张阿蛋: 就一个ILogger门面接口, 把Debug/Info/Error方法都放进去; 里面用个责任链, Debug/Info/Error各自做一个节点.
    • 王二狗: 张哥, 强!

设计

  • ILogger: 定义日志器门面接口
  • tSimpleLogger: 日志器门面, 实现ILogger接口, 内部使用责任链模式分别处理Debug/Info/Error请求
  • ILoggerFilter: 定义日志责任链节点的接口
  • tLoggerFilter: 日志责任链节点, 实现ILoggerFilter接口
  • tFileWriter: 具体负责日志输出, 实现io.StringWriter接口

单元测试

chain_responsibility_test.go

  1. package behavioral_patterns
  2. import (
  3. "learning/gooop/behavioral_patterns/chain"
  4. "testing"
  5. )
  6. func Test_ChainResponsibility(t *testing.T) {
  7. logger := chain.NewSimpleLogger()
  8. logger.Debug("a debug msg")
  9. logger.Info("an info msg")
  10. logger.Error("an error msg")
  11. }

测试输出

  1. $ go test -v chain_responsibility_test.go
  2. === RUN Test_ChainResponsibility
  3. tFileWriter.WriteString, file=debug.log, msg=DEBUG a debug msg
  4. tFileWriter.WriteString, file=info.log, msg=INFO an info msg
  5. tFileWriter.WriteString, file=error.log, msg=ERROR an error msg
  6. --- PASS: Test_ChainResponsibility (0.00s)
  7. PASS
  8. ok command-line-arguments 0.002s

ILogger.go

定义日志器门面接口

  1. package chain
  2. type ILogger interface {
  3. Debug(msg string)
  4. Info(msg string)
  5. Error(msg string)
  6. }

tSimpleLogger.go

日志器门面, 实现ILogger接口, 内部使用责任链模式分别处理Debug/Info/Error请求

  1. package chain
  2. type tSimpleLogger struct {
  3. chain ILoggerFilter
  4. }
  5. func NewSimpleLogger() ILogger {
  6. vErrorLogger := newLoggerFilter(newFileWriter("error.log"), LEVEL_ERROR, nil)
  7. vInfoLogger := newLoggerFilter(newFileWriter("info.log"), LEVEL_INFO, nil)
  8. vDebugLogger := newLoggerFilter(newFileWriter("debug.log"), LEVEL_DEBUG, nil)
  9. vDebugLogger.Next(vInfoLogger)
  10. vInfoLogger.Next(vErrorLogger)
  11. return &tSimpleLogger {
  12. vDebugLogger,
  13. }
  14. }
  15. func (me *tSimpleLogger) Debug(msg string) {
  16. me.chain.Handle(LEVEL_DEBUG, msg)
  17. }
  18. func (me *tSimpleLogger) Info(msg string) {
  19. me.chain.Handle(LEVEL_INFO, msg)
  20. }
  21. func (me *tSimpleLogger) Error(msg string) {
  22. me.chain.Handle(LEVEL_ERROR, msg)
  23. }

ILoggerFilter.go

定义日志责任链节点的接口

  1. package chain
  2. type LoggingLevel string
  3. const LEVEL_DEBUG LoggingLevel = "DEBUG"
  4. const LEVEL_INFO LoggingLevel = "INFO"
  5. const LEVEL_ERROR LoggingLevel = "ERROR"
  6. type ILoggerFilter interface {
  7. Next(filter ILoggerFilter)
  8. Handle(level LoggingLevel, msg string)
  9. }

tLoggerFilter.go

日志责任链节点, 实现ILoggerFilter接口

  1. package chain
  2. import (
  3. "fmt"
  4. "io"
  5. )
  6. type tLoggerFilter struct {
  7. writer io.StringWriter
  8. level LoggingLevel
  9. chain ILoggerFilter
  10. }
  11. func newLoggerFilter(writer io.StringWriter, level LoggingLevel, filter ILoggerFilter) ILoggerFilter {
  12. return &tLoggerFilter{
  13. writer, level, filter,
  14. }
  15. }
  16. func (me *tLoggerFilter) Next(filter ILoggerFilter) {
  17. me.chain = filter
  18. }
  19. func (me *tLoggerFilter) Handle(level LoggingLevel, msg string) {
  20. if me.level == level {
  21. _,_ = me.writer.WriteString(fmt.Sprintf("%v %s", me.level, msg))
  22. } else {
  23. if me.chain != nil {
  24. me.chain.Handle(level, msg)
  25. }
  26. }
  27. }

tFileWriter.go

具体负责日志输出, 实现io.StringWriter接口

  1. package chain
  2. import (
  3. "fmt"
  4. "io"
  5. )
  6. type tFileWriter struct {
  7. file string
  8. }
  9. func newFileWriter(file string) io.StringWriter {
  10. return &tFileWriter{
  11. file,
  12. }
  13. }
  14. func (me *tFileWriter) WriteString(s string) (n int, e error) {
  15. fmt.Printf("tFileWriter.WriteString, file=%s, msg=%s\n", me.file, s)
  16. return len(s), nil
  17. }

责任链模式小结

  1. 责任链模式的优点
  2. 1)将请求与处理解耦。
  3. 2)请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转发给下一个节点对象。
  4. 3)具备链式传递处理请求功能,请求发送者不需要知晓链路结构,只需等待请求处理结果即可。
  5. 4)链路结构灵活,可以通过改变链路结构动态地新增或删减责任。
  6. 5)易于扩展新的请求处理类(节点),符合开闭原则。
  7. 责任链模式的缺点
  8. 1)责任链太长或者处理时间过长,会影响整体性能。
  9. 2)如果节点对象存在循环引用,则会造成死循环,导致系统崩溃。
  10. (摘自 谭勇德 <<设计模式就该这样学>>)

(end)