摘要:在本節(jié)實(shí)驗(yàn)中,我們學(xué)習(xí)了四種設(shè)計(jì)模式策略模式,觀察者模式,命令模式以及模板方法模式。這四種設(shè)計(jì)模式都是行為型模式。這就是適配器模式。下面讓我們看看適配器模式在實(shí)驗(yàn)樓中使用吧。準(zhǔn)確來說,裝飾者模式能動(dòng)態(tài)的給對象添加行為。
1、策略模式
策略模式將各種操作(算法)進(jìn)行封裝,并使它們之間可以互換?;Q的意思是說可以動(dòng)態(tài)改變對象的操作方式(算法)。
-- coding: utf-8 --import abc
class AbsShow(object):
""" 抽象顯示對象 """ __metaclass__ = abc.ABCMeta @abc.abstractmethod def show(self): pass
class AdminShow(AbsShow):
""" 管理員的顯示操作 """ def show(self): return "show with admin"
class UserShow(AbsShow):
""" 普通用戶的顯示操作 """ def show(self): return "show with user"
class Question(object):
""" 問題對象,使用策略模式之后的作法 """ def __init__(self, show_obj): self.show_obj = show_obj def show(self): return self.show_obj.show()
if name == "__main__":
q = Question(show_obj=AdminShow()) print(q.show()) # 替換原來的顯示對象,體現(xiàn)了策略模式的互換行為 q.show_obj = UserShow() print(q.show()) 上面的代碼中,我們將原來的 Question.show 抽象成了 AbsShow ,這個(gè)操作類負(fù)責(zé)顯示信息。然后我們分別基于該抽象類實(shí)現(xiàn)管理員顯示類 AdminShow 和用戶顯示類 UserShow ,這樣一來我們就使操作和使用這些操作的客戶端完全分開了。在最后我們重新實(shí)現(xiàn)了 Question ,并且 Question.show 方法直接調(diào)用顯示對象的顯示方法。這樣一來我們將 Question 對象和顯示方法進(jìn)行了解耦,增加新的顯示方法時(shí),只需要增加新的顯示對象就可以了。同時(shí),在代碼中還可以看到我們可以動(dòng)態(tài)改變 Question 的顯示方式,這也體現(xiàn)了策略模式的互換行為。
二、觀察者模式
所謂觀察者模式,就是說當(dāng)一個(gè)對象發(fā)生變化時(shí),觀察者能及時(shí)得到通知并更新。觀察者模式在很多地方都有應(yīng)用,比如在實(shí)驗(yàn)樓上關(guān)注課程。下面就讓我們看下實(shí)驗(yàn)樓針對課程關(guān)注功能是怎么實(shí)現(xiàn)觀察者模式的。
-- coding: utf-8 --import abc
class Subject(object):
""" 被觀察對象的基類 """ def __init__(self): self._observers = [] def attach(self, observer): """ 注冊一個(gè)觀察者 """ if observer not in self._observers: self._observers.append(observer) def detach(self, observer): """ 注銷一個(gè)觀察者 """ try: self._observers.remove(observer) except ValueError: pass def notify(self): """ 通知所有觀察者,執(zhí)行觀察者的更新方法 """ for observer in self._observers: observer.update(self)
class Course(Subject):
""" 課程對象,被觀察的對象 """ def __init__(self): super(Course, self).__init__() self._message = None @property def message(self): """ message 是一個(gè)屬性 """ return self._message @message.setter def message(self, msg): """ message 屬性設(shè)置器 """ self._message = msg self.notify()
class Observer(object):
""" 觀察者抽象類 """ __metaclass__ = abc.ABCMeta @abc.abstractmethod def update(self, subject): pass
class UserObserver(Observer):
""" 用戶觀察者 """ def update(self, subject): print("User observer: %s" % subject.message)
class OrgObserver(Observer):
""" 機(jī)構(gòu)觀察者 """ def update(self, subject): print("Organization observer: %s" % subject.message)
if name == "__main__":
# 初始化一個(gè)用戶觀察者 user = UserObserver() # 初始化一個(gè)機(jī)構(gòu)觀察者 org = OrgObserver() # 初始化一個(gè)課程 course = Course() # 注冊觀察者 course.attach(user) course.attach(org) # 設(shè)置course.message,這時(shí)觀察者會(huì)收到通知 course.message = "two observers" # 注銷一個(gè)觀察者 course.detach(user) course.message = "single observer" 在上面的代碼中,最重要的就是Subject類了,它實(shí)現(xiàn)了觀察者模式中大部分功能。作為一個(gè)被觀察的對象,Subject實(shí)現(xiàn)了注冊觀察者,注銷觀察者和通知觀察者的功能。接著我們基于Subject創(chuàng)建了我們的課程Course類,并且當(dāng)我們設(shè)置Course.message屬性時(shí),Course對象會(huì)通知到所有觀察者??梢钥闯?,觀察者模式使被觀察的對象(主題)和觀察者之間解耦了。
三、命令模式
顧名思義,命令模式就是對命令的封裝。所謂封裝命令,就是將一系列操作封裝到命令類中,并且命令類只需要對外公開一個(gè)執(zhí)行方法execute,調(diào)用此命令的對象只需要執(zhí)行命令的execute方法就可以完成所有的操作。這樣調(diào)用此命令的對象就和命令具體操作之間解耦了。更進(jìn)一步,通過命令模式我們可以抽象出調(diào)用者,接收者和命令三個(gè)對象。調(diào)用者就是簡單的調(diào)用命令,然后將命令發(fā)送給接收者,而接收者則接收并執(zhí)行命令,執(zhí)行命令的方式也是簡單的調(diào)用命令的execute方法就可以了。發(fā)送者與接收者之間沒有直接引用關(guān)系,發(fā)送請求的對象只需要知道如何發(fā)送請求,而不必知道如何完成請求。下面讓我們使用 Python 來實(shí)現(xiàn)命令模式。
-- coding: utf-8 --import abc
class VmReceiver(object):
""" 命令接收者,真正執(zhí)行命令的地方 """ def start(self): print("Virtual machine start") def stop(self): print("Virtual machine stop")
class Command(object):
""" 命令抽象類 """ __metaclass__ = abc.ABCMeta @abc.abstractmethod def execute(self): """ 命令對象對外只提供 execute 方法 """ pass
class StartVmCommand(Command):
""" 開啟虛擬機(jī)的命令 """ def __init__(self, recevier): """ 使用一個(gè)命令接收者初始化 """ self.recevier = recevier def execute(self): """ 真正執(zhí)行命令的時(shí)候命令接收者開啟虛擬機(jī) """ self.recevier.start()
class StopVmCommand(Command):
""" 停止虛擬機(jī)的命令 """ def __init__(self, recevier): """ 使用一個(gè)命令接收者初始化 """ self.recevier = recevier def execute(self): """ 真正執(zhí)行命令的時(shí)候命令接收者關(guān)閉虛擬機(jī) """ self.recevier.stop()
class ClientInvoker(object):
""" 命令調(diào)用者 """ def __init__(self, command): self.command = command def do(self): self.command.execute()
if name == "__main__":
recevier = VmReceiver() start_command = StartVmCommand(recevier) # 命令調(diào)用者同時(shí)也是客戶端,通過命令實(shí)例也執(zhí)行真正的操作 client = ClientInvoker(start_command) client.do() # 能告訴命令接收者執(zhí)行不同的操作 stop_command = StopVmCommand(recevier) client.command = stop_command client.do()
以上代碼中,我們通過啟動(dòng)和停止 Linux 虛擬機(jī)的例子實(shí)現(xiàn)了命令模式。通過命令模式,使命令調(diào)用者ClinetInvoker和命令接收者VmRecevier之間解耦,前者不必知道后者具體是怎么操作虛擬機(jī)的,只需要通過ClientInvoker.do()方法調(diào)用Command.execute()方法能完成虛擬機(jī)的相關(guān)操作。
總的來說,命令模式的封裝性很好:每個(gè)命令都被封裝起來,對于客戶端來說,需要什么功能就去調(diào)用相應(yīng)的命令,而無需知道命令具體是怎么執(zhí)行的。同時(shí)命令模式的擴(kuò)展性很好,在命令模式中,在接收者類中一般會(huì)對操作進(jìn)行最基本的封裝,命令類則通過對這些基本的操作進(jìn)行二次封裝,當(dāng)增加新命令的時(shí)候,對命令類的編寫一般不是從零開始的,有大量的接收者類可供調(diào)用,也有大量的命令類可供調(diào)用,代碼的復(fù)用性很好。
四、模板方法模式
提到模板,不難想到文檔模板、簡歷模板等。其實(shí)模板方法模式中的模板就是這個(gè)意思,在模板方法模式中,我們先定義一個(gè)類模板,在這個(gè)類中,我們定義了各種操作的順序(輪轂或者說是骨架),但是并不實(shí)現(xiàn)這些操作,這些操作由子類來操作。
舉個(gè)例子,假如有一天我們想去去三岔湖釣魚,那么需要怎么操作呢?第一步:準(zhǔn)備魚餌;第二步:到達(dá)三岔湖;第三步;選擇釣點(diǎn)。這三步操作不能亂序,否則我們就釣不成魚啦。但是在準(zhǔn)備魚餌的時(shí)候,可以通過淘寶購買,也可以在漁具店里購買,這些都是不確定。同時(shí)怎么去三岔湖也是不確定的,你可以開車去,也可以搭車去。在這種情況下,模板方法模式就非常有用了。在模板方法模式中我們先定義去三岔湖釣魚的操作步驟,每一步的具體操作在不同的子類中可能都有不同的實(shí)現(xiàn)。下面讓我們看看具體的代碼吧。
在 /home/shiyanlou/Code/template-1.py 文件中添加如下代碼:
-- coding: utf-8 --import abc
class Fishing(object):
""" 釣魚模板基類 """ __metaclass__ = abc.ABCMeta def finishing(self): """ 釣魚方法中,確定了要執(zhí)行哪些操作才能釣魚 """ self.prepare_bait() self.go_to_riverbank() self.find_location() print("start fishing") @abc.abstractmethod def prepare_bait(self): pass @abc.abstractmethod def go_to_riverbank(self): pass @abc.abstractmethod def find_location(self): pass
class JohnFishing(Fishing):
""" John 也想去釣魚,它必須實(shí)現(xiàn)釣魚三步驟 """ def prepare_bait(self): """ 從淘寶購買魚餌 """ print("John: buy bait from Taobao") def go_to_riverbank(self): """ 開車去釣魚 """ print("John: to river by driving") def find_location(self): """ 在島上選擇釣點(diǎn) """ print("John: select location on the island")
class SimonFishing(Fishing):
""" Simon 也想去釣魚,它也必須實(shí)現(xiàn)釣魚三步驟 """ def prepare_bait(self): """ 從京東購買魚餌 """ print("Simon: buy bait from JD") def go_to_riverbank(self): """ 騎自行車去釣魚 """ print("Simon: to river by biking") def find_location(self): """ 在河邊選擇釣點(diǎn) """ print("Simon: select location on the riverbank")
if name == "__main__":
# John 去釣魚 f = JohnFishing() f.finishing() # Simon 去釣魚 f = SimonFishing() f.finishing()
運(yùn)行:
2-5-1
怎么樣?模板方法模式是不是簡單易懂呢?模板方法模式是結(jié)構(gòu)最簡單的行為型設(shè)計(jì)模式,在其結(jié)構(gòu)中只存在父類與子類之間的繼承關(guān)系。通過使用模板方法模式,可以將一些復(fù)雜流程的實(shí)現(xiàn)步驟封裝在一系列基本方法中,在抽象父類中提供一個(gè)稱之為模板方法的方法來定義這些基本方法的執(zhí)行次序,而通過其子類來覆蓋某些步驟,從而使得相同的算法框架可以有不同的執(zhí)行結(jié)果。模板方法模式提供了一個(gè)模板方法來定義算法框架,而某些具體步驟的實(shí)現(xiàn)可以在其子類中完成。
到目前為止,這一節(jié)實(shí)驗(yàn)就要結(jié)束了。在本節(jié)實(shí)驗(yàn)中,我們學(xué)習(xí)了四種設(shè)計(jì)模式:策略模式,觀察者模式,命令模式以及模板方法模式。這四種設(shè)計(jì)模式都是行為型模式。什么是行為型模式呢?
按照定義,行為型模式是對在不同的對象之間劃分責(zé)任和算法的抽象化。行為型模式不僅僅關(guān)注類和對象的結(jié)構(gòu),而且重點(diǎn)關(guān)注它們之間的相互作用。通過行為型模式,可以更加清晰地劃分類與對象的職責(zé),并研究系統(tǒng)在運(yùn)行時(shí)實(shí)例對象之間的交互。在系統(tǒng)運(yùn)行時(shí),對象并不是孤立的,它們可以通過相互通信與協(xié)作完成某些復(fù)雜功能,一個(gè)對象在運(yùn)行時(shí)也將影響到其他對象的運(yùn)行。
五、適配器模式
何為適配器?你買過水貨電子產(chǎn)品嗎?假如你是買的港行的電子產(chǎn)品,那么其電源插頭是香港標(biāo)準(zhǔn)的,在大陸不能直接使用。一般情況下,商家會(huì)附贈(zèng)一個(gè)轉(zhuǎn)換插頭。你把電子產(chǎn)品的電源插頭插在轉(zhuǎn)換插頭上,然后轉(zhuǎn)換插頭插上電源,電子產(chǎn)品就能正常工作了。這就是適配器模式。下面讓我們看看適配器模式在實(shí)驗(yàn)樓中使用吧。
在 /home/shiyanlou/Code/adapter-1.py 文件中添加如下代碼:
-- coding: utf-8 --class OldCourse(object):
""" 老的課程類 """ def show(self): """ 顯示關(guān)于本課程的所有信息 """ print("show description") print("show teacher of course") print("show labs")
class Page(object):
""" 使用課程對象的客戶端 """ def __init__(self, course): self.course = course def render(self): self.course.show()
class NewCourse(object):
""" 新的課程類, 為了模塊化顯示課程信息,實(shí)現(xiàn)了新的課程類 """ def show_desc(self): """ 顯示描述信息 """ print("show description") def show_teacher(self): """ 顯示老師信息 """ print("show teacher of course") def show_labs(self): """ 顯示實(shí)驗(yàn) """ print("show labs")
class Adapter(object):
""" 適配器, 盡管實(shí)現(xiàn)了新的課程類,但是在很多代碼中還是需要使用 OldCourse.show() 方法 """ def __init__(self, course): self.course = course def show(self): """ 適配方法,調(diào)用真正的操作 """ self.course.show_desc() self.course.show_teacher() self.course.show_labs()
if name == "__main__":
old_course = OldCourse() page = Page(old_course) page.render() print("") new_course = NewCourse() # 新課程類沒有 show 方法,我們需要使用適配器進(jìn)行適配 adapter = Adapter(new_course) page = Page(adapter) page.render()
運(yùn)行:
3-2-1
在上面的代碼中,我們原本有一個(gè)OldCourse類,它有一個(gè)方法OldCourse.show用于顯示課程的所有相關(guān)信息,并且在Page對象中用到?,F(xiàn)在,為了適應(yīng)模塊化顯示的需求,我們開發(fā)了新的課程類NewCourse,它只能分別顯示課程的部分信息?,F(xiàn)在為了使NewCourse對象能在Page對象中也能正常工作,我們使用了適配器模式來兼容。在適配器Adapter中,我們實(shí)現(xiàn)了Adapter.show()方法,它會(huì)調(diào)用NewCourse的一系列方法來完成顯示整個(gè)課程信息的需求。這樣一來,我們直接將Adapter對象傳遞給Page對象就可以兼容老的接口,使系統(tǒng)正常運(yùn)行。
適配器模式就是把一個(gè)類的接口變換成客戶端所期待的另一種接口,使原本因接口不兼容而無法在一起工作的兩個(gè)類能夠在一起工作。
六、裝飾者模式
裝飾者模式?裝飾器?對于一個(gè) Python 程序員來說,這再熟悉不過了。準(zhǔn)確來說,裝飾者模式能動(dòng)態(tài)的給對象添加行為。如果你對 Flask 比較熟悉的話,應(yīng)該知道在使用 Flask-Login 的時(shí)候可以使用 login_required 裝飾器包裝一個(gè)需要用戶登錄訪問的view。直接看看我們實(shí)現(xiàn)的裝飾者模式吧。
在 /home/shiyanlou/Code/decorator-1.py 文件中添加如下代碼:
-- coding: utf-8 --from functools import wraps
HOST_DOCKER = 0
def docker_host_required(f):
""" 裝飾器,必須要求 host 類型是 HOST_DOCKER """ @wraps(f) def wrapper(*args, **kwargs): if args[0].type != HOST_DOCKER: raise Exception("Not docker host") else: return f(*args, **kwargs) return wrapper
class Host(object):
""" host 類 """ def __init__(self, type): self.type = type # 裝飾這一方法 @docker_host_required def create_container(self): print("create container")
if name == "__main__":
# 初始化 Host host = Host(HOST_DOCKER) host.create_container() print("") # 再次初始化 Host host = Host(1) host.create_container()
在上面的代碼中,Host有一個(gè)方法Host.create_container,只有當(dāng)Host實(shí)例的類型是DOCKER_HOST的時(shí)候才能執(zhí)行該方法。為了加上這一行為,我們使用了裝飾者模式??梢钥闯鍪褂醚b飾者模式,我們可以動(dòng)態(tài)改變類的行為,同時(shí)能提高代碼復(fù)用性,因?yàn)槿魏晤愋蜑镠OST_DOCKER的Host都可以使用該裝飾器。另外要說明下:為了更好的實(shí)現(xiàn)裝飾器,我們使用functools.wrap函數(shù)。
七、代理模式
代理模式在生活中比比皆是。比如你通過代理上網(wǎng),比如你不會(huì)去華西牛奶生產(chǎn)地直接買牛奶,而是到超市這個(gè)代理購買牛奶,這些例子中都存在著代理模式。所謂代理模式就是給一個(gè)對象提供一個(gè)代理,并由代理對象控制對原對象的訪問。通過代理,我們可以對訪問做一些控制。在開發(fā)網(wǎng)站的過程中,針對一些頻繁訪問的資源,我們會(huì)使用緩存。在開發(fā)實(shí)驗(yàn)樓的過程中也是如此,我們通過緩存代理解決了一些熱點(diǎn)資源的訪問問題。下面讓我們看看是怎么實(shí)現(xiàn)的吧。
在 /home/shiyanlou/Code/proxy-1.py 文件中添加如下代碼:
-- coding: utf-8 --from time import sleep
class Redis(object):
""" 用于模擬 redis 服務(wù) """ def __init__(self): """ 使用字典存儲(chǔ)數(shù)據(jù) """ self.cache = dict() def get(self, key): """ 獲取數(shù)據(jù) """ return self.cache.get(key) def set(self, key, value): """ 設(shè)置數(shù)據(jù) """ self.cache[key] = value
class Image(object):
""" 圖片對象,圖片存在七牛云存儲(chǔ)中,我們只保存了一個(gè)地址 """ def __init__(self, name): self.name = name @property def url(self): sleep(2) return "https://dn-syl-static.qbox.me/img/logo-transparent.png"
class Page(object):
""" 用于顯示圖片 """ def __init__(self, image): """ 需要圖片進(jìn)行初始化 """ self.image = image def render(self): """ 顯示圖片 """ print(self.image.url)
redis = Redis()
class ImageProxy(object):
""" 圖片代理,首次訪問會(huì)從真正的圖片對象中獲取地址,以后都從 Redis 緩存中獲取 """ def __init__(self, image): self.image = image @property def url(self): addr = redis.get(self.image.name) if not addr: addr = self.image.url print("Set url in redis cache!") redis.set(self.image.name, addr) else: print("Get url from redis cache!") return addr
if name == "__main__":
img = Image(name="logo") proxy = ImageProxy(img) page = Page(proxy) # 首次訪問 page.render() print("") # 第二次訪問 page.render()
運(yùn)行:
3-4-1
在上面的代碼中我們使用代理模式實(shí)現(xiàn)了對圖片的緩存。在使用緩存之前,我們實(shí)現(xiàn)了Redis對象簡單模擬了Redis服務(wù)??梢钥吹皆L問Image.url屬性是比較耗時(shí)的操作(我們使用time.sleep模擬了耗時(shí)操作),如果每次都是直接訪問該屬性,就會(huì)浪費(fèi)大量的時(shí)間。通過實(shí)現(xiàn)ImageProxy緩存代理,我們將圖片地址緩存到 Redis 中,提高了后續(xù)的訪問速度。
從上面的代碼可以看出,代理對象和真實(shí)的對象之間都實(shí)現(xiàn)了共同的接口,這使我們可以在不改變原接口情況下,使用真實(shí)對象的地方都可以使用代理對象。其次,代理對象在客戶端和真實(shí)對象之間直接起到了中介作用,同時(shí)通過代理對象,我們可以在將客戶請求傳遞給真實(shí)對象之前做一些必要的預(yù)處理。
八、組合模式
什么是組合模式?按照定義來說,組合模式是將對象組合成樹形結(jié)構(gòu)表示,使得客戶端對單個(gè)對象和組合對象的使用具有一致性。組合模式的使用通常會(huì)生成一顆對象樹,對象樹中的葉子結(jié)點(diǎn)代表單個(gè)對象,其他節(jié)點(diǎn)代表組合對象。調(diào)用某一組合對象的方法,其實(shí)會(huì)迭代調(diào)用所有其葉子對象的方法。
使用組合模式的經(jīng)典例子是 Linux 系統(tǒng)內(nèi)的樹形菜單和文件系統(tǒng)。在樹形菜單中,每一項(xiàng)菜單可能是一個(gè)組合對象,其包含了菜單項(xiàng)和子菜單,這樣就形成了一棵對象樹。在文件系統(tǒng)中,葉子對象就是文件,而文件夾就是組合對象,文件夾可以包含文件夾和文件,同樣又形成了一棵對象樹。同樣的例子還有員工和領(lǐng)導(dǎo)之間的關(guān)系,下面就讓我們實(shí)現(xiàn)下吧。
在 /home/shiyanlou/Code/composite-1.py 文件中添加如下代碼:
-- coding: utf-8 --import abc
class Worker(object):
""" 員工抽象類 """ __metaclass__ = abc.ABCMeta def __init__(self, name): self.name = name @abc.abstractmethod def work(self): pass
class Employe(Worker):
""" 員工類 """ __metaclass__ = abc.ABCMeta def work(self): print("Employ: %s start to work " % self.name)
class Leader(Worker):
""" 領(lǐng)導(dǎo)類 """ def __init__(self, name): self.members = [] super(Leader, self).__init__(name) def add_member(self, employe): if employe not in self.members: self.members.append(employe) def remove_member(self, employe): if employe in self.members: self.members.remove(employe) def work(self): print("Leader: %s start to work" % self.name) for employe in self.members: employe.work()
if name == "__main__":
employe_1 = Employe("employe_1") employe_2 = Employe("employe_2") leader_1 = Leader("leader_1") leader_1.add_member(employe_1) leader_1.add_member(employe_2) employe_3 = Employe("employe_3") leader_2 = Leader("leader_2") leader_2.add_member(employe_3) leader_2.add_member(leader_1) leader_2.work()
運(yùn)行:
3-5-1
在以上的代碼中,雇員和領(lǐng)導(dǎo)都屬于員工,都會(huì)實(shí)現(xiàn)Worker.work()方法,只要執(zhí)行了該方法就代表這個(gè)員工開始工作了。我們也注意到一個(gè)領(lǐng)導(dǎo)名下,可能有多個(gè)次級(jí)領(lǐng)導(dǎo)和其他雇員,如果一個(gè)領(lǐng)導(dǎo)開始工作,那這些次級(jí)領(lǐng)導(dǎo)和雇員都需要開工。員工和領(lǐng)導(dǎo)組成了一個(gè)對象樹,領(lǐng)導(dǎo)是組合對象,員工是葉子對象。還可以看到 Leader類通常會(huì)實(shí)現(xiàn)類似于Leader.add_member的方法來用于添加另一個(gè)組合對象或者是葉子對象,并且調(diào)用組合對象的Leader.work方法會(huì)遍歷調(diào)用(通過迭代器)其子對象work方法??蛻舳耸褂媒M合模式實(shí)現(xiàn)的對象時(shí),不必關(guān)心自己處理的是單個(gè)對象還是組合對象,降低了客戶端的使用難度,降低了耦合性。
在最后的測試代碼中,我們首先創(chuàng)建了2個(gè)雇員: employe_1, employe_2 和1個(gè)領(lǐng)導(dǎo)leader_1, 前2個(gè)雇員被后一個(gè)領(lǐng)導(dǎo)管理。接著我們又創(chuàng)建了第3個(gè)雇員employe_3,和第2個(gè)領(lǐng)導(dǎo)leader_2,其中 leader_2是大領(lǐng)導(dǎo),他管理employe_3和leader_1。
十、外觀模式
所謂外觀模式,就是將各種子系統(tǒng)的復(fù)雜操作通過外觀模式簡化,讓客戶端使用起來更方便簡潔。比如你夏天晚上出門時(shí),要關(guān)閉電燈,關(guān)閉電視機(jī),關(guān)閉空調(diào),如果有了一個(gè)總開關(guān),通過它可以關(guān)閉電燈,電視機(jī)和空調(diào),你出門的時(shí)候關(guān)閉總開關(guān)就行了。在這個(gè)例子中,你就是客戶端,總開關(guān)就是外觀模式的化身。在實(shí)驗(yàn)樓中,外觀模式應(yīng)用在創(chuàng)建實(shí)驗(yàn)環(huán)境的接口上。讓我們看看具體的代碼吧。
在 /home/shiyanlou/Code/facade-1.py 文件中添加如下代碼:
-- coding: utf-8 --class User(object):
""" 用戶類 """ def is_login(self): return True def has_privilege(self, privilege): return True
class Course(object):
""" 課程類 """ def can_be_learned(self): return True
class Lab(object):
""" 實(shí)驗(yàn)類 """ def can_be_started(self): return True
class Client(object):
""" 客戶類,用于開始一個(gè)實(shí)驗(yàn) """ def __init__(self, user, course, lab): self.user = user self.course = course self.lab = lab def start_lab(self): """ 開始實(shí)驗(yàn),需要一系列的判斷:用戶是否登陸,課程是否可以學(xué)習(xí),實(shí)驗(yàn)是否可以開始。判斷非常繁瑣! """ if self.user.is_login() and self.course.can_be_learned() and self.lab.can_be_started(): print("start lab") else: print("can not start lab")
class FacadeLab(object):
""" 新的Lab類,應(yīng)用了面向?qū)ο竽J?""" def __init__(self, user, course, lab): self.user = user self.course = course self.lab = lab def can_be_started(self): if self.user.is_login() and self.course.can_be_learned() and self.lab.can_be_started(): return True else: return False
class NewClient(object):
""" 新的客戶類,使用外觀模式 """ def __init__(self, facade_lab): self.lab = facade_lab def start_lab(self): """ 開始實(shí)驗(yàn),只需要判斷 FacadeLab 是否可以開始 """ if self.lab.can_be_started: print("start lab") else: print("can not start lab")
if name == "__main__":
user = User() course = Course() lab = Lab() client = Client(user, course, lab) client.start_lab() print("Use Facade Pattern:") facade_lab = FacadeLab(user, course, lab) facade_client = NewClient(facade_lab) facade_client.start_lab()
運(yùn)行:
4-2-1
以上代碼中,我們使用了在實(shí)驗(yàn)樓中啟動(dòng)實(shí)驗(yàn)的案例實(shí)現(xiàn)了外觀模式。正常情況下,我們開始一個(gè)實(shí)驗(yàn),需要判斷一系列前置條件:用戶是否已經(jīng)登陸,課程是否滿足學(xué)習(xí)的條件,實(shí)驗(yàn)是否滿足可以啟動(dòng)等。如果我們直接將這些對象在客戶端Client類中使用,無疑增加了客戶端類和User,Course和Lab類的耦合度。另外如果我們要增加新的前置條件判斷時(shí),我們就要修改Client類。為了解決這些問題,我們引入了外觀模式實(shí)現(xiàn)了FacadeLab類,在這個(gè)類中,我們通過對外提供接口FacadeLab.can_be_started來屏蔽客戶端類對子系統(tǒng)的直接訪問,使得新的客戶端類NewClient的代變得簡潔。
總的來說外觀模式的主要目的在于降低系統(tǒng)的復(fù)雜程度,在面向?qū)ο筌浖到y(tǒng)中,類與類之間的關(guān)系越多,不能表示系統(tǒng)設(shè)計(jì)得越好,反而表示系統(tǒng)中類之間的耦合度太大,這樣的系統(tǒng)在維護(hù)和修改時(shí)都缺乏靈活性,因?yàn)橐粋€(gè)類的改動(dòng)會(huì)導(dǎo)致多個(gè)類發(fā)生變化,而外觀模式的引入在很大程度上降低了類與類之間的耦合關(guān)系。引入外觀模式之后,增加新的子系統(tǒng)或者移除子系統(tǒng)都非常方便,客戶類無須進(jìn)行修改(或者極少的修改),只需要在外觀類中增加或移除對子系統(tǒng)的引用即可。
到這里所有的設(shè)計(jì)模式就已經(jīng)學(xué)習(xí)完啦,總的來說設(shè)計(jì)模式的目的就是為了是代碼解耦。設(shè)計(jì)模式的學(xué)習(xí)是一個(gè)長期的過程,在平時(shí)的代碼編寫過程中要多思考能否應(yīng)用設(shè)計(jì)模式。良好的應(yīng)用設(shè)計(jì)模式,能使我們的代碼更加靈活,適應(yīng)性更強(qiáng)。除了設(shè)計(jì)模式,其實(shí)還有六大設(shè)計(jì)原則可以指導(dǎo)我們的代碼設(shè)計(jì)。
六大設(shè)計(jì)準(zhǔn)則
3.1 單一職責(zé)原則 (Single Responsibility Principle)
顧名思義,單一職責(zé)的原則是說一個(gè)類直負(fù)責(zé)一項(xiàng)職責(zé)(操作)。如果一個(gè)類負(fù)責(zé)多個(gè)職責(zé),其中一項(xiàng)職責(zé)發(fā)生變化就需要修改整個(gè)類,這可能會(huì)導(dǎo)致其他的職責(zé)運(yùn)行錯(cuò)誤。一個(gè)類,只應(yīng)該有一個(gè)引起它變化的原因。
其優(yōu)點(diǎn)有:
可以降低類的復(fù)雜度,一個(gè)類只負(fù)責(zé)一項(xiàng)職責(zé),其邏輯肯定要比負(fù)責(zé)多項(xiàng)職責(zé)簡單的多;
提高類的可讀性,提高系統(tǒng)的可維護(hù)性;
變更引起的風(fēng)險(xiǎn)降低,變更是必然的,如果單一職責(zé)原則遵守的好,當(dāng)修改一個(gè)功能時(shí),可以顯著降低對其他功能的影響。
3.2 里氏替換原則 (Liskov Substitution Principle)
里氏替換的意思是說所有引用基類的地方必須能透明地使用其子類的對象。這種情況在代碼中隨處可以,我們在類中使用基類進(jìn)行定義,而在運(yùn)行時(shí)使用子類對象,為了確保代碼運(yùn)行正常,在實(shí)現(xiàn)子類時(shí)要注意以下一些地方:
子類可以實(shí)現(xiàn)父類的抽象方法,但不能覆蓋父類的非抽象方法;
子類中可以增加自己特有的方法;
當(dāng)子類的方法重載父類的方法時(shí),子類方法的輸入?yún)?shù)要比父類方法的輸入?yún)?shù)更寬松;
3.3 依賴倒置原則 (Dependence Inversion Principle)
定義:抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)當(dāng)依賴于抽象。換言之,要針對接口編程,而不是針對實(shí)現(xiàn)編程。依賴倒置原則要求我們在程序代碼中傳遞參數(shù)時(shí)或在關(guān)聯(lián)關(guān)系中,盡量引用層次高的抽象層類,即使用接口和抽象類進(jìn)行變量類型聲明、參數(shù)類型聲明、方法返回類型聲明,以及數(shù)據(jù)類型的轉(zhuǎn)換等,而不要用具體類來做這些事情。依賴倒置原則的本質(zhì)就是通過抽象(接口或抽象類)使各個(gè)類或模塊的實(shí)現(xiàn)彼此獨(dú)立,不互相影響,實(shí)現(xiàn)模塊間的松耦合。在編寫代碼中落到實(shí)處,需要注意以下一些地方:
每個(gè)類盡量都有接口或抽象類,或者抽象類和接口兩者都具備;
變量的表名類型盡量是接口或者抽象類;
盡量不要覆寫基類的方法;
結(jié)合里氏替換原則使用。
由于 Python 是一門動(dòng)態(tài)語言,在傳遞參數(shù)時(shí)不需要定義具體類型,所以依賴倒置原則其實(shí)一定程度上已經(jīng)內(nèi)嵌在了 Python 語言中。
3.4 接口隔離原則 (Interface Segregation Principle)
接口隔離原則提示我們客戶端不應(yīng)該依賴它不需要的接口,一個(gè)類對另一個(gè)類的依賴應(yīng)該建立在最小的接口上。根據(jù)接口隔離原則,當(dāng)一個(gè)接口太大時(shí),我們需要將它分割成一些更細(xì)小的接口,使用該接口的客戶端僅需知道與之相關(guān)的方法即可。每一個(gè)接口應(yīng)該承擔(dān)一種相對獨(dú)立的角色,不干不該干的事,該干的事都要干。
看到這里你們或許認(rèn)為接口隔離原則與單一職責(zé)原則是相同的。其實(shí)接口隔離原則與單一職責(zé)原則的審視角度是不相同的,單一職責(zé)原則要求的是類和接口職責(zé)單一,注重的是職責(zé),這是業(yè)務(wù)邏輯上的劃分,而接口隔離原則要求接口的方法盡量少。在使用接口隔離原則時(shí),我們需要注意控制接口的粒度,接口不能太小,如果太小會(huì)導(dǎo)致系統(tǒng)中接口泛濫,不利于維護(hù);接口也不能太大,太大的接口將違背接口隔離原則,靈活性較差,使用起來很不方便。一般而言,接口中僅包含為某一類用戶定制的方法即可,不應(yīng)該強(qiáng)迫客戶依賴于那些它們不用的方法。
3.5 迪米特原則 (Law of Demeter)
定義:一個(gè)對象應(yīng)該對其他對象有最少的了解。通俗地講,一個(gè)類應(yīng)該對自己需要耦合或調(diào)用的類知道得最少,你(被耦合或調(diào)用的類)的內(nèi)部是如何復(fù)雜都和我沒關(guān)系,那是你的事情,我就知道你提供的公開方法,我就調(diào)用這么多,其他的我一概不關(guān)心。迪米特法則指導(dǎo)我們在設(shè)計(jì)系統(tǒng)時(shí),應(yīng)該盡量減少對象之間的交互,如果兩個(gè)對象之間不必彼此直接通信,那么這兩個(gè)對象就不應(yīng)當(dāng)發(fā)生任何直接的相互作用,如果其中的一個(gè)對象需要調(diào)用另一個(gè)對象的某一個(gè)方法的話,可以通過第三者轉(zhuǎn)發(fā)這個(gè)調(diào)用。簡言之,就是通過引入一個(gè)合理的第三者來降低現(xiàn)有對象之間的耦合度。可以看到迪米特原則在代理模式和外觀模式中都有被使用。
3.6 開閉原則 (Open Closed Principle)
軟件實(shí)體應(yīng)該對擴(kuò)展開放,對修改關(guān)閉,其含義是說一個(gè)軟件實(shí)體應(yīng)該通過擴(kuò)展來實(shí)現(xiàn)變化,而不是通過修改已有的代碼來實(shí)現(xiàn)變化。根據(jù)開閉原則,在設(shè)計(jì)一個(gè)軟件系統(tǒng)模塊(類,方法)的時(shí)候,應(yīng)該可以在不修改原有的模塊(修改關(guān)閉)的基礎(chǔ)上,能擴(kuò)展其功能(擴(kuò)展開放)。遵循開閉原則的系統(tǒng)設(shè)計(jì),可以讓軟件系統(tǒng)可復(fù)用,并且易于維護(hù)。這也是系統(tǒng)設(shè)計(jì)需要遵循開閉原則的原因:
穩(wěn)定性:開閉原則要求擴(kuò)展功能不修改原來的代碼,這可以讓軟件系統(tǒng)在變化中保持穩(wěn)定。
擴(kuò)展性:開閉原則要求對擴(kuò)展開放,通過擴(kuò)展提供新的或改變原有的功能,讓軟件系統(tǒng)具有靈活的可擴(kuò)展性。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/45077.html
摘要:可以對文件進(jìn)行查看創(chuàng)建等功能,可以對文件內(nèi)容進(jìn)行添加修改刪除,且所使用到的函數(shù)在為,在同時(shí)支持和,但是在系列移除了函數(shù)。在及以后,又支持同時(shí)對多個(gè)文件的上下文進(jìn)行管理,即原文鏈接 Python可以對文件進(jìn)行查看、創(chuàng)建等功能,可以對文件內(nèi)容進(jìn)行添加、修改、刪除,且所使用到的函數(shù)在Python3.5.x為open,在Python2.7.x同時(shí)支持file和open,但是在3.5.x系列移除...
摘要:但是語言并沒有成功,究其原因,認(rèn)為是其非開標(biāo)識(shí)放造成的。已經(jīng)成為最受歡迎的程序設(shè)計(jì)語言之一。年月,該語言作者在郵件列表上宣布將于年月日終止支持。其中很重要的一項(xiàng)就是的縮進(jìn)規(guī)則。設(shè)計(jì)定位的設(shè)計(jì)哲學(xué)是優(yōu)雅明確簡單。 文本標(biāo)簽 換行標(biāo)簽 -- br 是單標(biāo)簽,意味著它沒有結(jié)束標(biāo)簽。起強(qiáng)制換行作用 段落中的文字段落中的文字段落中的文字 水平分割線 -- hr 與br相同,也是單標(biāo)簽??捎脕韰^(qū)分...
摘要:希望引以為戒鄭傳裝飾模式如果你了解,你肯定聽過裝飾器模式。在面向?qū)ο笾?,裝飾模式指動(dòng)態(tài)地給一個(gè)對象添加一些額外的職責(zé)。就增加一些功能來說,裝飾模式比生成子類更為靈活。 漫談 如果作為一個(gè)Python入門,不了解Python裝飾器也沒什么,但是如果作為一個(gè)中級(jí)Python開發(fā)人員,如果再不對python裝飾器熟稔于心的話,那么可能并沒有量變積累到質(zhì)變。 我以前也看過很多講python 裝...
摘要:該系列文章入門,編程基礎(chǔ)概念介紹變量,條件,函數(shù),循環(huán)中的數(shù)據(jù)類型,,,,在中創(chuàng)建對象學(xué)一門編程語言正在變得越來越容易,只要念過高中甚至是初中小學(xué),能熟練聊和懂得一點(diǎn)點(diǎn)軟件的人,入門一門編程語言都不在話下。 該系列文章: 《python入門,編程基礎(chǔ)概念介紹(變量,條件,函數(shù),循環(huán))》 《python中的數(shù)據(jù)類型(list,tuple,dict,set,None)》 《在python...
摘要:作者宋天龍來源科技大本營導(dǎo)語一切都始于年的那個(gè)圣誕節(jié),的誕生并不算恰逢其時(shí),它崛起充滿了機(jī)遇巧合,也有其必然性。年的圣誕節(jié),開始編寫語言的編譯器。年發(fā)布的標(biāo)志著的框架基本確定。年月發(fā)布了系列的最后一個(gè)版本,主版本號(hào)為。 showImg(https://segmentfault.com/img/remote/1460000019862276); 作者 | 宋天龍來源 | AI科技大本營 ...
閱讀 3445·2021-09-26 09:46
閱讀 2792·2021-09-13 10:23
閱讀 3533·2021-09-07 10:24
閱讀 2400·2019-08-29 13:20
閱讀 2927·2019-08-28 17:57
閱讀 3080·2019-08-26 13:27
閱讀 1187·2019-08-26 12:09
閱讀 514·2019-08-26 10:27