關於裝飾器模式和責任鏈模式

裝飾器模式和責任鏈模式,是兩個看起來很相像的模式,這篇文章會簡單的探討他們之前的差別

淺談責任鏈(Chain of Responsibility)和裝飾器(Decorator)模式的差別

裝飾器模式和責任鍊模式,都是很常見用來把程式碼邏輯切開,讓不同元件能更明確用code表達其負責的邏輯的方法。

以下先簡單介紹一下這兩種模式

責任鏈模式(Chain of Responsibility)

接收輸入物件並沿鏈傳遞請求,直到其中一個handler處理它為止。 從重構的角度來看,當發現code的邏輯可以被切成不同的角色負責的邏輯時,可以重構成責任鍊的形式,分成不同的角色Class,讓各自的邏輯交由不同角色Class來處理。

範例

要實作一個HandlerRequest,它可以處理Auth, Log, Data三種類型的Request 使用的時候要像下面例子這樣使用。

func main() {
    req1 := &Request{Type: "Auth", Payload: "User credentials"}
    req2 := &Request{Type: "Log", Payload: "User activity"}
    req3 := &Request{Type: "Data", Payload: "User data"}

    fmt.Println(HandleRequest(req1))
    fmt.Println(HandleRequest(req2))
    fmt.Println(HandleRequest(req3))
}

未使用責任鍊模式的HandlerRequest

func HandleRequest(request *Request) string {
    if request.Type == "Auth" {
        return fmt.Sprintf("Handling authentication: %s", request.Payload)
    } else if request.Type == "Log" {
        return fmt.Sprintf("Logging data: %s", request.Payload)
    } else if request.Type == "Data" {
        return fmt.Sprintf("Processing data: %s", request.Payload)
    } else {
        return fmt.Sprintf("Unknown request type: %s", request.Type)
    }
}

使用責任鏈模式的HandlerRequest

要先定義interface & Base struct

// Handler 定義處理器接口
type Handler interface {
	Handle(request *Request) string
}
// BaseHandler 實作責任鍊的pass邏輯
type BaseHandler struct {
	next Handler
}

//只在其他Handler的Constructor中使用
func (h *BaseHandler) setNext(next Handler) {
	h.next = next
}

func (h *BaseHandler) handleNext(request *Request) string{
	if h.next != nil {
		return h.next.Handle(request)
	}
    return ""
}

接著實作AuthHandler, LogHandler, DataHandler

AuthHandler:

// AuthHandler 處理認證請求,並且有自己的參數
type AuthHandler struct {
	BaseHandler
	authKey string
}

func NewAuthHandler(authKey string, next Handler) *AuthHandler {
	handler := &AuthHandler{
		authKey: authKey,
	}
	handler.setNext(next) // 設置下一個處理器
	return handler
}

func (h *AuthHandler) Handle(request *Request) string {
	if request.Type == "Auth" {
		return fmt.Sprintf("Handling authentication with key: %s, Payload: %s", h.authKey, request.Payload)
	} else {
		return h.handleNext(request)
	}
}

LogHandler:

// LogHandler 處理日誌請求,並且有自己的參數
type LogHandler struct {
	BaseHandler
	logLevel string
}

func NewLogHandler(logLevel string, next Handler) *LogHandler {
	handler := &LogHandler{
		logLevel: logLevel,
	}
	handler.setNext(next) // 設置下一個處理器
	return handler
}

func (h *LogHandler) Handle(request *Request)string {
	if request.Type == "Log" {
		fmt.Sprintf("Logging at level: %s, Payload: %s", h.logLevel, request.Payload)
	} else {
		return h.handleNext(request)
	}
}  

DataHandler:

// DataHandler 處理數據請求,並且有自己的參數
type DataHandler struct {
	BaseHandler
	dataSource string
}

func NewDataHandler(dataSource string, next Handler) *DataHandler {
	handler := &DataHandler{
		dataSource: dataSource,
	}
	handler.setNext(next) // 設置下一個處理器
	return handler
}

func (h *DataHandler) Handle(request *Request) string {
	if request.Type == "Data" {
		return fmt.Sprintf("Processing data from source: %s, Payload:%s", h.dataSource, request.Payload)
	} else {
		return h.handleNext(request)
	}
}

最後,在main()需要多加上把不同的handler串成一個鍊(這邊是使用constructor注入的方式設定)

func main() {
	// 初始化處理器
	authHandler := NewAuthHandler()
	logHandler := NewLogHandler(authHandler)
	dataHandler := NewDataHandler(logHandler)

	// 創建請求
	req1 := &Request{Type: "Auth", Payload: "User credentials"}
	req2 := &Request{Type: "Log", Payload: "User activity"}
	req3 := &Request{Type: "Data", Payload: "User data"}

	// 處理請求
	fmt.Println(authHandler.Handle(req1))
    fmt.Println(authHandler.Handle(req2))
	fmt.Println(authHandler.Handle(req3))
}

這樣做的優勢是,如果要修改Auth相關的邏輯,只要到AuthHandler去改即可。

如果沒有使用責任鏈,當HandlerRequest內部使用的handler數量變得很大,並且決定要不要使用某個handler的邏輯很複雜時,邏輯甚至會互相糾結,讓code就會變得非常的難修改和維護。

裝飾器模式(Decorator)

在現已有的邏輯上,外掛上額外的邏輯。

範例

一樣是上面的requestHandler,但現在要加上一個額外的功能:不論處理成功或失敗,都要把結果記錄寫成log。

這個外掛的功能就可以用decorator來實作,它並不會影響到核心功能,只是額外加其它功能上去。

// LogDecorator 處理數據請求,並且有自己的參數
type LogDecorator struct {
	BaseHandler
}

func NewLogDecorator(next Handler) *DataHandler {
	handler := &LogDecorator{}
	handler.setNext(next) // 設置下一個處理器
	return handler
}

func (h *LogDecorator) Handle(request *Request) {
	if h.handlerNext(request) != "" {
      log.Info("request success")
    }else{
      log.Info("request failed")
    }
}

結論

從實作方面來看,責任鏈和裝飾器的code是大同小異,都是藉由實作相同的interface,把多個邏輯拆分到不同的物件上處理。最後把Object依照順序一層層的注入到其它Object上,形成一個執行的鏈。它們唯一的差別是兩者切分邏輯的方式。

責任鏈是鏈上的每個Object都能獨立完成一種使用情境的邏輯;而裝飾器則是為核心邏輯外掛其它額外的功能。

當有核心功能存在,需要外掛附加功能時,這個做法就可以稱為裝飾器模式;而當有不同的handler, 並且每個都可以獨立處理完某一種使用情境時,就稱其為責任鏈模式。一切都是看想要切分的邏輯,是切出核心邏輯和外掛邏輯(裝飾器), 還是切出不同的職責,每個都是一種情境的核心邏輯(責任鏈)。他們的形式可以長得一樣,差別只在於他們切分產生出來的handler能否獨立完成一種工作。

資料來源

comments powered by Disqus