裝飾器模式


title: 裝飾器模式
date: 2021-11-05 13:58:21


前言

裝飾器模式是一種結(jié)構(gòu)型設(shè)計模式势誊,它可以向現(xiàn)有對象動態(tài)地添加任意功能呜达,而不改變其原本結(jié)構(gòu)

面向?qū)ο笾械难b飾器

1.png

由圖可知粟耻,具體裝飾器類Concrete Decorators繼承Component接口類查近,與具體部件類Concrete Component一樣擁有execute方法眉踱。但不同在于,Concrete Decorators的execute方法霜威,是在被裝飾對象wrappee的execute方法的基礎(chǔ)上添加額外功能谈喳。而這個被裝飾對象wrappee,既可以是具體部件類戈泼,也可以是裝飾器類婿禽。

使用時,用戶可用多層裝飾器來包裹部件大猛,整體呈一個鏈?zhǔn)疥P(guān)系:

c = new ConcreteDecoratorA(new ConcreteDecoratorB(new ConcreteComponent()))
c.execute()

最終調(diào)用execute方法時扭倾,會依次調(diào)用ConcreteDecoratorA.execute() → ConcreteDecoratorB.execute() → ConcreteComponent.execute()

舉例

有一家奶茶店挽绩,提供多種類型的奶茶:奶青膛壹、烏龍奶茶、椰奶奶茶等等琼牧。當(dāng)然恢筝,奶茶也可以加料哀卫,配料有椰果巨坊、珍珠、仙草等等此改。剛開始售賣奶茶時趾撵,奶茶店將奶茶與配料捆綁在一起,就會出現(xiàn)一份椰果的烏龍奶茶共啃、一份珍珠的椰奶奶茶占调、一份椰果與一份珍珠的椰奶奶茶等,這樣不僅使奶茶種類變得臃腫移剪,還喪失了靈活性究珊。顧客體驗很差,生意自然就不好纵苛。

但好巧不巧剿涮,老板無意中學(xué)習(xí)到了裝飾器模式,幡然醒悟攻人,將奶茶與配料拆開取试,讓顧客先選擇奶茶,再添加任意配料怀吻。如此瞬浓,顧客就有非常自由且豐富的選擇。顧客高興了蓬坡,生意自然就好起來了猿棉,奶茶店也有了好盼頭磅叛。

在這個例子中,奶茶就是Component接口類萨赁,配料就是Base Decorator類宪躯。一杯奶茶Concrete Component被生產(chǎn)出來后,可以向其中添加任意類型和數(shù)量的配料Concrete Decorators位迂。

自問自答

為什么不把Decorators作為Component類的一個數(shù)組屬性访雪?然后在execute時依次調(diào)用Decorators中 的方法呢?

首先掂林,這樣需要修改Component類的結(jié)構(gòu)和內(nèi)容臣缀。而在實際中,往往是先有Component再有Decorator泻帮,甚至Component是不允許修改的精置。

其次,缺乏靈活性锣杂。不同Decorator可能有不同的行為脂倦,有些Decorator想在execute方法被調(diào)用之前做處理,有些則想在之后做處理元莫。

而裝飾器模式既不用修改被裝飾對象赖阻,又保證了靈活性。

面向過程中的裝飾器

面向?qū)ο笸ǔkx我們較遠踱蠢,而真正讓我領(lǐng)會到裝飾器模式厲害之處的火欧,是在面向過程的編程中。

Python中的裝飾器

編寫函數(shù)時我總有一個習(xí)慣茎截,在函數(shù)的開頭打印函數(shù)開始與函數(shù)參數(shù)苇侵,并在函數(shù)結(jié)尾打印函數(shù)結(jié)束與函數(shù)返回值。如此企锌,打印輸出的邏輯順序較為清晰榆浓,就可以較為輕松地進行調(diào)試。

def funcA(s):
    print("++++++ funA begin ++++++")
    print(s)

    ...

    print(ret)
    print("------ funA end ------")
    return ret

funcA(1)

但是撕攒,如果每個函數(shù)都添加這些步驟陡鹃,重復(fù)性又太高,代碼將充滿壞味道打却。有一種方法是定義專門的函數(shù)如下:

# args 參數(shù)數(shù)組杉适,kwargs 參數(shù)字典
def func_log(func, *args, **kwargs):
    print(f"++++++ {func.__name__} begin ++++++")
    print(*args, **kwargs)
    ret = func(*args, **kwargs)
    print(ret)
    print(f"------ {func.__name__} end ------")
    return ret

def funcA(s):
    ...

# old: funcA(1)
func_log(funcA, 1)

這樣雖然能復(fù)用,但原來通過funcA(1)柳击,現(xiàn)在要通過func_log(funcA, 1)的形式調(diào)用猿推。改變了代碼的原本結(jié)構(gòu) ,這是我們不想看到的。

于是蹬叭,裝飾器模式登場了藕咏。裝飾器模式的本質(zhì)在于:傳入一種類型的變量或?qū)ο螅诓桓淖兤湓薪Y(jié)構(gòu)的基礎(chǔ)上進行裝飾加工秽五,最終返回相同類型的變量或?qū)ο蟆?/strong>

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"++++++ {func.__name__} begin ++++++")
        print(f"===> args:{args}, kwargs:{kwargs}")
        ret = func(*args, **kwargs)
        print(f"<=== return:{ret}")
        print(f"------ {func.__name__} end ------")
        return ret
    return wrapper

def funcA(s):
    ...

funcA = log_decorator(funcA) # 使用裝飾器裝飾
...
funcA(1)

使用裝飾器后孽查,既能在函數(shù)的調(diào)用前后添加額外功能,又不會改變函數(shù)的內(nèi)容和調(diào)用方式坦喘。這已經(jīng)非常完美了盲再,但還能更完美。在python中瓣铣,還提供了裝飾器語法糖@答朋,使裝飾器的使用更加方便:

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"++++++ {func.__name__} begin ++++++")
        print(f"===> args:{args}, kwargs:{kwargs}")
        ret = func(*args, **kwargs)
        print(f"<=== return:{ret}")
        print(f"------ {func.__name__} end ------")
        return ret
    return wrapper

@log_decorator # 使用裝飾器裝飾
def funcA(s):
    ...

funcA(1)

Go中的裝飾器

只要在一門語言中函數(shù)是一等公民,那這門語言就可以使用過程式的裝飾器棠笑。因而梦碗,Go語言也可以編寫過程式的裝飾器。

package main

import (
  "log"
  "runtime"
  "reflect"
)


type Func func(s string) string

func getFuncName(i interface{}) string {
  return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}

func logDecorator(fn Func) Func {
  return func(s string) string {
    funcName := getFuncName(fn)
    log.Printf("===> %s arg: %v\n", funcName, s)
    ret := fn(s)
    log.Printf("<=== %s ret: %v\n", funcName, ret)
    return ret
  }
}

func funcA(s string) string {
  s = "hello " + s
  return s
}

func main() {
  fn := logDecorator(funcA)
  fn("decorator")
}

但是蓖救,Go語言是一門強類型語言洪规,它無法做到python那樣將一個裝飾器應(yīng)用到任意函數(shù)上。在Go語言中循捺,只能對同一類型的函數(shù)(參數(shù)與返回值都相同)編寫專門的裝飾器斩例,這與面向?qū)ο蟮难b飾器相似。同時巨柒,它也暫不支持裝飾器語法糖樱拴。

總結(jié)

裝飾器模式,尤其是面向過程的裝飾器洋满,可以將許多小功能(如打印日志、計算運行時間珍坊、參數(shù)預(yù)處理等)作為裝飾器牺勾,靈活地組合在一起,大大提高代碼的復(fù)用性阵漏。

其實驻民,裝飾器模式這種“只提供最基礎(chǔ)本體,而將附加功能作為裝飾器 ”的思想履怯,不僅適用于代碼編寫回还,還適用于方方面面。比如叹洲,當(dāng)今后端框架越來越趨于小而輕柠硕,而不是大而重,框架只需提供最基礎(chǔ)的功能,而附加功能可通過插件的形式進行補充蝗柔,這樣就能使得用戶的選擇更加靈活多樣闻葵。再比如,奶茶中的配料癣丧、烹飪時的佐料等等槽畔,都是裝飾器模式思想的體現(xiàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胁编,一起剝皮案震驚了整個濱河市厢钧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嬉橙,老刑警劉巖坏快,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異憎夷,居然都是意外死亡莽鸿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門拾给,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祥得,“玉大人,你說我怎么就攤上這事蒋得〖都埃” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵额衙,是天一觀的道長饮焦。 經(jīng)常有香客問我,道長窍侧,這世上最難降的妖魔是什么县踢? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮伟件,結(jié)果婚禮上硼啤,老公的妹妹穿的比我還像新娘斧账。我一直安慰自己,他們只是感情好咧织,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著习绢,像睡著了一般渠抹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天耸黑,我揣著相機與錄音篮幢,去河邊找鬼大刊。 笑死,一個胖子當(dāng)著我的面吹牛三椿,可吹牛的內(nèi)容都是我干的缺菌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼伴郁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了焊傅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤狐胎,失蹤者是張志新(化名)和其女友劉穎歌馍,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體松却,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年歌焦,在試婚紗的時候發(fā)現(xiàn)自己被綠了带射。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡窟社,死狀恐怖绪钥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情程腹,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布色鸳,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏菇民。R本人自食惡果不足惜甘畅,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一再榄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧淀歇,春花似錦匈织、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岳链。三九已至劲件,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間零远,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工摔癣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纬向,地道東北人择浊。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓琢岩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親担孔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 8.門面模式 8.1.課程目標(biāo) 1挑豌、掌握門面模式和裝飾器模式的特征和應(yīng)用場景 2、理解裝飾器模式和代理模式的根本區(qū)...
    我是阿喵醬閱讀 908評論 0 1
  • 場景 奶茶加糖(網(wǎng)上看到一個簡單的例子浮毯,和獎金的基本一致)一杯奶茶的價格是10元泰鸡,提供其他單加產(chǎn)品。如:糖 1元布...
    阿白_12b4閱讀 128評論 0 1
  • 什么是裝飾器模式 裝飾器模式的概念 從概念上講饰迹,是在實現(xiàn)一個抽象內(nèi)容的同時,以增強或擴展其功能為目的啊鸭,對它進行一層...
    Auri秋河閱讀 1,238評論 0 1
  • 裝飾器模式用于在動態(tài)修改現(xiàn)有對象的功能匿值。 同一類的其他實例將不會受到修改的影響,只有單個對象獲得修改后的行為挟憔。 裝...
    智行孫閱讀 433評論 0 0
  • 裝飾器模式簡介 在使用 React 框架編程中,我們用高階組件的時候绊谭,使用時往往需要用高階組件函數(shù)包裹當(dāng)前組件來導(dǎo)...
    鶴仔z閱讀 1,001評論 0 3