国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Python學(xué)習(xí)之路25-使用一等函數(shù)實(shí)現(xiàn)設(shè)計(jì)模式

econi / 1566人閱讀

摘要:本篇主要講述中使用函數(shù)來實(shí)現(xiàn)策略模式和命令模式,最后總結(jié)出這種做法背后的思想。

《流暢的Python》筆記。

本篇主要講述Python中使用函數(shù)來實(shí)現(xiàn)策略模式和命令模式,最后總結(jié)出這種做法背后的思想。

1. 重構(gòu)策略模式

策略模式如果用面向?qū)ο蟮乃枷雭砗唵谓忉尩脑挘鋵?shí)就是“多態(tài)”。父類指向子類,根據(jù)子類對(duì)同一方法的不同重寫,得到不同結(jié)果。

1.1 經(jīng)典的策略模式

下圖是經(jīng)典的策略模式的UML類圖:

《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》一書這樣描述策略模式:

定義一系列算法,把它們封裝起來,且使它們能相互替換。本模式使得算法可獨(dú)立于使用它的客戶而變化。

下面以一個(gè)電商打折的例子來說明策略模式,打折方案如下:

有1000及以上積分的顧客,每個(gè)訂單享5%優(yōu)惠;

同一訂單中,每類商品的數(shù)量達(dá)到20個(gè)及以上時(shí),該類商品享10%優(yōu)惠;

訂單中的不同商品達(dá)10個(gè)及以上時(shí),整個(gè)訂單享7%優(yōu)惠。

為此我們需要?jiǎng)?chuàng)建5個(gè)類:

Order類:訂單類,相當(dāng)于上述UML圖中的Context上下文;

Promotion類:折扣類的父類,相當(dāng)于UML圖中的Strategy策略類,實(shí)現(xiàn)不同策略的共同接口;

具體策略類:FidelityPromoBulkPromoLargeOrderPromo依次對(duì)應(yīng)于上述三個(gè)打折方案。

以下是經(jīng)典的策略模式在Python中的實(shí)現(xiàn):

from abc import ABC, abstractmethod
from collections import namedtuple

Customer = namedtuple("Customer", "name fidelity")

class LineItem:  # 單個(gè)商品
    def __init__(self, product, quantity, price):
        self.produce = product
        self.quantity = quantity
        self.price = price

    def total(self):
        return self.price * self.quantity

class Order:  # 訂單類,上下文
    def __init__(self, customer, cart, promotion=None):
        self.customer = customer
        self.cart = list(cart)  # 形參cart中的元素是LineItem
        self.promotion = promotion

    def total(self):  # 未打折時(shí)的總價(jià)
        if not hasattr(self, "__total"):
            self.__total = sum(item.total() for item in self.cart)
        return self.__total

    def due(self):  # 折扣
        if self.promotion is None:
            discount = 0
        else:
            discount = self.promotion.discount(self)
        return self.total() - discount

class Promotion(ABC): # 策略:抽象基類
    @abstractmethod  # 抽象方法
    def discount(self, order):
        """返回折扣金額(正值)"""

class FidelityPromo(Promotion): # 第一個(gè)具體策略
    """積分1000及以上的顧客享5%"""
    def discount(self, order):
        return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0

class BulkItemPromo(Promotion): # 第二個(gè)具體策略
    """某類商品為20個(gè)及以上時(shí),該類商品享10%優(yōu)惠"""
    def discount(self, order):
        discount = 0
        for item in order.cart:
            if item.quantity >= 20:
                discount += item.total() * 0.1
        return discount

class LargeOrderPromo(Promotion): # 第三個(gè)具體策略
    """訂單中的不同商品達(dá)到10個(gè)及以上時(shí)享7%優(yōu)惠"""
    def discount(self, order):
        distinct_items = {item.product for item in order.cart}
        if len(distinct_items) >= 10:
            return order.total() * 0.07
        return 0

該類的使用示例如下:

>>> ann = Customer("Ann Smith", 1100)
>>> joe = Customer("John Joe", 0)
>>> cart = [LineItem("banana", 4, 0.5), LineItem("apple", 10, 1.5), 
...         LineItem("watermellon", 5, 5.0)]
>>> Order(ann, cart, FidelityPromo())  # 每次新建一個(gè)具體策略類
>>> Order(joe, cart, FidelityPromo())
1.2 Python函數(shù)重構(gòu)策略模式

現(xiàn)在用Python函數(shù)以更少的代碼來重構(gòu)上述的策略模式,去掉了抽象類Promotion,用函數(shù)代替具體的策略類:

# 不用導(dǎo)入abc模塊,去掉了Promotion抽象類;
# Customer, LineItem不變,Order類只修改due()函數(shù);三個(gè)具體策略類改為函數(shù)
-- snip -- 
class Order:
    -- snip --
    def due(self):  # 折扣
        if self.promotion is None:
            discount = 0
        else:
            discount = self.promotion(self)  # 修改為函數(shù)
        return self.total() - discount

def fidelity_promo(order):
    """積分1000及以上的顧客享5%"""
    return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0

def bulk_item_promo(order):
    """某類商品為20個(gè)及以上時(shí),該類商品享10%優(yōu)惠"""
    discount = 0
    for item in order.cart:
        if item.quantity >= 20:
            discount += item.total() * 0.1
    return discount

def large_order_promo(order):
    """訂單中的不同商品達(dá)到10個(gè)及以上時(shí)享7%優(yōu)惠"""
    distinct_items = {item.product for item in order.cart}
    if len(distinct_items) >= 10:
        return order.total() * 0.07
    return 0

該類現(xiàn)在的使用示例如下:

>>> Order(ann, cart, fidelity_promo)  # 沒有實(shí)例化新的促銷對(duì)象,函數(shù)拿來即用

脫離Python語言環(huán)境,從面相對(duì)象編程來說:

1.1中的使用示例可以看出,每次創(chuàng)建Order類時(shí),都創(chuàng)建了一個(gè)具體策略類,即使不同的訂單都用的同一個(gè)策略。按理說它們應(yīng)該共享同一個(gè)具體策略的實(shí)例,但實(shí)際并沒有。這就是策略模式的一個(gè)弊端。為了彌補(bǔ)這個(gè)弊端,如果具體的策略沒有維護(hù)內(nèi)部狀態(tài),你可以為每個(gè)具體策略創(chuàng)建一個(gè)實(shí)例,然后每次都傳入這個(gè)實(shí)例,這就是單例模式;但如果要維護(hù)內(nèi)狀態(tài),就需要將策略模式和享元模式結(jié)合使用,這又提高了代碼行數(shù)和維護(hù)成本。

在Python中則可以用函數(shù)來避開策略模式的這些弊端:

不用維護(hù)內(nèi)部狀態(tài)時(shí),我們可以直接用一般的函數(shù);如果需要維護(hù)內(nèi)部狀態(tài),可以編寫裝飾器(裝飾器也是函數(shù));

相對(duì)于編寫一個(gè)抽象類,再實(shí)現(xiàn)這個(gè)抽象類的接口來說,直接編寫函數(shù)更方便;

函數(shù)比用戶定義的類的實(shí)例更輕量;

無需去實(shí)現(xiàn)享元模式,每個(gè)函數(shù)在Python編譯模塊時(shí)只會(huì)創(chuàng)建一次,函數(shù)本身就是可共享的對(duì)象。

1.3 自動(dòng)選擇最佳策略

上述代碼中,我們需要自行傳入打折策略,但我們更希望的是程序自動(dòng)選擇最佳打折策略。以下是我們最能想到的一種方式:

# 在生成Order實(shí)例時(shí),傳入一個(gè)best_promo函數(shù),讓其自動(dòng)選擇最佳策略
promos = [fidelity_promo, bulk_item_promo, large_order_promo] # 三個(gè)打折函數(shù)的列表
def best_promo(order):
    """選擇可用的最佳策略"""
    return max(promo(order) for promo in promos)

但這樣做有一個(gè)弊端:如果要新增打折策略,不光要編寫打折函數(shù),還得把函數(shù)手動(dòng)加入到promos列表中。我們希望程序自動(dòng)識(shí)別這些具體策略。改變代碼如下:

promos = [globals()[name] for name in globals() 
          if name.endswith("_promo") and 
          name != "best_promo"] # 自動(dòng)獲取當(dāng)前模塊中的打折函數(shù)
def best_promo(order):
    -- snip --

在Python中,模塊也是一等對(duì)象globals()函數(shù)是標(biāo)準(zhǔn)庫提供的處理模塊的函數(shù),它返回一個(gè)字典,表示當(dāng)前全局符號(hào)表。這個(gè)符號(hào)表始終針對(duì)當(dāng)前模塊(對(duì)函數(shù)或方法來說,是指定義它們的模塊,而不是調(diào)用它們的模塊)

如果我們把各種具體策略多帶帶放到一個(gè)模塊中,比如放到promotions模塊中,上述代碼還可改為如下形式:

# 各具體策略多帶帶放到一個(gè)模塊中
import promotions, inspect
# inspect.getmembers函數(shù)用于獲取對(duì)象的屬性,第二個(gè)參數(shù)是可選的判斷條件
promos = [func for name, func in inspect.getmembers(promotions, inspect.isfunction)]
def best_promo(order):
    -- snip --

其實(shí),動(dòng)態(tài)收集具體策略函數(shù)更為顯式的一種方案是使用簡單的裝飾器,這將在下一篇中介紹。

2. 命令模式

命令模式的UML類圖如下:

命令模式的目的是解耦發(fā)起調(diào)用的對(duì)象(調(diào)用者,Caller)和提供實(shí)現(xiàn)的對(duì)象(接受者,Receiver)。實(shí)際做法就是在它們之間增加一個(gè)命令類(Command),它只有一個(gè)抽象接口execute(),具體命令類實(shí)現(xiàn)這個(gè)接口即可。這樣調(diào)用者就無需了解接受者的接口,不同的接受者還可以適應(yīng)不同的Command子類。

有人說“命令模式是回調(diào)機(jī)制的面向?qū)ο筇娲贰保珕栴}是,Python中我們不一定需要這個(gè)替代品。具體說來,我們可以不為調(diào)用者提供一個(gè)Command實(shí)例,而是給它一個(gè)函數(shù)。此時(shí),調(diào)用者不用調(diào)用command.execute(),而是直接command()

以下是一般的命令模式代碼:

from abc import ABC, abstractmethod

class Caller:
    def __init__(self, command=None):
        self.command = command

    def action(self):
        """把對(duì)接受者的調(diào)用交給中介Command"""
        self.command.execute()

class Receiver:
    def do_something(self):
        """具體的執(zhí)行命令"""
        print("I"m a receiver")

class Command(ABC):
    @abstractmethod
    def execute(self):
        """調(diào)用具體的接受者方法"""

class ConcreteCommand(Command):
    def __init__(self, receiver):
        self.receiver = receiver

    def execute(self):
        self.receiver.do_something()

if __name__ == "__main__":
    receiver = Receiver()
    command = ConcreteCommand(receiver)
    caller = Caller(command)
    caller.action()

# 結(jié)果:
I"m a receiver

直接將上述代碼改成函數(shù)的形式,其實(shí)并不容易改寫,因?yàn)榫唧w的命令類還保存了接收者。但是換個(gè)思路,將其改成可調(diào)用對(duì)象,那么代碼就可以變成如下形式:

class Caller:
    def __init__(self, command=None):
        self.command = command

    def action(self):
        # 之前是self.command.execute()
        self.command()

class Receiver:
    def do_something(self):
        """具體的執(zhí)行命令"""
        print("I"m a receiver")

class ConcreteCommand:
    def __init__(self, receiver):
        self.receiver = receiver

    def __call__(self):
        self.receiver.do_something()

if __name__ == "__main__":
    receiver = Receiver()
    command = ConcreteCommand(receiver)
    caller = Caller(command)
    caller.action()
3. 總結(jié)

看完這兩個(gè)例子,不知道大家發(fā)現(xiàn)了什么相似之處了沒有:

它們都把實(shí)現(xiàn)單方法接口的類的實(shí)例替換成了可調(diào)用對(duì)象。畢竟,每個(gè)Python可調(diào)用對(duì)象都實(shí)現(xiàn)了單方法接口,即__call__方法。

直白一點(diǎn)說就是,如果你定義了一個(gè)抽象類,這個(gè)類只有一個(gè)抽象方法a(),然后還要為這個(gè)抽象類派生出一大堆具體類來重寫這個(gè)方法a(),那么此時(shí)大可不必定義這個(gè)抽象類,直接將這些具體類改寫成可調(diào)用對(duì)象即可,在__call__方法中實(shí)現(xiàn)a()要實(shí)現(xiàn)的功能。

這相當(dāng)于用Python中可調(diào)用對(duì)象的基類充當(dāng)了我們定義的基類,我們便不用再定義基類;對(duì)抽象方法a()的重寫變成了對(duì)特殊方法__call__的重寫,畢竟我們只是想要這些方法有一個(gè)相同的名字,至于叫什么其實(shí)無所謂。


迎大家關(guān)注我的微信公眾號(hào)"代碼港" & 個(gè)人網(wǎng)站 www.vpointer.net ~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/44727.html

相關(guān)文章

  • Python學(xué)習(xí)之路24-一等函數(shù)

    摘要:函數(shù)內(nèi)省的內(nèi)容到此結(jié)束。函數(shù)式編程并不是一個(gè)函數(shù)式編程語言,但通過和等包的支持,也可以寫出函數(shù)式風(fēng)格的代碼。 《流暢的Python》筆記。本篇主要講述Python中函數(shù)的進(jìn)階內(nèi)容。包括函數(shù)和對(duì)象的關(guān)系,函數(shù)內(nèi)省,Python中的函數(shù)式編程。 1. 前言 本片首先介紹函數(shù)和對(duì)象的關(guān)系;隨后介紹函數(shù)和可調(diào)用對(duì)象的關(guān)系,以及函數(shù)內(nèi)省。函數(shù)內(nèi)省這部分會(huì)涉及很多與IDE和框架相關(guān)的東西,如果平時(shí)...

    wind3110991 評(píng)論0 收藏0
  • 如何合理的規(guī)劃前端之路

    摘要:什么是前端工程師總而言之就是運(yùn)用等技術(shù),在工作中配合設(shè)計(jì)師實(shí)現(xiàn)用戶界面,和后端工程師進(jìn)行數(shù)據(jù)對(duì)接,完成應(yīng)用開發(fā)的職位。 什么是前端工程師?總而言之,就是運(yùn)用 HTML、CSS、JavaScript 等 Web 技術(shù),在工作中配合UI設(shè)計(jì)師實(shí)現(xiàn)用戶界面,和后端工程師進(jìn)行數(shù)據(jù)對(duì)接,完成 Web 應(yīng)用開發(fā)的職位。Tips:個(gè)人博客排版、UI更佳;地址:https://haonancx.git...

    Pines_Cheng 評(píng)論0 收藏0
  • 如何合理的規(guī)劃前端之路

    摘要:什么是前端工程師總而言之就是運(yùn)用等技術(shù),在工作中配合設(shè)計(jì)師實(shí)現(xiàn)用戶界面,和后端工程師進(jìn)行數(shù)據(jù)對(duì)接,完成應(yīng)用開發(fā)的職位。 什么是前端工程師?總而言之,就是運(yùn)用 HTML、CSS、JavaScript 等 Web 技術(shù),在工作中配合UI設(shè)計(jì)師實(shí)現(xiàn)用戶界面,和后端工程師進(jìn)行數(shù)據(jù)對(duì)接,完成 Web 應(yīng)用開發(fā)的職位。Tips:個(gè)人博客排版、UI更佳;地址:https://haonancx.git...

    skinner 評(píng)論0 收藏0
  • 如何合理的規(guī)劃前端之路

    摘要:什么是前端工程師總而言之就是運(yùn)用等技術(shù),在工作中配合設(shè)計(jì)師實(shí)現(xiàn)用戶界面,和后端工程師進(jìn)行數(shù)據(jù)對(duì)接,完成應(yīng)用開發(fā)的職位。 什么是前端工程師?總而言之,就是運(yùn)用 HTML、CSS、JavaScript 等 Web 技術(shù),在工作中配合UI設(shè)計(jì)師實(shí)現(xiàn)用戶界面,和后端工程師進(jìn)行數(shù)據(jù)對(duì)接,完成 Web 應(yīng)用開發(fā)的職位。Tips:個(gè)人博客排版、UI更佳;地址:https://haonancx.git...

    lwx12525 評(píng)論0 收藏0
  • python學(xué)習(xí)筆記 函數(shù)

    摘要:一等函數(shù)在中,函數(shù)是一等對(duì)象。匿名函數(shù)關(guān)鍵字在表達(dá)式內(nèi)創(chuàng)建匿名函數(shù)然而,簡單的句法限制了函數(shù)的定義體只能使用純表達(dá)式,即函數(shù)的定義體中不能賦值,不能使用等語句。匿名函數(shù)適合用于作為函數(shù)的參數(shù) 一等函數(shù) 在python中,函數(shù)是一等對(duì)象。編程語言理論家把一等對(duì)象定義為滿足以下條件的程序?qū)嶓w: 在運(yùn)行時(shí)創(chuàng)建 能賦值給變量或數(shù)據(jù)結(jié)構(gòu)中的元素 能作為參數(shù)傳給函數(shù) 能作為函數(shù)的返回結(jié)果 在p...

    Scorpion 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

econi

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<