設計模式 1 (Python版)

Roy2048發表於2024-05-26

設計模式

解釋:

概念

《設計模式:可複用物件導向軟體的基礎》

  • 物件導向的特性:封裝,繼承,多型

介面:若干方法的集合

限制實現介面的類必須按照介面給定的呼叫方式實現這些方法

對高層模組隱藏了類的內部實現

#介面實現的兩種方法:

#1.寫一個父類,其他類必須繼承這個方法,若子類不實現這個方法,則丟擲異常
#缺點:如果不呼叫pay,則不會報錯
class Payment:
    def pay(self, money):
        raise NotImplementedError

#2.寫一個抽象類,讓其他類實現這個介面
from abc import ABCMeta, abstractmethod
class Pament(metaclass = ABCMeta):#指定為抽象類
    @abstractmethod#指定為抽象方法
    def pay(self,money):
        pass

class Alipay(Payment):
    def pay(self, money):#1.缺點:如果這裡不實現這個方法,並且沒有呼叫p.pay(money),則不會報錯
        pass            #2.若不實現此方法,報錯:can't instantiate 抽象類的抽象方法
class Wechatpay(Payment):
    def pay(self, money):
        pass
p = Alipay()
def finish_pay(p, money):
    p.pay(money)

設計原則solid

開放封閉原則 Open closed principle

類、模組、函式應該對擴充套件開放,對修改關閉。(在不修改原始碼的時候進行擴充套件)

里氏替換原則 Liskov substitution principle

所有引用父類的地方都必須透明的使用子類物件

Eg:傳入user 可以使用,傳入VIP(user) 必須也可以使用

依賴倒置原則 Dependence inversion principle

高層不應該依賴底層,二者都應該依賴抽象;抽象不應該依賴細節,細節應該依賴抽象。

高層邏輯不隨底層修改,都依賴介面;先定義介面,按照介面格式寫細節。

介面隔離原則 Interface segregation principle

使用多個專用介面,而不是使用單一的總介面,高層程式碼不應該依賴那些它不需要的介面。

#錯誤的方式:
class animal:實現walk,fly,swim方法
class Tiger(animal):只實現walk,則報錯,必須實現所有的抽象方法
#但Tiger不應該實現fly,swim,所以修改設計模式

#修改後的設計:
class LandAnimal(metaclass = ABCMeta)#實現walk
class WaterAnimal(metaclass = ABCMeta)#實現swim
class SkyAnimal(metaclass = ABCMeta)#實現fly
#採用多繼承的方法
class Tiger(LandAnimal):
    def walk:
        pass
class Frog(LandAnimal, WaterAnimal):
    def walk:
        pass
    def swim:
        pass

單一職責原則 Single responsibility principle

不要存在多於一個導致類變更的原因。

一個類只負責一項職責

迪米特法則 Law of Demeter

talk only to your immediate friends and not to strangers 兩個模組無需直接通訊,則不應該發生直接的相互呼叫,透過第三方轉發呼叫(降低類之間的耦合,提高模組的獨立性)

設計模式分類

建立型模式(5):聚焦於建立物件

工廠方法模式, 抽象工廠模式,建立者模式, 原型模式, 單例模式

結構型模式(7):聚焦於類之間

介面卡模式, 橋模式,組合模式,裝飾模式,外觀模式、享元模式, 代理模式

行為型模式(11):關注類的具體行為

直譯器模式, 責任鏈模式,命令模式迭代器模式中介者模式備忘錄模式,觀察者模式,狀態模式,策略模式,訪問者模式,模板方法模式


模式詳解

1. 前言:簡單工廠模式

解釋:不直接向client暴露物件建立的實現細節,而是透過一個工廠類來負責建立產品類的例項。

工廠類:creator, 抽象產品:介面, 具體產品: 繼承介面的類

優點:隱藏了物件建立的實現細節,客戶端不需要修改程式碼

缺點:違反單一職責原則,將建立邏輯集中到一個工廠類

​ 新增新產品時,需要修改工廠類程式碼,違反了開閉原則

from abc import ABCMeta, abstractmethod
#抽象產品
class Pament(metaclass = ABCMeta):
    @abstractmethod
    def pay(self,money):
        pass
#具體產品
class Alipay(Payment):
    def pay(self, money):
        print('支付寶支付%d'% money)
class Wechatpay(Payment):
    def pay(self, money):
        print('微信支付%d'% money)
# 上面為抽象產品和具體產品
# 下面為生產物件:
#具體工廠
class PaymentFactory:
    def create_payment(self, method):#good part 可以把時間戳、證書之類的隱藏在工廠類裡面
        if method == 'alipay':        #而client不需要知道這些
            return Alipay()
        elif method == 'wechat':
            return Wechatpay()
        else:
            raise TypeError("no such pament named%s" % method)
#client呼叫
pf = PamentFactory()
p = pf.create_payment('alipay')
p.pay(100)

2. 工廠方法模式

增加了抽象工廠


內容:定義一個建立工廠的介面,讓子類決定例項化哪一種產品類


抽象工廠類:creator, 具體工廠類: concrete creator, 抽象產品類: product, 具體產品類: concrete product

新增一個工廠介面來約束工廠方法

即建立一個抽象工廠,多個不同的工廠類

優點:每個具體產品由對應的具體工廠類產生,不需要修改工廠類程式碼;隱藏了物件建立的實現細節

缺點:每新增一個具體產品類,就必須新增一個相應的具體工廠類

#抽象工廠
class PaymentFactory(metaclass = ABCMeta):
    @abstractmethod
    def create_payment(self):
        pass
#具體工廠
class AlipayFactory(PaymentFactory):
    def create_payment(self):
        return Alipay()
class WechatFactory(PaymentFactory):
    def create_payment(self):
        return Wechatpay()
#client呼叫
pf = AlipayFactory()
p = pf.create_payment()
p.pay(100)

3. 抽象工廠模式(與前面完全不同應用場景)

內容:定義一個工廠類介面,讓工廠子類建立一系列相關或依賴的物件

如,產生一個手機,需要手機殼,cpu,os三部分,每種類物件都有不同的種類(對os有安卓和ios)。對每個具體工廠,分別生產一部手機需要三個物件。

相比工廠方法模式,抽象工廠模式種的每個具體工廠都生產一套產品(一系列)。

優點:將客戶端與類實現相分離;每個工廠建立了一個完整的產品系列,易於交換產品系列;有利於產品的一致性(產品間的約束關係)

缺點:難以支援新種類的產品,需要新增產品,改變抽象類工廠,改變具體工廠

from abc import ABCMeta, abstractmethod
#抽象產品
class OS(metaclass = ABCMeta):
    @abstractmethod
    def show_OS(self):
        pass
class CPU(metaclass = ABCMeta):
    @abstractmethod
    def show_CPU(self):
        pass
#抽象工廠
class PhoneFactory(metaclass = ABCMeta):
    @abstractmethod
    def make_OS(self):
        pass
    @abstractmethod
    def make_CPU(self):
        pass
#具體產品
class androidOS(OS):
    def show_OS(self):
        print('安卓')
class IOS(OS):
    def show_OS(self):
        print('apple')

class gaotongCPU(CPU):
    def show_CPU(self):
        print('高通')
class lianfakeCPU(CPU):
    def show_CPU(self):
        print('聯發科')

#具體工廠(同時限制組裝,避免android安裝到蘋果上)
class MiFactory(PhoneFactory):
    def make_OS(self):
        return androidOS()
    def make_CPU(self):
        return gaotongCPU()

#client
#建立手機樣式
class Phone:
    def __init__(self, os, cpu):
        self.os = os
        self.cpu = cpu
    def show_info(self):
        self.os.show_OS()
        self.cpu.show_CPU()
#傳入 手機工廠
def make_phone(factory):
    cpu = factory.make_CPU()
    os = factory.make_OS()
    return Phone(os, cpu)
phone1 = make_phone(MiFactory())
phone1.show_info()

4.建造者模式

內容:將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。

角色:抽象建造,具體建造;指揮者,產品

產品 = 指揮者.方法(建造者)

建造者模式與抽象工廠模式類似,也用來建立複雜物件。

區別:建造者模式著重一步步構造一個複雜物件,抽象工廠模式著重於多個系列的產品物件

優點:隱藏產品內部結構和裝配,構造程式碼表示程式碼分開,可以對構造過程進行更精細的控制

class Player:
    def __init__(self, face=None, body=None, arm=None, leg=None):
        self.face = face
        self.body = body
        self.arm = arm
        self.leg = leg
    def __str__(self):
        return '%s, %s, %s, %s'%(self.face, self.body, self.arm, self.leg)
#抽象建造
class PlayerBuilder(metaclass = ABCMeta):
    @abstractmethod
    def build_face(self):
        pass
    def build_body(self):
        pass
    def build_arm(self):
        pass
    def build_leg(self):
        pass
 #具體建造者   
class SexyGirl(PlayerBuilder):
    def __init__(self):
        self.player = Player()

    def build_face(self):
        self.player.face = 'lian'
    def build_body(self):
        self.player.body = 'shengti'
    def build_arm(self):
        self.player.arm = 'gebo'
    def build_leg(self):
        self.player.leg = 'tui'
class Monster(PlayerBuilder):
    def __init__(self):
        self.player = Player()
    def build_face(self):
        self.player.face = 'lian2'
    def build_body(self):
        self.player.body = 'shengti2'
    def build_arm(self):
        self.player.arm = 'gebo2'
    def build_leg(self):
        self.player.leg = 'tui2'
#控制組裝順序,如必須現有body,後有face
class PlayerDirector:
    def build_player(self, builder):
        builder.build_body()
        builder.build_face()
        builder.build_arm()
        builder.build_leg()
        return builder.player

#client
builder1 = Monster()
director1 = PlayerDirector()
monster1 = director1.build_player(builder1)

5. 單例模式

內容:保證一個類只有一個例項,並提供一個訪問它的全域性訪問點

角色:單例

優點:對唯一例項的受控訪問;單例相當於全域性變數,但防止了名稱空間被汙染

class Singleton:
    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,a):
        self.a = a
a = MyClass(10)
b = MyClass(20)
print(a.a)
print(b.a)  
#列印都是20
#因為
print(id(a),id(b))#這倆相同

抽象工廠模式和建造者模式相比與簡單工廠模式和工廠方法模式更靈活,更復雜

通常,以簡單工廠模式或工廠方法模式開始


6. 介面卡模式

內容:將一個類的介面轉換為client希望的另一個介面。介面卡模式使得原本由於介面不相容而不能一起工作的那些類可以一起工作。

實現方法有兩種:類介面卡(多繼承實現)

​ 物件介面卡(使用組合實現)


角色:目標介面,待適配的類(新寫的類),介面卡


使用場景:

類介面卡:想使用一個已經存在的類,而介面不符合要求。

物件介面卡:想使用一些已經存在的子類,但不可能對每個子類都去匹配,物件介面卡可以批次適配

#組合:在一個類複用其他類的方法
#class B想使用A的方法,則把A()放入另一個類種
class A:
    pass
class B:
    def __init__(self):
        self.a = A()
from abc import ABCMeta, abstractmethod
class Payment(metaclass = ABCMeta):
    @abstractmethod
    def pay(self, money):
        pass
class Alipay(Payment):
    def pay(self, money):
        print('Alipay')
class Wechatpay(Payment):
    def pay(self, money):
        print('Wechat')
#another coder:
class Bankpay:
    def cost(self, money):
        print('Bankpay')
#client
p = Alipay()
p.pay(100)#正常使用
p = Bankpay()
p.pay(100)#無法使用
#1.複用寫好的程式碼2.bankpay的程式碼可能在別的地方已經呼叫了
#********************   第一種介面卡:套殼,多繼承
class Newbankpay(Payment, Bankpay):#繼承payment實現介面統一,繼承bankpay實現程式碼複用
    def pay(self, money):
        self.cost(money)
#********************   第二種介面卡:組合,解決多個都含有cost方法的類的問題
class PaymentAdapter(newpayment):#新寫的類作為形參傳入
    def __init__(self, payment):
        self.payment = payment
    def pay(self, money):
        self.payment.cost(money)
#此時client呼叫要寫
p = PaymentAdapter(Bankpay())
p.pay(100)

7. 橋模式

將一個事物的兩個維度進行分離,使其都可以獨立的變化。(方便擴充套件)

內容:抽象,細化抽象,實現,具體實現

eg: 畫圖有形狀和顏色兩個維度:形狀.draw()作為抽象(呼叫實現程式碼),顏色.paint()作為實現(真正實現了程式碼),必須有了顏色之後才可以實現

優點 :抽象 實現的分離; 優秀的擴充套件能力

class Shape:
    pass
class Line(Shape):
    pass
class Rectangle(Shape):
    pass
#寫紅色,藍色,綠色的:
class RedLine(Line):
    pass
class BlueLine(Line):
    pass
class RedRectangle(Rectangle):#寫的類過於多,不容易擴充套件
    pass                    #耦合方式過於緊密

橋模式:組合

from abc import ABCMeta, abstractmethod
class Shape(metaclass = ABCMeta):
    def __init__(self, color):
        self.color = color
    @abstractmethod
    def draw(self):
        pass
class Color(metaclass = ABCMeta):
    @abstractmethod
    def paint(self, shape):
        pass
#形狀維度:
class Rectangle(Shape):
    name = 'rectangle'
    def draw(self):
        self.color.paint(self)
#顏色維度:
class Red(Color):
    def paint(self.shape):
        prnt(shape.name)

#client呼叫
rec = Rectangle(Red())
rec.draw()

8. 組合模式

內容:將物件組合成樹形結構以表示‘部分-整體’的層次結構。組合模式使得使用者對單個物件組合物件的使用具有一致性

角色: 抽象元件,葉子節點,複合元件,client

適用場景:表示物件的‘部分-整體’層次結構(遞迴了)

希望使用者忽略組合物件與單個物件的不同,統一的使用組合結構中的所有物件。(如ppt中的圖形一樣可以組合可以單獨使用)

優點: 定義了基本物件和組合物件的類層次結構;簡化client程式碼,可以一致使用組合物件和單獨物件。更容易新增新型別的元件。

from abc import ABCMeta, abstractmethod
class Graphic(metaclass = ABCMeta):
    @abstractmethod
    def draw(self):
        pass
class Point(Graphic):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __str__(self):
        return "點(%s, %s)" %(self.x, self.y)
    def draw(self):
        print(str(self))
class Line(Graphic):
    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2
    def __str__(self):
        return "線 [%s---%s]" %(self.p1, self.p2)
    def draw(self):
        print(str(self))
class Picture(Graphic):
    def __init__(self, iterable):
        self.children = []
        for i in iterable:
            self.add(i)
    def add(self, graphic):
        self.children.append(graphic)
    def draw(self):
        print('複合圖形')
        for i in self.children:
            i.draw()
        print('複合圖形')

p1 = Point(1,3)
p2 = Point(3,4)
l1 = Line(p1, p2)
l2 = Line(Point(2,3),Point(5,6))
pic1 = Picture([p1,p2,l1,l2])
pic1.draw()
#print
複合圖形
點(1, 3)
點(3, 4)
線 [點(1, 3)---點(3, 4)]
線 [點(2, 3)---點(5, 6)]
複合圖形

9. 外觀模式(不用看)

內容:為子系統中的一組介面提供一個一致的介面,外觀模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。

角色:外觀,子系統類

優點 : 減少系統相互依賴;提高靈活性;提高安全性

#子系統類
class Cpu:
    def run(self):
        print('cpu run')
    def stop(self):
        print('cpu stop')
class Memory:
    def run(self):
        print('memory run')
    def stop(self):
        print('memory run')
#外觀
class Computer:
    def __init__(self):
        self.cpu = Cpu()
        self.memory = Memory()
    def run(self):
        self.cpu.run()
        self.memory.run()
    def stop(self):
        self.cpu.stop()
        self.memory.stop()
#client
#先呼叫外觀介面:
computer1 = Computer()
computer1.run()
computer1.stop()

10. 代理模式

內容:為其他物件提供一種代理以控制對這個物件的訪問

角色:subject抽象實體(subject介面,目的:使實體和代理對外具有一致的方法), realsubject實體,proxy代理

應用場景:

遠端代理:為遠端的物件提供代理(隱藏物件位於遠端地址空間的事實)

虛代理: 根據需要建立很大的物件(一個讀檔案和寫檔案的類,建立時沒必要直接把檔案讀進來佔記憶體,只在讀檔案時讀進來),可以進行最佳化,如根據要求來建立物件

保護代理:控制對原始物件的訪問,用於物件有不同訪問許可權時。(允許在訪問一個物件時有一些附加的內務處理)

from abc import ABCMeta, abstractmethod
class Subject(metaclass = ABCMeta):
    @abstractmethod
    def get_content(self):#獲取一個很大的檔案
        pass
    @abstractmethod
    def set_content(self):#寫入檔案
        pass
#真實類
class RealSubject(Subject):
    def __init__(self, filename):
        self.filename = filename
        f = open(filename, 'r')
        print('讀取檔案內容')
        self.content = f.read()#在只呼叫set_content函式時也會佔用記憶體
        f.close()
    def get_content(self):
        return self.content
    def set_content(self, content):
        f = open(self.filename, 'w')
        f.write(content)
        f.close()
#虛代理
class VirtualProxy(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()
    def set_content(self,content):
        if not self.subj:
            self.subj = RealSubject(self.filename)
        self.subj.set_content(content)
#client
#1.
#subj = RealSubject('test.txt')
#此時直接會列印‘讀取檔案內容’

subj = VirtualProxy('test.txt')
#此時不會讀入檔案佔用記憶體,呼叫.get_content()之後才會
#保護代理
class ProtectedProxy(Subject):
    def __init__(self, filename):
        self.subj = RealSubject(filename)
    def get_content(self):
        return self.subj.get-content()
    def set_content(self, content):
        raise PermissionError('無寫入許可權')
subj = ProtectedProxy('test.txt')
subj.get_content()#正常使用
subj.set_content()#丟擲異常

行為型模式

11. 責任鏈模式

內容:使多個物件都有機會處理請求,從而避免請求的傳送者和接收者之間的耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,知道有一個物件處理為止。

角色: 抽象處理者handler, 具體處理者 concretehandler, client

適用場景: 有多個物件可以處理請求,哪個物件處理則由執行時決定;在不明確接收者的情況下,對多個物件中的一個提交請求就可以了。

優點: 降低耦合度

from abc import ABCMeta, abstractmethod
class Handler(metaclass = ABCMeta):
    @abstractmethod
    def handle_leave(self, day):
        pass
class GeneralManager(Handler):
    def handle_leave(self, day):
        if day <= 10:
            print('總經理准假%d天'% day)
        else:
            print('go fuck off')
class DepartmentManager(Handler):
    def __init__(self):
        self.next = GeneralManager()
    def handle_leave(self, day):
        if day <= 5:
            print('部門經理准假%d 天'% day)
        else:
            print('無法審批,up')
            self.next.handle_leave(day)
class ProjectDirector(Handler):
    def __init__(self):
        self.next = DepartmentManager()
    def handle_leave(self, day):
        if day <= 3:
            print('專案經理准假%d 天' % day)
        else:
            print('無法審批,up')
            self.next.handle_leave(day)
#client
athing = ProjectDirector()
athing.handle_leave(5)

12. 觀察者模式(釋出-訂閱模式)

內容:定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時, 所有依賴於它的物件都得到通知並被自動更新。

角色:抽象主題subject, 具體主題Concrete Subject(釋出者), 抽象觀察者(Observer),具體觀察者(Concrete Observer)訂閱者

適用場景:

  1. 當一個抽象模型有兩個方面(excel中的表格和圖(柱狀,扇形等)的關係),其中一個方面依賴於另一個方面。將這兩者封裝在獨立物件中以使它們可以各自獨立的改變和複用
  2. 當對一個物件的改變需要同時改變其他的物件,而不知道具體有多少個物件待改變時。
  3. 當一個物件必須通知其他物件,而又不能假定其他物件是誰。(單項緊耦合)

優點: 目標和觀察者之間的耦合最小; 支援廣播通訊

from abc improt 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 obs in self.observers:
            obs.update(self)

class StaffNotice(Notice):#具體釋出者
    def __init__(self, company_info= None):
        super().__init__()
        self.__company_info = company_info#處理為私有物件
    @property
    def company_info(self):#只讀屬性,防止company_info被篡改, 並用類.屬性 的方式呼叫,不需要加括號
        return self.__company_info#讀私有成員
    @company_info.setter
    def company_info(self, info):#寫私有成員, 如何去修改私有成員company_info,
        self.__company_info = info
        self.notify()#只要修改內容,就自動推送

#具體訂閱者
class Staff(Observer):
    def __init__(self):
        self.company_info = None
    def update(self, notice):
        self.company_info = notice.company_info#無法執行,因為notice的是私有的
#client
notice = StaffNotice('初始公司')
s1 = Staff()
s2 = Staff()
notice.attach(s1)
notice.attach(s2)
notice.company_info = 'over'
print(s1.company_info)

13. 策略模式

內容:定義一系列演算法,把他們一個個封裝起來,並且可以在使用的時候相互替換。可以使演算法獨立於適用的客戶而變化。

角色: 抽象策略 strategy, 具體策略 Concrete Strategy, 上下文 context

優點 : 定義了一系列可重用的演算法和行為;消除了條件語句;可以提供相同行為的不同實現

缺點: 客戶必須瞭解不同的策略

from abc import ABCMeta, abstractmethod
class Strategy(metaclass = ABCMeta):
    @abstractmethod
    def execute(self, data):
        pass
class FastStrategy(Strategy):
    def execute(self, data):
        print('fast method %s' % data)
class SlowStrategy(Strategy):
    def execute(self, data):
        print('slow method %s' % data)
class Context:#可以對client隱藏默寫實現,如隨機數的產生,拿到今天的日期,時間戳之類的
    def __init__(self, strategy, data):
        self.strategy = strategy
        self.data = data
    def set_strategy(self, strategy):#策略切換
        self.strategy = strategy
    def do_strategy(self):#策略執行
        self.strategy.execute(self.data)

#Client
data = '...'
s1 = FastStrategy()
s2 = SlowStrategy()

context = Context(s1, data)
context.do_strategy()
context.set_strategy(s2)
context.do_strategy()   

14. 模板方法模式

內容:定義一個操作中的演算法的骨架, 而將一些步驟延遲到子類中。 模板方法使得子類可以不改變一個演算法的結構時重定義該演算法的某些特定步驟。

角色: 抽象類 abstract class: 定義抽象的原子操作(鉤子操作),實現一個模板方法作為演算法的骨架。

具體類concrete class: 實現原子操作

適用場景:一次性實現一個演算法不變的部分;各個子類中的公共部分行為應該被提取出來並集中到一個公共父類中以避免程式碼重複; 控制子類擴充套件(只有在定義的模板中的才可以擴充套件)

from time import sleep
from abc import ABCMeta, abstractmethod
class Window(metaclass = ABCMeta):
    @abstractmethod
    def start(self):
        pass
    @abstractmethod
    def repaint(self):
        pass
    @abstractmethod
    def stop(self):#原子操作
        pass
    def run(self):#定義抽象的原子操作(模板方法)
        self.start()
        while True:
            try:
                self.repaint()
                sleep(1)
            except KeyboardInterrupt:
                    break
        self.stop()

#子類
class MyWindow(Window):
    def __init__(self, msg):#實現原子操作
        self.msg  = msg
    def start(self):
        print('start')
    def stop(self):
        print('stop')
    def repaint(self):
        print(self.msg)
MyWindow('it\'s working').run()

相關文章