什么是設計模式
設計模式是針對面向對象的,所謂的設計就是在編寫程序之前對其有一個基礎的架構,如需要創建哪些類,定義那些變量,有哪些方法。每一個設計模式系統的命名、解釋和評價了面向對象系統中一個重要和重復出現的設計。設計模式四個基本要素:模式名稱、問題、解決方案、效果。
一些需要的知識點
面向對象的三大特性:
-
封裝:封裝指的是兩方面 , 一方面把相關的功能歸納到一個類中, 另一方面把數據封裝到對象中。
-
繼承:繼承就是為了提升代碼的復用性,更加靈活。
-
多態:多態指的是一個變量可以有多種狀態或者多種形態。
接口:
-
本質是一個抽象類,要求繼承接口的類,必須實現接口內定義的一些方法。
-
抽象類就是不能實例化的類。
-
如果繼承抽象類后一樣沒有實現抽象方法,那么它也是一個抽象類。
-
作用:限制繼承接口的類的方法的名稱及調用方式;隱藏了類的內部實現。
#在python中沒有嚴謹的限制函數簽名,只限制了函數名。 from abc import abstractmethod, ABCMeta class Interface(metaclass=ABCMeta): @abstractmethod def method(self, arg): pass
設計模式六大原則:
-
開閉原則:一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。即軟件實體應盡量在不修改原有代碼的情況下進行擴展。
-
里氏(Liskov)替換原則:所有引用基類(父類)的地方必須能透明地使用其子類的對象。
-
能夠進行統一的調用,函數簽名必須一致。
-
-
依賴倒置原則:高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該依賴細節;細節應該依賴抽象。換言之,要針對接口編程,而不是針對實現編程。
-
A,B同時調用C模塊,C模塊進行修改,那么AB模塊可能也需要修改,所以接口就是哪些東西不能改的限制,先有接口后又代碼。
-
-
接口隔離原則:使用多個專門的接口,而不使用單一的總接口,即客戶端不應該依賴那些它不需要的接口。
-
如果不實現不需要的方法,那么就無法實例化,可以使用多繼承來繼承多個小基類。
-
-
迪米特法則:一個軟件實體應當盡可能少地與其他實體發生相互作用。
-
解耦,讓依賴越少,那么自由度越高。
-
-
單一職責原則:不要存在多於一個導致類變更的原因。
-
通俗的說,即一個類只負責一項職責。
-
設計模式分類
創建型模式:工廠方法模式,抽象工廠模式,創建者模式,原型模式,單例模式。結構型模式:適配器模式,橋模式,組合模式,裝飾模式,外觀模式,享元模式,代理模式。行為型模式:解釋器模式,責任鏈模式,命令模式,迭代器模式,中介者模式,備忘錄模式,觀察者模式,狀態模式,策略模式,模板方法模式。
創建型模式
創建型模式的重點在於如何創建一個對象。
簡單工廠模式
簡單工廠模式:
-
不直接向客戶端暴露對象創建的實現細節,而是通過一個工廠類來負責創建產品類的實例。
-
角色:工廠角色,抽象產品角色,具體產品角色。
-
優點:隱藏了對象創建的實現細節,客戶端不需要修改代碼
-
缺點:
-
違反了單一職責原則,將創建邏輯集中到一個工廠類里。
-
當添加新產品時,需要修改工廠類代碼,違反了開閉原則
-
from abc import abstractmethod, ABCMeta class Payment(metaclass=ABCMeta): @abstractmethod def pay(self, money): raise NotImplementedError class Alipay(Payment): def __init__(self, use_huabei=False): self.use_huabei = use_huabei def pay(self, money): if self.use_huabei: print("花唄支付%s元" % money) else: print("支付寶支付%s元" % money) class ApplePay(Payment): def __init__(self, s): pass def pay(self, money): print("蘋果支付%s元" % money) class PaymentFactory: def create_payment(self, method): if method == "alipay": return Alipay() elif method == "applepay": return ApplePay() elif method == 'huabei': return Alipay(use_huabei=True) else: raise NameError(method) pf = PaymentFactory() p = pf.create_payment('huabei') p.pay(100)
工廠方法模式:
-
定義一個用於創建對象的接口(工廠接口),讓子類決定實例化哪一個產品類。
-
角色:抽象工廠角色,具體工廠角色,抽象產品角色,具體產品角色。
-
工廠方法模式擁有繼承抽象工廠類的抽象工廠類。
-
使用場景:需要生產多種大量的對象的時候;需要降低耦合度的時候;當系統的產品種類需要擴展的時候。
-
優點: 每個具體產品對應一個具體工廠類,隱藏了對象創建的細節。
-
缺點:每增加一個具體產品類,就要添加一個具體工廠類。
from abc import abstractmethod, ABCMeta class Payment(metaclass=ABCMeta): @abstractmethod def pay(self, money): raise NotImplementedError class Alipay(Payment): def __init__(self, use_huabei=False): self.use_huabei = use_huabei def pay(self, money): if self.use_huabei: print("花唄支付%s元" % money) else: print("支付寶支付%s元" % money) class ApplePay(Payment): def pay(self, money): print("蘋果支付%s元"%money) class PaymentFactory(metaclass=ABCMeta): @abstractmethod def create_payment(self): pass class AlipayFactory(PaymentFactory): def create_payment(self): return Alipay(use_huabei=False) class ApplePayFactory(PaymentFactory): def create_payment(self): return ApplePay() class HuabeiFactory(PaymentFactory): def create_payment(self): return Alipay(use_huabei=True)
抽象工廠模式:
-
多個對象需要一起使用的時候使用,多個小對象組合成一個大對象。如面向對象中實現代碼復用的組合。
-
角色:抽象工廠角色,具體工廠角色,抽象產品角色,具體產品角色,客戶端。
-
抽象工廠模式中每個具體工廠都生產一套產品。
-
適用場景:系統要獨立於產品的創建與組合時;強調一系列相關的產品對象的設計以便進行聯合使用時;提供一個產品類庫,想隱藏產品的具體實現時。
-
優點:將客戶端與類的具體實現相分離;每個工廠創建了一個完整的產品系列,使得易於交換產品系列;有利於產品的一致性(即產品之間的約束關系)。
-
缺點:難以支持新種類的(抽象)產品
from abc import abstractmethod, ABCMeta # ------抽象產品------ class PhoneShell(metaclass=ABCMeta): @abstractmethod def show_shell(self): pass class CPU(metaclass=ABCMeta): @abstractmethod def show_cpu(self): pass class OS(metaclass=ABCMeta): @abstractmethod def show_os(self): pass # ------抽象工廠------ class PhoneFactory(metaclass=ABCMeta): @abstractmethod def make_shell(self): pass @abstractmethod def make_cpu(self): pass @abstractmethod def make_os(self): pass # ------具體產品------ class SmallShell(PhoneShell): def show_shell(self): print("普通手機小手機殼") class BigShell(PhoneShell): def show_shell(self): print("普通手機大手機殼") class AppleShell(PhoneShell): def show_shell(self): print("蘋果手機殼") class SnapDragonCPU(CPU): def show_cpu(self): print("驍龍CPU") class MediaTekCPU(CPU): def show_cpu(self): print("聯發科CPU") class AppleCPU(CPU): def show_cpu(self): print("蘋果CPU") class Android(OS): def show_os(self): print("Android系統") class IOS(OS): def show_os(self): print("iOS系統") # ------具體工廠------ class MiFactory(PhoneFactory): def make_cpu(self): return SnapDragonCPU() def make_os(self): return Android() def make_shell(self): return BigShell() class HuaweiFactory(PhoneFactory): def make_cpu(self): return MediaTekCPU() def make_os(self): return Android() def make_shell(self): return SmallShell() class IPhoneFactory(PhoneFactory): def make_cpu(self): return AppleCPU() def make_os(self): return IOS() def make_shell(self): return AppleShell() # ------客戶端------ class Phone: def __init__(self, cpu, os, shell): self.cpu = cpu self.os = os self.shell = shell def show_info(self): print("手機信息:") self.cpu.show_cpu() self.os.show_os() self.shell.show_shell() def make_phone(factory): cpu = factory.make_cpu() os = factory.make_os() shell = factory.make_shell() return Phone(cpu, os, shell) p1 = make_phone(IPhoneFactory()) p1.show_info()
將一個復雜的對象的構建和它的表示分離,使同樣的構建過程可以創建不同的表示。
-
角色:抽象建造者,具體建造者,指揮者,產品。
-
建造者模式與抽象工廠模式相似,也用來創建復雜對象。主要區別是建造者模式着重一步步構造一個復雜對象,而抽象工廠模式着重於多個系列的產品對象。
-
適用場景:
-
當創建復雜對象的算法(Director)應該獨立於該對象的組成部分以及它們的裝配方式(Builder)時。
-
當構造過程允許被構造的對象有不同的表示時(不同Builder)。
-
優點:隱藏了一個產品的內部結構和裝配過程;將構造代碼與表示代碼分開;可以對構造過程進行更精細的控制。
from abc import abstractmethod, ABCMeta #------產品------ class Player: def __init__(self, face=None, body=None, arm=None, leg=None): self.face = face self.arm = arm self.leg = leg self.body = body def __str__(self): return "%s, %s, %s, %s" % (self.face, self.arm, self.body, self.leg) #------建造者------ class PlayerBuilder(metaclass=ABCMeta): @abstractmethod def build_face(self): pass @abstractmethod def build_arm(self): pass @abstractmethod def build_leg(self): pass @abstractmethod def build_body(self): pass @abstractmethod def get_player(self): pass class BeautifulWomanBuilder(PlayerBuilder): def __init__(self): self.player = Player() def build_face(self): self.player.face = "漂亮臉蛋" def build_arm(self): self.player.arm="細胳膊" def build_body(self): self.player.body="細腰" def build_leg(self): self.player.leg="長腿" def get_player(self): return self.player #無所謂beautfulbuilder編寫的順序 class PlayerDirector: def build_player(self, builder): builder.build_body() builder.build_face() builder.build_arm() builder.build_leg() return builder.get_player() pd = PlayerDirector() pb = BeautifulWomanBuilder() p = pd.build_player(pb) print(p)
單例模式
保證一個類只有一個實例對象,並提供一個訪問它的全局訪問點,日志對象就是單例,數據庫連接對象也是單例的。
-
適用場景:當類只能有一個實例而且客戶可以從一個眾所周知的訪問點訪問它時。
-
優點:對唯一實例的受控訪問;單例相當於全局變量,但防止命名空間污染。
單例模式
保證一個類只有一個實例對象,並提供一個訪問它的全局訪問點,日志對象就是單例,數據庫連接對象也是單例的。適用場景:當類只能有一個實例而且客戶可以從一個眾所周知的訪問點訪問它時。優點:對唯一實例的受控訪問;單例相當於全局變量,但防止命名空間污染。
from abc import abstractmethod, ABCMeta class Singleton(object): def __new__(cls, *args, **kwargs): if not hasattr(cls, "_instance"): cls._instance = super(Singleton, cls).__new__(cls) return cls._instance class MyClass(Singleton): def __init__(self, name=None): if name is not None: self.name = name a = MyClass("a") print(a) print(a.name) b = MyClass("b") print(b) print(b.name) print(a) print(a.name)
結構型模式
適配器模式
當兩個類不能一起使用,那么增加一些方法使它們能夠一起使用,這就叫適配器模式。類適配器處理多個待適配類時,十分麻煩,所以通過組合來寫適配器,也就是對象適配器,會更好。
-
將一個類的接口轉換成客戶希望的另一個接口。適配器模式使得原本由於接口不兼容而不能一起工作的那些類可以一起工作。
-
角色:目標接口,待適配的類,適配器。
-
實現方式:類適配器,對象適配器。
-
適用場景:想使用一個已經存在的類,而它的接口不符合你的要求;(對象適配器)想使用一些已經存在的子類,但不可能對每一個都進行子類化以匹配它們的接口。對象適配器可以適配它的父類接口。)
from abc import abstractmethod, ABCMeta class Payment(metaclass=ABCMeta): @abstractmethod def pay(self, money): raise NotImplementedError class Alipay(Payment): def pay(self, money): print("支付寶支付%s元"%money) class ApplePay(Payment): def pay(self, money): print("蘋果支付%s元"%money) #------待適配類------ class WechatPay: def huaqian(self, money): print("微信支付%s元"%money) class BankPay: def huaqian(self, money): print("銀行卡支付%s元" % money) # # 類適配器 # class NewWechatPay(WechatPay, Payment): # def pay(self, money): # self.huaqian(money) # 對象適配器 class PaymentAdapter(Payment): def __init__(self, p): self.payment = p def pay(self, money): self.payment.huaqian(100) p = PaymentAdapter(BankPay()) p.pay(100)
代理模式
為其他對象提供一種代理以控制對這個對象的訪問。角色:抽象實體,實體,代理。使用場景:
-
遠程代理:為遠程的對象提供代理。
-
虛代理:根據需要創建很大的對象。
-
保護代理:控制對原始對象的訪問,用於對象有不同訪問權限時。優點:
-
遠程代理:可以隱藏對象位於遠程地址空間的事實。
-
虛代理:可以進行優化,例如根據要求創建對象。
-
保護代理:允許在訪問一個對象時有一些附加的內務處理。
from abc import ABCMeta, abstractmethod class Subject(metaclass=ABCMeta): @abstractmethod def get_content(self): pass class RealSubject(Subject): def __init__(self, filename): print("讀取%s文件內容"%filename) f = open(filename) self.content = f.read() f.close() def get_content(self): return self.content class ProxyB(Subject): def __init__(self, filename): self.filename = filename self.subj = None def get_content(self): if not self.subj: self.subj = RealSubject(self.filename) return self.subj.get_content() p = ProxyB("abc.txt")
組合模式
將對象組合成樹形結構以表示“部分-整體”的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。角色:抽象組件,葉子組件,復合組件,客戶端。適用場景:
-
表示對象的“部分-整體”層次結構(特別是結構是遞歸的)
-
希望用戶忽略組合對象與單個對象的不同,用戶統一地使用組合結構中的所有對象
- 優點:定義了包含基本對象和組合對象的類層次結構;簡化客戶端代碼,即客戶端可以一致地使用組合對象和單個對象;更容易增加新類型的組件。
-
缺點:很難限制組合中的組件。
from abc import abstractmethod, ABCMeta class Graphic(metaclass=ABCMeta): @abstractmethod def draw(self): pass @abstractmethod def add(self, graphic): pass def getchildren(self): pass class Point(Graphic): def __init__(self, x, y): self.x = x self.y = y def draw(self): print(self) def add(self, graphic): raise TypeError def getchildren(self): raise TypeError def __str__(self): return "點(%s, %s)" % (self.x, self.y) class Line(Graphic): def __init__(self, p1, p2): self.p1 = p1 self.p2 = p2 def draw(self): print(self) def add(self, graphic): raise TypeError def getchildren(self): raise TypeError def __str__(self): return "線段[%s, %s]" % (self.p1, self.p2) # 復合圖形 class Picture(Graphic): def __init__(self): self.children = [] def add(self, graphic): self.children.append(graphic) def getchildren(self): return self.children def draw(self): print("------復合圖形------") for g in self.children: g.draw() print("------END------") pic1 = Picture() pic1.add(Point(2,3)) pic1.add(Line(Point(1,2), Point(4,5))) pic1.add(Line(Point(0,1), Point(2,1))) pic2 = Picture() pic2.add(Point(-2,-1)) pic2.add(Line(Point(0,0), Point(1,1))) pic = Picture() pic.add(pic1) pic.add(pic2) pic1.draw()
行為型模式
觀察者模式(發布-訂閱模式)
定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時, 所有依賴於它的對象都得到通知並被自動更新。觀察者模式又稱“發布-訂閱”模式。
角色:
- 抽象主體(發布者)
- 具體主體
- 抽象發布者
- 具體發布者
優點:目標和觀察者之間的抽象耦合最小。
缺點:多個觀察者之間互不知道對方存在,因此一個觀察者對主題的修改可能造成錯誤的更新。
適用場景:
- 當一個抽象模型有兩方面,其中一個方面依賴於另一個方面。將這兩者封裝在獨立對象中以使它們可以各自獨立地改變和復用。
-
當對一個對象的改變需要同時改變其它對象,而不知道具體有多少對象有待改變。
- 對象之間不是緊密耦合。
from abc import ABCMeta, abstractmethod class Observer(metaclass=ABCMeta): @abstractmethod def update(self, notice): pass class Notice: def __init__(self): self.observers = [] def attach(self, obs): self.observers.append(obs) def detach(self, obs): self.observers.remove(obs) def notify(self): for obj in self.observers: obj.update(self) class ManagerNotice(Notice): def __init__(self, company_info=None): super().__init__() self.__company_info = company_info @property def company_info(self): return self.__company_info @company_info.setter def company_info(self, info): self.__company_info = info self.notify() class Manager(Observer): def __init__(self): self.company_info = None def update(self, noti): self.company_info = noti.company_info
策略模式
定義一系列的算法,把它們一個個封裝起來,並且使它們可相互替換。本模式使得算法可獨立於使用它的客戶而變化。
優點:定義了一系列可重用的算法和行為;消除了一些條件語句;提供行為的不同實現。
缺點:客戶必須了解全部策略;
適用場景:
- 相同算法的不同變體
- 封裝一些條件語句
- 適用一些客戶不需要知道的數據。
class Sort(metaclass=ABCMeta): @abstractmethod def sort(self, data): pass class Merge_sort(Sort): def sort(self, data): pass class Quick_sort(Sort): def sort(self, data): pass class Context: def __init__(self, data, strategy=None): self.data = data self.strategy = strategy def set_strategy(self, strategy): self.strategy = strategy def do_strategy(self): if self.strategy: self.strategy.sort(self.data) else: raise TypeError
責任鏈模式
使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關系。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它為止。
角色:抽象處理者,具體處理者,客戶端。
使用場景:
- 有多個對象可以處理一個請求,哪個對象處理由運行時決定
- 在不明確接收者的情況下,向多個對象中的一個提交一個請求
優點:降低耦合度,不需要知道是那個對象處理的請求。
缺點:請求不能保證一定被接受。
from abc import ABCMeta, abstractmethod class Handler(metaclass=ABCMeta): @abstractmethod def handle_leave(self, day): pass class GeneralManagerliHandler(Handler): def handle_leave(self, day): if day < 10: print("總經理批准%d天假"%day) else: print("呵呵") class DepartmentManagerHandler(Handler): def __init__(self): self.successor = GeneralManagerliHandler() def handle_leave(self, day): if day < 7: print("部門經理批准%d天假"%day) else: print("部門經理無權准假") self.successor.handle_leave(day) class ProjectDirectorHandler(Handler): def __init__(self): self.successor = DepartmentManagerHandler() def handle_leave(self, day): if day < 3: print("項目主管批准%d天假") else: print("項目主管無權准假") self.successor.handle_leave(day) day = 10 h = ProjectDirectorHandler() h.handle_leave(day)
模板方法模式
定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。如市面上的一些框架。
角色:
- 抽象類,定義抽象的原子操作,實現一個原子方法作為算法的骨架。
- 具體類,實現原子操作
適用場景:
- 一次性實現一個算法中不可變的部分。
- 把各個子類中的公共行為提出來,避免重復。
from abc import ABCMeta, abstractmethod class IOHandler(metaclass=ABCMeta): @abstractmethod def open(self, name): pass @abstractmethod def deal(self, change): pass @abstractmethod def close(self): pass def process(self, name, change): self.open(name) self.deal(change) self.close()