title: 裝飾器模式
date: 2021-11-05 13:58:21
前言
裝飾器模式是一種結(jié)構(gòu)型設(shè)計模式势誊,它可以向現(xiàn)有對象動態(tài)地添加任意功能呜达,而不改變其原本結(jié)構(gòu) 。
面向?qū)ο笾械难b飾器
由圖可知粟耻,具體裝飾器類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)。