色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

GoF給裝飾者模式的定義

元閏子的邀請 ? 來源:元閏子的邀請 ? 作者:元閏子的邀請 ? 2022-06-29 10:22 ? 次閱讀

上一篇:【Go實現】實踐GoF的23種設計模式:原型模式

簡單的分布式應用系統(示例代碼工程):https://github.com/ruanrunxue/Practice-Design-Pattern--Go-Implementation

簡介

我們經常會遇到“給現有對象/模塊新增功能”的場景,比如 http router 的開發場景下,除了最基礎的路由功能之外,我們常常還會加上如日志、鑒權、流控等 middleware。如果你查看框架的源碼,就會發現 middleware 功能的實現用的就是裝飾者模式(Decorator Pattern)。

GoF給裝飾者模式的定義如下:

Decorators provide a flexible alternative to subclassing for extending functionality. Attach additional responsibilities to an object dynamically.

簡單來說,裝飾者模式通過組合的方式,提供了能夠動態地給對象/模塊擴展新功能的能力。理論上,只要沒有限制,它可以一直把功能疊加下去,具有很高的靈活性。

如果寫過 Java,那么一定對 I/O Stream 體系不陌生,它是裝飾者模式的經典用法,客戶端程序可以動態地為原始的輸入輸出流添加功能,比如按字符串輸入輸出,加入緩沖等,使得整個 I/O Stream 體系具有很高的可擴展性和靈活性。

UML 結構

0c30719e-f700-11ec-ba43-dac502259ad0.jpg

場景上下文

在簡單的分布式應用系統(示例代碼工程)中,我們設計了 Sidecar 邊車模塊,它的用處主要是為了 1)方便擴展network.Socket的功能,如增加日志、流控等非業務功能;2)讓這些附加功能對業務程序隱藏起來,也即業務程序只須關心看到network.Socket接口即可。

0c4d81d0-f700-11ec-ba43-dac502259ad0.jpg

代碼實現

Sidecar 的這個功能場景,很適合使用裝飾者模式來實現,代碼如下:

//demo/network/socket.go
packagenetwork

//關鍵點1:定義被裝飾的抽象接口
//Socket網絡通信Socket接口
typeSocketinterface{
//Listen在endpoint指向地址上起監聽
Listen(endpointEndpoint)error
//Close關閉監聽
Close(endpointEndpoint)
//Send發送網絡報文
Send(packet*Packet)error
//Receive接收網絡報文
Receive(packet*Packet)
//AddListener增加網絡報文監聽者
AddListener(listenerSocketListener)
}

//關鍵點2:提供一個默認的基礎實現
typesocketImplstruct{
listenerSocketListener
}

funcDefaultSocket()*socketImpl{
return&socketImpl{}
}

func(s*socketImpl)Listen(endpointEndpoint)error{
returnInstance().Listen(endpoint,s)
}
...//socketImpl的其他Socket實現方法


//demo/sidecar/flowctrl_sidecar.go
packagesidecar

//關鍵點3:定義裝飾器,實現被裝飾的接口
//FlowCtrlSidecarHTTP接收端流控功能裝飾器,自動攔截Socket接收報文,實現流控功能
typeFlowCtrlSidecarstruct{
//關鍵點4:裝飾器持有被裝飾的抽象接口作為成員屬性
socketnetwork.Socket
ctx*flowctrl.Context
}

//關鍵點5:對于需要擴展功能的方法,新增擴展功能
func(f*FlowCtrlSidecar)Receive(packet*network.Packet){
httpReq,ok:=packet.Payload().(*http.Request)
//如果不是HTTP請求,則不做流控處理
if!ok{
f.socket.Receive(packet)
return
}
//流控后返回429TooManyRequest響應
if!f.ctx.TryAccept(){
httpResp:=http.ResponseOfId(httpReq.ReqId()).
AddStatusCode(http.StatusTooManyRequest).
AddProblemDetails("enterflowctrlstate")
f.socket.Send(network.NewPacket(packet.Dest(),packet.Src(),httpResp))
return
}
f.socket.Receive(packet)
}

//關鍵點6:不需要擴展功能的方法,直接調用被裝飾接口的原生方法即可
func(f*FlowCtrlSidecar)Close(endpointnetwork.Endpoint){
f.socket.Close(endpoint)
}
...//FlowCtrlSidecar的其他方法

//關鍵點7:定義裝飾器的工廠方法,入參為被裝飾接口
funcNewFlowCtrlSidecar(socketnetwork.Socket)*FlowCtrlSidecar{
return&FlowCtrlSidecar{
socket:socket,
ctx:flowctrl.NewContext(),
}
}

//demo/sidecar/all_in_one_sidecar_factory.go
//關鍵點8:使用時,通過裝飾器的工廠方法,把所有裝飾器和被裝飾者串聯起來
func(aAllInOneFactory)Create()network.Socket{
returnNewAccessLogSidecar(NewFlowCtrlSidecar(network.DefaultSocket()),a.producer)
}

總結實現裝飾者模式的幾個關鍵點:

  1. 定義需要被裝飾的抽象接口,后續的裝飾器都是基于該接口進行擴展。
  2. 為抽象接口提供一個基礎實現。
  3. 定義裝飾器,并實現被裝飾的抽象接口。
  4. 裝飾器持有被裝飾的抽象接口作為成員屬性。“裝飾”的意思是在原有功能的基礎上擴展新功能,因此必須持有原有功能的抽象接口。
  5. 在裝飾器中,對于需要擴展功能的方法,新增擴展功能。
  6. 不需要擴展功能的方法,直接調用被裝飾接口的原生方法即可
  7. 為裝飾器定義一個工廠方法,入參為被裝飾接口。
  8. 使用時,通過裝飾器的工廠方法,把所有裝飾器和被裝飾者串聯起來。

擴展

Go 風格的實現

在 Sidecar 的場景上下文中,被裝飾的Socket是一個相對復雜的接口,裝飾器通過實現Socket接口來進行功能擴展,是典型的面向對象風格。

如果被裝飾者是一個簡單的接口/方法/函數,我們可以用更具 Go 風格的實現方式,考慮前文提到的 http router 場景。如果你使用原生的net/http進行 http router 開發,通常會這么實現:

funcmain(){
//注冊/hello的router
http.HandleFunc("/hello",hello)
//啟動http服務器
http.ListenAndServe("localhost:8080",nil)
}

//具體的請求處理邏輯,類型是http.HandlerFunc
funchello(whttp.ResponseWriter,r*http.Request){
w.Write([]byte("hello,world"))
}

其中,我們通過http.HandleFunc來注冊具體的 router,hello是具體的請求處理方法。現在,我們想為該 http 服務器增加日志、鑒權等通用功能,那么可以把func(w http.ResponseWriter, r *http.Request)作為被裝飾的抽象接口,通過新增日志、鑒權等裝飾器完成功能擴展。

//demo/network/http/http_handle_func_decorator.go

//關鍵點1:確定被裝飾接口,這里為原生的http.HandlerFunc
typeHandlerFuncfunc(ResponseWriter,*Request)

//關鍵點2:定義裝飾器類型,是一個函數類型,入參和返回值都是http.HandlerFunc函數
typeHttpHandlerFuncDecoratorfunc(http.HandlerFunc)http.HandlerFunc

//關鍵點3:定義裝飾函數,入參為被裝飾的接口和裝飾器可變列表
funcDecorate(hhttp.HandlerFunc,decorators...HttpHandlerFuncDecorator)http.HandlerFunc{
//關鍵點4:通過for循環遍歷裝飾器,完成對被裝飾接口的裝飾
for_,decorator:=rangedecorators{
h=decorator(h)
}
returnh
}

//關鍵點5:實現具體的裝飾器
funcWithBasicAuth(hhttp.HandlerFunc)http.HandlerFunc{
returnfunc(whttp.ResponseWriter,r*http.Request){
cookie,err:=r.Cookie("Auth")
iferr!=nil||cookie.Value!="Pass"{
w.WriteHeader(http.StatusForbidden)
return
}
//關鍵點6:完成功能擴展之后,調用被裝飾的方法,才能將所有裝飾器和被裝飾者串起來
h(w,r)
}
}

funcWithLogger(hhttp.HandlerFunc)http.HandlerFunc{
returnfunc(whttp.ResponseWriter,r*http.Request){
log.Println(r.Form)
log.Printf("path%s",r.URL.Path)
h(w,r)
}
}

funchello(whttp.ResponseWriter,r*http.Request){
w.Write([]byte("hello,world"))
}

funcmain(){
//關鍵點7:通過Decorate函數完成對hello的裝飾
http.HandleFunc("/hello",Decorate(hello,WithLogger,WithBasicAuth))
//啟動http服務器
http.ListenAndServe("localhost:8080",nil)
}

上述的裝飾者模式的實現,用到了類似于Functional Options的技巧,也是巧妙利用了 Go 的函數式編程的特點,總結下來有如下幾個關鍵點:

  1. 確定被裝飾的接口,上述例子為http.HandlerFunc
  2. 定義裝飾器類型,是一個函數類型,入參和返回值都是被裝飾接口,上述例子為func(http.HandlerFunc) http.HandlerFunc
  3. 定義裝飾函數,入參為被裝飾的接口和裝飾器可變列表,上述例子為Decorate方法。
  4. 在裝飾方法中,通過for循環遍歷裝飾器,完成對被裝飾接口的裝飾。這里是用來類似Functional Options的技巧,一定要注意裝飾器的順序
  5. 實現具體的裝飾器,上述例子為WithBasicAuthWithLogger函數。
  6. 在裝飾器中,完成功能擴展之后,記得調用被裝飾者的接口,這樣才能將所有裝飾器和被裝飾者串起來。
  7. 在使用時,通過裝飾函數完成對被裝飾者的裝飾,上述例子為Decorate(hello, WithLogger, WithBasicAuth)

Go 標準庫中的裝飾者模式

在 Go 標準庫中,也有一個運用了裝飾者模式的模塊,就是context,其中關鍵的接口如下:

packagecontext

//被裝飾接口
typeContextinterface{
Deadline()(deadlinetime.Time,okbool)
Done()<-chanstruct{}
Err()error
Value(keyany)any
}

//cancel裝飾器
typecancelCtxstruct{
Context//被裝飾接口
musync.Mutex
doneatomic.Value
childrenmap[canceler]struct{}=
errerror
}
//cancel裝飾器的工廠方法
funcWithCancel(parentContext)(ctxContext,cancelCancelFunc){
//...
c:=newCancelCtx(parent)
propagateCancel(parent,&c)
return&c,func(){c.cancel(true,Canceled)}
}

//timer裝飾器
typetimerCtxstruct{
cancelCtx//被裝飾接口
timer*time.Timer

deadlinetime.Time
}
//timer裝飾器的工廠方法
funcWithDeadline(parentContext,dtime.Time)(Context,CancelFunc){
//...
c:=&timerCtx{
cancelCtx:newCancelCtx(parent),
deadline:d,
}
//...
returnc,func(){c.cancel(true,Canceled)}
}
//timer裝飾器的工廠方法
funcWithTimeout(parentContext,timeouttime.Duration)(Context,CancelFunc){
returnWithDeadline(parent,time.Now().Add(timeout))
}

//value裝飾器
typevalueCtxstruct{
Context//被裝飾接口
key,valany
}
//value裝飾器的工廠方法
funcWithValue(parentContext,key,valany)Context{
ifparent==nil{
panic("cannotcreatecontextfromnilparent")
}
//...
return&valueCtx{parent,key,val}
}
0c708ad6-f700-11ec-ba43-dac502259ad0.jpg

使用時,可以這樣:

//使用時,可以這樣
funcmain(){
ctx:=context.Background()
ctx=context.WithValue(ctx,"key1","value1")
ctx,_=context.WithTimeout(ctx,time.Duration(1))
ctx=context.WithValue(ctx,"key2","value2")
}

不管是 UML 結構,還是使用方法,context模塊都與傳統的裝飾者模式有一定出入,但也不妨礙context是裝飾者模式的典型運用。還是那句話,學習設計模式,不能只記住它的結構,而是學習其中的動機和原理

典型使用場景

  • I/O 流,比如為原始的 I/O 流增加緩沖、壓縮等功能。
  • Http Router,比如為基礎的 Http Router 能力增加日志、鑒權、Cookie等功能。
  • ......

優缺點

優點

  1. 遵循開閉原則,能夠在不修改老代碼的情況下擴展新功能。
  2. 可以用多個裝飾器把多個功能組合起來,理論上可以無限組合。

缺點

  1. 一定要注意裝飾器裝飾的順序,否則容易出現不在預期內的行為。
  2. 當裝飾器越來越多之后,系統也會變得復雜。

與其他模式的關聯

裝飾者模式和代理模式具有很高的相似性,但是兩種所強調的點不一樣。前者強調的是為本體對象添加新的功能;后者強調的是對本體對象的訪問控制

裝飾者模式和適配器模式的區別是,前者只會擴展功能而不會修改接口;后者則會修改接口。

文章配圖

可以在用Keynote畫出手繪風格的配圖中找到文章的繪圖方法。

審核編輯 :李倩


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 模塊
    +關注

    關注

    7

    文章

    2696

    瀏覽量

    47437
  • UML
    UML
    +關注

    關注

    0

    文章

    122

    瀏覽量

    30858

原文標題:【Go實現】實踐GoF的23種設計模式:裝飾者模式

文章出處:【微信號:yuanrunzi,微信公眾號:元閏子的邀請】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    給我兩分鐘,搞懂發布-訂閱模式很輕松!

    什么是發布/訂閱模式?舉一個生活中常見的例子說明:小李到某房產中介提出租房需求,根據需求,房產中介將之前房東發布的出租信息提供給小李選擇,小李確定租房后,中介會將信息同步房東知曉。這是一個典型
    的頭像 發表于 10-25 08:06 ?363次閱讀
    給我兩分鐘,搞懂發布-訂閱<b class='flag-5'>模式</b>很輕松!

    【每天學點AI】一個例子帶你了解Python裝飾器到底在干嘛!

    今天我們來聊聊一種能給你的代碼變得“加料”的神器——Python裝飾器。就像一杯咖啡,原本它是苦的,為了讓它符合我的口味,我給它添加了糖,添加之后就完美的符合了我的口味。那么,裝飾器又是如何代碼
    的頭像 發表于 09-20 16:54 ?552次閱讀
    【每天學點AI】一個例子帶你了解Python<b class='flag-5'>裝飾</b>器到底在干嘛!

    DMX512AW裝飾照明驅動IC規格書

    電子發燒友網站提供《DMX512AW裝飾照明驅動IC規格書.pdf》資料免費下載
    發表于 07-09 16:52 ?0次下載

    HarmonyOS實戰開發-深度探索與打造個性化自定義組件

    ,容器組件,媒體組件,繪制組件,畫布組件組件等,如Button、Text 是基礎組件。 由開發在基礎組件基礎上 添加一些封裝和修飾 定義的組件稱為自定義組件。自定義組件的實現大大提高
    發表于 05-08 16:30

    實踐GoF的23種設計模式實現:橋接模式

    也即,將抽象部分和實現部分進行解耦,使得它們能夠各自往獨立的方向變化。
    的頭像 發表于 04-14 09:30 ?439次閱讀
    實踐<b class='flag-5'>GoF</b>的23種設計<b class='flag-5'>模式</b>實現:橋接<b class='flag-5'>模式</b>

    鴻蒙OS開發實例:【ArkTS類庫多線程@Concurrent裝飾器校驗并發函數】

    在使用TaskPool時,執行的并發函數需要使用該裝飾器修飾,否則無法通過相關校驗。從API version 9開始,該裝飾器支持在ArkTS卡片中使用。
    的頭像 發表于 04-02 14:45 ?668次閱讀
    鴻蒙OS開發實例:【ArkTS類庫多線程@Concurrent<b class='flag-5'>裝飾</b>器校驗并發函數】

    實踐GoF的23種設計模式:解釋器模式

    解釋器模式(Interpreter Pattern)應該是 GoF 的 23 種設計模式中使用頻率最少的一種了,它的應用場景較為局限。
    的頭像 發表于 04-01 11:01 ?694次閱讀
    實踐<b class='flag-5'>GoF</b>的23種設計<b class='flag-5'>模式</b>:解釋器<b class='flag-5'>模式</b>

    LoRa模組FSK模式下WOR功耗估算實例

    的LoRa模式,而是使用的該模組的FSK模式,并需要使用FSK模式下的WOR功能。在確定使用FSK模式下的WOR功能時需要先估算功耗是否能滿足使用
    的頭像 發表于 03-29 08:12 ?4643次閱讀
    LoRa模組FSK<b class='flag-5'>模式</b>下WOR功耗估算實例

    鴻蒙OS開發實例:【裝飾器@Observed@ObjectLink】

    加深對@Observed@ObjectLink 裝飾器使用的理解,以小故事做注釋
    的頭像 發表于 03-28 17:05 ?1004次閱讀
    鴻蒙OS開發實例:【<b class='flag-5'>裝飾</b>器@Observed@ObjectLink】

    鴻蒙原生應用開發-ArkTS語言基礎類庫多線程@Concurrent裝飾器校驗并發函數

    在使用TaskPool時,執行的并發函數需要使用該裝飾器修飾,否則無法通過相關校驗。從API version 9開始,該裝飾器支持在ArkTS卡片中使用。 裝飾器說明 裝飾器使用示例
    發表于 03-18 10:30

    ?用于LED裝飾照明DMX512并聯驅動控制芯片作用與應用領域

    LED裝飾照明DMX512并聯驅動控制芯片是一種新型的照明控制芯片,它能夠實現LED燈的分段控制,提供更加豐富多彩的照明效果。在現代建筑裝飾和舞臺演出中,LED裝飾照明已經成為主流,而DMX512
    的頭像 發表于 02-23 16:40 ?696次閱讀
    ?用于LED<b class='flag-5'>裝飾</b>照明DMX512并聯驅動控制芯片作用與應用領域

    OpenHarmony父子組件單項同步使用:@Prop裝飾

    @Prop裝飾的變量可以和父組件建立單向的同步關系。@Prop裝飾的變量是可變的,但是變化不會同步回其父組件。 說明: 從API version 9開始,該裝飾器支持在ArkTS卡片中使用。 概述
    的頭像 發表于 02-03 10:57 ?410次閱讀
    OpenHarmony父子組件單項同步使用:@Prop<b class='flag-5'>裝飾</b>器

    OpenHarmony 定義擴展組件樣式:@Extend 裝飾

    說明: 從 API version 9 開始,該裝飾器支持在 ArkTS 卡片中使用。 裝飾器使用說明 語法 ? @Extend(UIComponentName) function
    的頭像 發表于 02-01 20:53 ?269次閱讀

    什么是觀察設計模式?Golang中的觀察模式介紹

    當涉及到訂單處理系統時,觀察設計模式可以用于實現訂單狀態的變化和通知。
    的頭像 發表于 01-08 10:08 ?424次閱讀

    ADE7880除了線周期累計模式還有其他的累計模式嗎?兩的區別是什么?

    您好: 有幾個問題要請教下: 1、在ADE7880中在關于電能累計這一章節中說到了線周期功率累計模式,這是一種怎樣的模式? 2、除了線周期累計模式還有其他的累計模式嗎?兩
    發表于 12-27 08:03
    主站蜘蛛池模板: 精品久久久久久久99热| 最近免费中文字幕MV在线视频3| 亚洲欧美精品无码一区二在线 | 男女爽爽无遮挡午夜视频在线观看| 全部免费特黄特色大片看片| 亚洲国产高清在线| 亚洲国产AV精品一区二区蜜芽| 2021自产拍在线观看视频| 国产99小视频| 久青草国产97香蕉在线视频| 乱xxxjapanese黑人| 婷婷四房播客五月天| 最美白嫩的极品美女ASSPICS| 国产成久久免费精品AV片天堂| 久久久精品成人免费看| 翁熄性放纵交换01| 97视频免费在线观看| 国精产品999一区二区三区有限| 欧美日韩中文字幕综合图区| 亚洲欧美综合中文| 古风一女N男到处做高H| 美女激清床上戏大全| 亚洲高清在线mv| xx在线观看| 老阿姨才是最有味的一区二区| 无敌在线视频观看免费| ASIAN大陆明星裸休合成PICS| 精品欧美一区二区三区久久久| 日韩中文字幕欧美在线视频| 2021全国精品卡一卡二| 黄色三级三级免费看| 爽死你个放荡粗暴小淫货漫画| 亚洲精品无码成人AAA片| 成人无码国产AV免费看直播| 李亚男三级| 亚洲一级特黄| av亚洲2017色天堂| 久久精品国产亚洲AV麻豆欧美玲| 消息称老熟妇乱视频一区二区 | 99久久精品6在线播放| 久久国产乱子伦精品免费M|