Python 设计模式(第2版) -- 第三部分(行为型模式)

Python 设计模式(第2版)

再介绍下行为型设计模式。

行为型模式,顾名思义,它主要关注的是对象的责任。它们用来处理对象之间的交互,以实现更大的功能。行为型模式建议:对象之间应该能够彼此交互,同时还应该是松散耦合的。

观察者设计模式是最简单的行为型模式之一,所以,首先来看看观察者设计模式。

观察者模式 – 了解对象的情况

观察者模式的主要目标如下:

  • 它定义了对象之间的一对多的依赖关系,从而使得一个对象中的任何更改都将自动通知给其他依赖对象;
  • 它封装了主题的核心组件。

观察者模式可用于以下多种场景:

  • 在分布式系统中实现事件服务;
  • 用作新闻机构的框架;
  • 股票市场也是观察者模式的一个大型场景。

下面是观察者设计模式的 Python 实现:

class Subject:

    def __init__(self):
        self. __observers = []

    def register(self, observer):
        self. __observers.append(observer)

    def notifyAll(self, *args, **kwargs):
        for observer in self. __observers:
            observer.notify(self, *args, **kwargs)


class Observer1:

    def __init__(self, subject):
        subject.register(self)

    def notify(self, subject, *args):
        print(type(self). __name__, ':: Got', args, 'From', subject)


class Observer2:

    def __init__(self, subject):
        subject.register(self)

    def notify(self, subject, *args):
        print(type(self). __name__, ':: Got', args, 'From', subject)

subject = Subject()
observer1 = Observer1(subject)
observer2 = Observer2(subject)
subject.notifyAll('notification')

观察者模式有 3 个主要角色:主题(Subject),观察者(Observer),具体观察者(ConcreteObserver)。

这里将以新闻机构为例来展示观察者模式的现实世界场景。新闻机构通常从不同地点收集新闻,并将其发布给订阅者。

主题的行为由 NewsPublisher 类表示, NewsPublisher 提供了一个供订户使用的接口, attach() 方法供观察者(Observer)来注册 NewsPublisherObserver,detach() 方法用于注销,subscriber() 方法返回已经使用 Subject 注册的所有订户的列表,notifySubscriber() 方法可以用来遍历已向 NewsPublisher 注册的所有订户,发布者可以使用 addNews() 方法创建新消息,getNews() 用于返回最新消息,并通知观察者。

现在来讨论观察者(Observer)接口,在这个例子中,Subscriber 表示 Observer,它是一个抽象的基类,代表其他 ConcreteObserver,Subscriber 有一个 update() 方法,但是它需要由 ConcreteObservers 实现,update() 方法是由 ConcreteObserver 实现的,这样只要有新闻发布的时候,它们都能得到 Subject (NewsPublishers)的相应通知。

本例有两个主要观察者,分别是实现订户接口的 EmailSubscriber 和 SMSSubscriber,还建立了另一个观察者 AnyOtherObserver,它是用来演示 Observers 与 Subject 的松散耦合关系的, 每个具体观察者的 __init__() 方法都是使用 attach() 方法向 NewsPublisher 进行注册的,具体观察者的 update() 方法由 NewsPublisher 在内部用来通知添加了新的新闻。

from abc import ABCMeta, abstractmethod

class NewsPublisher:

    def __init__(self):
        self. __subscribers = []
        self. __latestNews = None

    def attach(self, subscriber):
        self. __subscribers.append(subscriber)

    def detach(self):
        return self. __subscribers.pop()

    def subscribers(self):
        return [type(x). __name__ for x in self. __subscribers]

    def notifySubscribers(self):
        for sub in self. __subscribers:
            sub.update()

    def addNews(self, news):
        self. __latestNews = news

    def getNews(self):
        return "Got News:", self. __latestNews

class Subscriber(metaclass=ABCMeta):

    @abstractmethod
    def update(self):
        pass

class SMSSubscriber:

    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attach(self)

    def update(self):
        print(type(self). __name__, self.publisher.getNews())

class EmailSubscriber:

    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attach(self)

    def update(self):
        print(type(self). __name__, self.publisher.getNews())

class AnyOtherSubscriber:

    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attach(self)

    def update(self):
        print(type(self). __name__, self.publisher.getNews())

if __name__ == '__main__':
    news_publisher = NewsPublisher()
    for Subscribers in [SMSSubscriber, EmailSubscriber, AnyOtherSubscriber]:
        Subscribers(news_publisher)
    print("\nSubscribers:", news_publisher.subscribers())

    news_publisher.addNews('Hello World! ')
    news_publisher.notifySubscribers()

    print("\nDetached:", type(news_publisher.detach()). __name__)
    print("\nSubscribers:", news_publisher.subscribers())

    news_publisher.addNews('My second news! ')
    news_publisher.notifySubscribers()

客户端为 NewsPublisher 创建一个对象,以供具体观察者用于各种操作,使用发布者的对象初始化 SMSSubscriber、EmailSubscriber 和 AnyOtherSubscriber 类,在 Python 中,当我们创建对象时,__init__() 方法就会被调用。在 ConcreteObserver 类中,__init__() 方法在内部使用 NewsPublisher 的 attach() 方法进行注册以获取新闻更新,然后,我们打印出已经通过主题注册的所有订户(具体观察者)的列表,接着,使用 newsPublisher(news_publisher)的对象通过 addNews() 方法创建新消息,NewsPublisher 的 notifySubscribers() 方法用于通知所有订户出现了新消息。notifySubscribers() 方法在内部调用由具体观察者实现的 update() 方法,以便它们可以获得最新的消息。

观察者模式具有以下优点:

  • 它使得彼此交互的对象之间保持松耦合;
  • 它使得我们可以在无需对主题或观察者进行任何修改的情况下高效地发送数据到其他对象;
  • 可以随时添加/删除观察者。

以下是观察者模式的缺点:

  • 观察者接口必须由具体观察者实现,而这涉及继承。无法进行组合,因为观察者接口可以实例化;
  • 如果实现不当的话,观察者可能会增加复杂性,并导致性能降低;
  • 在软件应用程序中,通知有时可能是不可靠的,并导致竞争条件或不一致性。

命令模式 – 封装调用

命令模式通常使用以下术语:Command、Receiver、Invoker 和 Client:

  • Command 对象了解 Receiver 对象的情况,并能调用 Receiver 对象的方法;
  • 调用者方法的参数值存储在 Command 对象中;
  • 调用者知道如何执行命令;
  • 客户端用来创建 Command 对象并设置其接收者。

命令模式的主要意图如下:

  • 将请求封装为对象;
  • 可用不同的请求对客户进行参数化;
  • 允许将请求保存在队列中(我们将在本章后面进行讨论);
  • 提供面向对象的回调。

命令模式可用于以下各种情景:

  • 根据需要执行的操作对对象进行参数化;
  • 将操作添加到队列并在不同地点执行请求;
  • 创建一个结构来根据较小操作完成高级操作。

以下的 Python 代码实现了命令设计模式。假设我们想要开发一个安装向导,或者更常见的安装程序。通常,安装意味着需要根据用户做出的选择来复制或移动文件系统中的文件。在下面的示例中,我们首先在客户端代码中创建 Wizard 对象,然后使用 preferences() 方法存储用户在向导的各个屏幕期间做出的选择。在向导中单击 Finish 按钮时,就会调用 execute() 方法。之后,execute() 方法将会根据首选项来开始安装。

class Wizard():

    def __init__(self, src, rootdir):
        self.choices = []
        self.rootdir = rootdir
        self.src = src

    def preferences(self, command):
        self.choices.append(command)

    def execute(self):
        for choice in self.choices:
            if list(choice.values())[0]:
                print("Copying binaries --", self.src, " to ", self. rootdir)
            else:
                print("No Operation")

if __name__ == '__main__':
    ## Client code
    wizard = Wizard('python3.5.gzip', '/usr/bin/')
    ## Users chooses to install Python only
    wizard.preferences({'python':True})
    wizard.preferences({'java':False})
    wizard.execute()

这里将通过一个(在互联网世界中经常讲到的)证券交易所的例子来演示命令模式的实现。

作为证券交易所的用户,你会创建买入或卖出股票的订单。通常情况下,你无法直接执行买入或卖出。实际上,代理或经纪人,在你和证券交易所之间扮演了中介的角色。代理负责将你的请求提交给证券交易所,完成工作。我们假设你想在星期一早上开市后卖出股票。但是在星期日晚上,虽然交易所尚未开市,你就可以向代理提出卖出股票的请求。然后,代理会将该请求放入排队,以便在星期一早晨当交易所开市的时候执行该请求,完成相应的交易。这实际上就是一个命令模式的经典情形。

首先介绍 Command 对象,即 Order,还开发了表示 ConcreteCommand 的两个主要的具体类:BuyStockOrder 和 SellStockOrder,StockTrade 类表示该示例中的 Receiver 对象,Agent 类表示调用者,代理是客户端和 StockExchange 之间的中介,并执行客户下达的订单。

from abc import ABCMeta, abstractmethod

class Order(metaclass=ABCMeta):

    @abstractmethod
    def execute(self):
        pass

class BuyStockOrder(Order):

    def __init__(self, stock):
        self.stock = stock

    def execute(self):
        self.stock.buy()


class SellStockOrder(Order):

    def __init__(self, stock):
        self.stock = stock

    def execute(self):
        self.stock.sell()

class StockTrade:

    def buy(self):
        print("You will buy stocks")

    def sell(self):
        print("You will sell stocks")

class Agent:

    def __init__(self):
        self. __orderQueue = []

    def placeOrder(self, order):
        self. __orderQueue.append(order)
        order.execute()

if __name__ == '__main__':
    #Client
    stock = StockTrade()
    buyStock = BuyStockOrder(stock)
    sellStock = SellStockOrder(stock)

    #Invoker
    agent = Agent()
    agent.placeOrder(buyStock)
    agent.placeOrder(sellStock)

客户首先设置其接收者,StockTrade 类, 它使用 BuyStockOrder 和 SellStockOrder(ConcreteCommand)创建订单来买卖股票,执行 StockTrade 的相关操作,调用者对象是通过实例化 Agent 类创建的,Agent 的 placeOrder() 方法用于获取客户端所下的订单。

在软件中应用命令模式的方式有很多种。我们将讨论与云应用密切相关的两个实现。

  • 重做或回滚操作。
  • 异步任务执行。

命令模式具有以下优点:

  • 将调用操作的类与知道如何执行该操作的对象解耦;
  • 提供队列系统后,可以创建一系列命令;
  • 添加新命令更加容易,并且无需更改现有代码;
  • 还可以使用命令模式来定义回滚系统,例如,在向导示例中,我们可以编写一个回滚方法。

下面是命令模式的缺点:

  • 为了实现目标,需要大量的类和对象进行协作。应用程序开发人员为了正确开发这些类,需要倍加小心;
  • 每个单独的命令都是一个 ConcreteCommand 类,从而增加了需要实现和维护的类的数量。

模板方法模板 – 封装算法

模板方法模式通过一种称为模板方法的方式来定义程序框架或算法。

模板方法模式适用于以下场景:

  • 当多个算法或类实现类似或相同逻辑的时候;
  • 在子类中实现算法有助于减少重复代码的时候;
  • 可以让子类利用覆盖实现行为来定义多个算法的时候。

模板方法模式使用以下术语 —— AbstractClass、ConcreteClass、Template Method 和 Client。

  • AbstractClass:声明一个定义算法步骤的接口。
  • ConcreteClass:定义子类特定的步骤。
  • template_method():通过调用步骤方法来定义算法。

想象一个旅行社的例子,例如 Dev Travels。他们定义了各种旅游路线,并提供度假套装行程。一个行程套餐本质上是你作为客户允诺的一次旅行。旅行还涉及一些详细信息,如游览的地点、交通方式和与旅行有关的其他因素。

抽象对象由 Trip 类表示。它是一个接口(Python 的抽象基类),定义了不同日子使用的交通方式和参观的地点等细节,setTransport 是一个抽象方法,它由 ConcreteClass 实现,作用是设置交通方式,day1()、day2()、day3() 抽象方法定义了特定日期所参观的地点,itinerary() 模板方法创建完整的行程(即算法,在本例中为旅行)。

在本例中,我们主要有两个实现 Trip 接口的具体类:VeniceTrip 和 MaldivesTrip,这两个具体类代表游客根据他们的选择和兴趣所进行的两次不同的旅行,VeniceTrip 和 MaldivesTrip 都实现了 setTransport()、day1()、day2()、day3() 和 returnHome()。

TravelAgency 类代表该示例中的 Client 对象,它定义了 arrange_trip() 方法,让客户选择历史旅行或海滩旅行,这个对象然后调用 itinerary() 模板方法,并根据客户的选择为游客安排相应的旅行。

from abc import abstractmethod, ABCMeta

class Trip(metaclass=ABCMeta):
    
    @abstractmethod
    def setTransport(self):
        pass

    @abstractmethod
    def day1(self):
        pass

    @abstractmethod
    def day2(self):
        pass

    @abstractmethod
    def day3(self):
        pass

    @abstractmethod
    def returnHome(self):
        pass

    def itinerary(self):
        self.setTransport()
        self.day1()
        self.day2()
        self.day3()
        self.returnHome()

class VeniceTrip(Trip):

    def setTransport(self):
        print("Take a boat and find your way in the Grand Canal")

    def day1(self):
        print("Visit St Mark's Basilica in St Mark's Square")

    def day2(self):
        print("Appreciate Doge's Palace")

    def day3(self):
        print("Enjoy the food near the Rialto Bridge")

    def returnHome(self):
        print("Get souvenirs for friends and get back")


class MaldivesTrip(Trip):

    def setTransport(self):
        print("On foot, on any island, Wow! ")

    def day1(self):
        print("Enjoy the marine life of Banana Reef")

    def day2(self):
        print("Go for the water sports and snorkelling")

    def day3(self):
        print("Relax on the beach and enjoy the sun")

    def returnHome(self):
        print("Dont feel like leaving the beach..")

class TravelAgency:

    def arrange_trip(self):
        choice = input("What kind of place you'd like to go historical or to a beach? ")
        if choice == 'historical':
            self.trip = VeniceTrip()
            self.trip.itinerary()
        if choice == 'beach':
            self.trip = MaldivesTrip()
            self.trip.itinerary()

TravelAgency().arrange_trip()

模板方法模式提供以下优点:

  • 正如我们在本章前面所看到的,没有代码重复;
  • 由于模板方法模式使用继承而不是合成,因此能够对代码进行重用。所以,只有为数不多的几个方法需要重写;
  • 灵活性允许子类决定如何实现算法中的步骤。

模板方法模式的缺点如下:

  • 调试和理解模板方法模式中的流程序列有时会令人困惑。你最终实现的方法可能是一个不应该实现的方法,或根本没有实现抽象方法。文档和严格的错误处理必须由程序员完成;
  • 模板框架的维护可能是一个问题,因为任何层次(低层或高层)的变更都可能对实现造成干扰。因此,使用模板方法模式可能会使维护变得异常痛苦。
  • 20
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值