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