一、策略模式程序員
策略模式將各類操做(算法)進行封裝,並使它們之間能夠互換。互換的意思是說能夠動態改變對象的操做方式(算法)。redis
import abc算法
class AbsShow(object):docker
""" 抽象顯示對象 """ __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 的顯示方式,這也體現了策略模式的互換行爲。
2、觀察者模式app
所謂觀察者模式,就是說當一個對象發生變化時,觀察者能及時獲得通知並更新。觀察者模式在不少地方都有應用,好比在實驗樓上關注課程。下面就讓咱們看下實驗樓針對課程關注功能是怎麼實現觀察者模式的。框架
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對象會通知到全部觀察者。能夠看出,觀察者模式使被觀察的對象(主題)和觀察者之間解耦了。
3、命令模式
顧名思義,命令模式就是對命令的封裝。所謂封裝命令,就是將一系列操做封裝到命令類中,而且命令類只須要對外公開一個執行方法execute,調用此命令的對象只須要執行命令的execute方法就能夠完成全部的操做。這樣調用此命令的對象就和命令具體操做之間解耦了。更進一步,經過命令模式咱們能夠抽象出調用者,接收者和命令三個對象。調用者就是簡單的調用命令,而後將命令發送給接收者,而接收者則接收並執行命令,執行命令的方式也是簡單的調用命令的execute方法就能夠了。發送者與接收者之間沒有直接引用關係,發送請求的對象只須要知道如何發送請求,而沒必要知道如何完成請求。下面讓咱們使用 Python 來實現命令模式。
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()方法能完成虛擬機的相關操做。
總的來講,命令模式的封裝性很好:每一個命令都被封裝起來,對於客戶端來講,須要什麼功能就去調用相應的命令,而無需知道命令具體是怎麼執行的。同時命令模式的擴展性很好,在命令模式中,在接收者類中通常會對操做進行最基本的封裝,命令類則經過對這些基本的操做進行二次封裝,當增長新命令的時候,對命令類的編寫通常不是從零開始的,有大量的接收者類可供調用,也有大量的命令類可供調用,代碼的複用性很好。
4、模板方法模式
提到模板,不難想到文檔模板、簡歷模板等。其實模板方法模式中的模板就是這個意思,在模板方法模式中,咱們先定義一個類模板,在這個類中,咱們定義了各類操做的順序(輪轂或者說是骨架),可是並不實現這些操做,這些操做由子類來操做。
舉個例子,假若有一天咱們想去去三岔湖釣魚,那麼須要怎麼操做呢?第一步:準備魚餌;第二步:到達三岔湖;第三步;選擇釣點。這三步操做不能亂序,不然咱們就釣不成魚啦。可是在準備魚餌的時候,能夠經過淘寶購買,也能夠在漁具店裏購買,這些都是不肯定。同時怎麼去三岔湖也是不肯定的,你能夠開車去,也能夠搭車去。在這種狀況下,模板方法模式就很是有用了。在模板方法模式中咱們先定義去三岔湖釣魚的操做步驟,每一步的具體操做在不一樣的子類中可能都有不一樣的實現。下面讓咱們看看具體的代碼吧。
在 /home/shiyanlou/Code/template-1.py 文件中添加以下代碼:
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
怎麼樣?模板方法模式是否是簡單易懂呢?模板方法模式是結構最簡單的行爲型設計模式,在其結構中只存在父類與子類之間的繼承關係。經過使用模板方法模式,能夠將一些複雜流程的實現步驟封裝在一系列基本方法中,在抽象父類中提供一個稱之爲模板方法的方法來定義這些基本方法的執行次序,而經過其子類來覆蓋某些步驟,從而使得相同的算法框架能夠有不一樣的執行結果。模板方法模式提供了一個模板方法來定義算法框架,而某些具體步驟的實現能夠在其子類中完成。
到目前爲止,這一節實驗就要結束了。在本節實驗中,咱們學習了四種設計模式:策略模式,觀察者模式,命令模式以及模板方法模式。這四種設計模式都是行爲型模式。什麼是行爲型模式呢?
按照定義,行爲型模式是對在不一樣的對象之間劃分責任和算法的抽象化。行爲型模式不只僅關注類和對象的結構,並且重點關注它們之間的相互做用。經過行爲型模式,能夠更加清晰地劃分類與對象的職責,並研究系統在運行時實例對象之間的交互。在系統運行時,對象並非孤立的,它們能夠經過相互通訊與協做完成某些複雜功能,一個對象在運行時也將影響到其餘對象的運行。
5、適配器模式
何爲適配器?你買過水貨電子產品嗎?假如你是買的港行的電子產品,那麼其電源插頭是香港標準的,在大陸不能直接使用。通常狀況下,商家會附贈一個轉換插頭。你把電子產品的電源插頭插在轉換插頭上,而後轉換插頭插上電源,電子產品就能正常工做了。這就是適配器模式。下面讓咱們看看適配器模式在實驗樓中使用吧。
在 /home/shiyanlou/Code/adapter-1.py 文件中添加以下代碼:
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對象就能夠兼容老的接口,使系統正常運行。
適配器模式就是把一個類的接口變換成客戶端所期待的另外一種接口,使本來因接口不兼容而沒法在一塊兒工做的兩個類可以在一塊兒工做。
6、裝飾者模式
裝飾者模式?裝飾器?對於一個 Python 程序員來講,這再熟悉不過了。準確來講,裝飾者模式能動態的給對象添加行爲。若是你對 Flask 比較熟悉的話,應該知道在使用 Flask-Login 的時候可使用 login_required 裝飾器包裝一個須要用戶登陸訪問的view。直接看看咱們實現的裝飾者模式吧。
在 /home/shiyanlou/Code/decorator-1.py 文件中添加以下代碼:
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函數。
7、代理模式
代理模式在生活中比比皆是。好比你經過代理上網,好比你不會去華西牛奶生產地直接買牛奶,而是到超市這個代理購買牛奶,這些例子中都存在着代理模式。所謂代理模式就是給一個對象提供一個代理,並由代理對象控制對原對象的訪問。經過代理,咱們能夠對訪問作一些控制。在開發網站的過程當中,針對一些頻繁訪問的資源,咱們會使用緩存。在開發實驗樓的過程當中也是如此,咱們經過緩存代理解決了一些熱點資源的訪問問題。下面讓咱們看看是怎麼實現的吧。
在 /home/shiyanlou/Code/proxy-1.py 文件中添加以下代碼:
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 中,提升了後續的訪問速度。
從上面的代碼能夠看出,代理對象和真實的對象之間都實現了共同的接口,這使咱們能夠在不改變原接口狀況下,使用真實對象的地方均可以使用代理對象。其次,代理對象在客戶端和真實對象之間直接起到了中介做用,同時經過代理對象,咱們能夠在將客戶請求傳遞給真實對象以前作一些必要的預處理。
8、組合模式
什麼是組合模式?按照定義來講,組合模式是將對象組合成樹形結構表示,使得客戶端對單個對象和組合對象的使用具備一致性。組合模式的使用一般會生成一顆對象樹,對象樹中的葉子結點表明單個對象,其餘節點表明組合對象。調用某一組合對象的方法,其實會迭代調用全部其葉子對象的方法。
使用組合模式的經典例子是 Linux 系統內的樹形菜單和文件系統。在樹形菜單中,每一項菜單多是一個組合對象,其包含了菜單項和子菜單,這樣就造成了一棵對象樹。在文件系統中,葉子對象就是文件,而文件夾就是組合對象,文件夾能夠包含文件夾和文件,一樣又造成了一棵對象樹。一樣的例子還有員工和領導之間的關係,下面就讓咱們實現下吧。
在 /home/shiyanlou/Code/composite-1.py 文件中添加以下代碼:
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。
10、外觀模式
所謂外觀模式,就是將各類子系統的複雜操做經過外觀模式簡化,讓客戶端使用起來更方便簡潔。好比你夏天晚上出門時,要關閉電燈,關閉電視機,關閉空調,若是有了一個總開關,經過它能夠關閉電燈,電視機和空調,你出門的時候關閉總開關就好了。在這個例子中,你就是客戶端,總開關就是外觀模式的化身。在實驗樓中,外觀模式應用在建立實驗環境的接口上。讓咱們看看具體的代碼吧。
在 /home/shiyanlou/Code/facade-1.py 文件中添加以下代碼:
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)
軟件實體應該對擴展開放,對修改關閉,其含義是說一個軟件實體應該經過擴展來實現變化,而不是經過修改已有的代碼來實現變化。根據開閉原則,在設計一個軟件系統模塊(類,方法)的時候,應該能夠在不修改原有的模塊(修改關閉)的基礎上,能擴展其功能(擴展開放)。遵循開閉原則的系統設計,可讓軟件系統可複用,而且易於維護。這也是系統設計須要遵循開閉原則的緣由:
穩定性:開閉原則要求擴展功能不修改原來的代碼,這可讓軟件系統在變化中保持穩定。擴展性:開閉原則要求對擴展開放,經過擴展提供新的或改變原有的功能,讓軟件系統具備靈活的可擴展性。