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, abstractmethodclass NewsPublisher:def __init__(self):self. __subscribers = []self. __latestNews = Nonedef 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 = newsdef getNews(self):return "Got News:", self. __latestNewsclass Subscriber(metaclass=ABCMeta):@abstractmethoddef update(self):passclass SMSSubscriber:def __init__(self, publisher):self.publisher = publisherself.publisher.attach(self)def update(self):print(type(self). __name__, self.publisher.getNews())class EmailSubscriber:def __init__(self, publisher):self.publisher = publisherself.publisher.attach(self)def update(self):print(type(self). __name__, self.publisher.getNews())class AnyOtherSubscriber:def __init__(self, publisher):self.publisher = publisherself.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 = rootdirself.src = srcdef 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 codewizard = Wizard('python3.5.gzip', '/usr/bin/')## Users chooses to install Python onlywizard.preferences({'python':True})wizard.preferences({'java':False})wizard.execute()
这里将通过一个(在互联网世界中经常讲到的)证券交易所的例子来演示命令模式的实现。
作为证券交易所的用户,你会创建买入或卖出股票的订单。通常情况下,你无法直接执行买入或卖出。实际上,代理或经纪人,在你和证券交易所之间扮演了中介的角色。代理负责将你的请求提交给证券交易所,完成工作。我们假设你想在星期一早上开市后卖出股票。但是在星期日晚上,虽然交易所尚未开市,你就可以向代理提出卖出股票的请求。然后,代理会将该请求放入排队,以便在星期一早晨当交易所开市的时候执行该请求,完成相应的交易。这实际上就是一个命令模式的经典情形。
首先介绍 Command 对象,即 Order,还开发了表示 ConcreteCommand 的两个主要的具体类:BuyStockOrder 和 SellStockOrder,StockTrade 类表示该示例中的 Receiver 对象,Agent 类表示调用者,代理是客户端和 StockExchange 之间的中介,并执行客户下达的订单。
from abc import ABCMeta, abstractmethodclass Order(metaclass=ABCMeta):@abstractmethoddef execute(self):passclass BuyStockOrder(Order):def __init__(self, stock):self.stock = stockdef execute(self):self.stock.buy()class SellStockOrder(Order):def __init__(self, stock):self.stock = stockdef 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__':#Clientstock = StockTrade()buyStock = BuyStockOrder(stock)sellStock = SellStockOrder(stock)#Invokeragent = 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, ABCMetaclass Trip(metaclass=ABCMeta):@abstractmethoddef setTransport(self):pass@abstractmethoddef day1(self):pass@abstractmethoddef day2(self):pass@abstractmethoddef day3(self):pass@abstractmethoddef returnHome(self):passdef 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()
模板方法模式提供以下优点:
- 正如我们在本章前面所看到的,没有代码重复;
- 由于模板方法模式使用继承而不是合成,因此能够对代码进行重用。所以,只有为数不多的几个方法需要重写;
- 灵活性允许子类决定如何实现算法中的步骤。
模板方法模式的缺点如下:
- 调试和理解模板方法模式中的流程序列有时会令人困惑。你最终实现的方法可能是一个不应该实现的方法,或根本没有实现抽象方法。文档和严格的错误处理必须由程序员完成;
- 模板框架的维护可能是一个问题,因为任何层次(低层或高层)的变更都可能对实现造成干扰。因此,使用模板方法模式可能会使维护变得异常痛苦。
相关文章:

Python 设计模式(第2版) -- 第三部分(行为型模式)
Python 设计模式(第2版) 再介绍下行为型设计模式。 行为型模式,顾名思义,它主要关注的是对象的责任。它们用来处理对象之间的交互,以实现更大的功能。行为型模式建议:对象之间应该能够彼此交互,同时还应该是松散耦合…...

EXCEL数据导入HIVE
引言 本文将论述如何将Windows本地的excel表数据,导入到虚拟机Linux系统中的Hadoop生态中的Hive数据仓库中。 实验准备 DBeaver Hive3.1(Hadoop3.1) excel数据表 实验步骤 一、首先打开虚拟机,启动Hadoop,启动h…...

C语言常用标准头文件
头文件的基础概念 在C的系列语言程序中,头文件(通常扩展名为.h)被大量使用,它通常包含函数、变量、结构体等的声明和定义,以及一些宏定义和类型定义。头文件的主要作用是为了方便管理和重用代码,它可以被多…...

vuejs3用gsap实现动画
效果 gsap官网地址: https://gsap.com/ 安装gsap npm i gsap 创建Gsap.vue文件 <script setup> import {reactive, watch} from "vue"; import gsap from "gsap"; const props defineProps({value:{type:Number,default:0} }) cons…...

企业级-PDF文件下载
作者:fyupeng 技术专栏:☞ https://github.com/fyupeng 项目地址:☞ https://github.com/fyupeng/rpc-netty-framework 留给读者 一、介绍 文件下载在浏览器可以根据响应头设置纯下载和直接打开两种方式。 二、代码 RequestMapping("/…...

00 - React 基础
1. React 基础 安装react指令 可参考: 官网官网使用教程 如: npx create-react-app 项目名 如:npx create-react-app react-redux-proJSX JSX 是一种 JavaScript 的语法扩展,类似于 XML 或 HTML,允许我们在 Java…...

基于WPF技术的换热站智能监控系统17--项目总结
1、项目颜值,你打几分? 基于WPF技术的换热站智能监控系统01--项目创建-CSDN博客 基于WPF技术的换热站智能监控系统02--标题栏实现-CSDN博客 基于WPF技术的换热站智能监控系统03--实现左侧加载动画_wpf控制系统-CSDN博客 基于WPF技术的换热站智能监…...

CI /CD学习
CI/CD概述 CI/CD 是持续集成和持续交付/部署的缩写,旨在简化并加快软件开发生命周期。 持续集成(CI)是指自动且频繁地将代码更改集成到共享源代码存储库中的做法。持续交付和/或持续部署(CD)是一个由两部分组成的过程…...

基于matlab的高斯滤波与图像去噪
1 高斯滤波原理 1.1 原理 高斯滤波是一种线性平滑滤波技术,主要用于消除图像中的高斯噪声。它的工作原理可以理解为对整幅图像进行加权平均的过程,即每个像素点的值都由其本身和邻域内的其他像素值经过加权平均后得到。 高斯滤波实质上是一种信号的滤…...

解决 uniapp h5 页面在私有企微iOS平台 间歇性调用uni api不成功问题(uni.previewImage为例)。
demo <template><view class"content"><image class"logo" src"/static/logo.png"></image><button click"previewImage">预览图片</button></view> </template><script> //打…...

Jenkins nginx自动化构建前端vue项目
在现代的Web开发中,Vue.js已经成为一种非常流行的JavaScript框架。为了更高效地管理和部署Vue.js项目,使用自动化构建工具是至关重要的。Jenkins作为一款强大的持续集成和持续部署(CI/CD)工具,为我们提供了一种便捷的方…...

Devicetree - 删除某个节点或属性
在设备树(devicetree)中,删除语法用于从现有设备树中删除属性或节点。这通常在设备树覆盖文件(DTS)或片段文件中完成。该语法使用 /delete-property/ 和 /delete-node/ 指令。 以下是如何使用这些指令的示例࿱…...

Xcode will continue when the operation completes
Xcode 15后,连接手机经常出现 Preparing iPhone. Xcode will continue when the operation completes.记录一下解决办法。其他提示,Xcode连接不上手机也可以尝试通过这个方法解决。 注意:最好关闭科学上网再操作。 从Xcode取消iPhone的配对(右键单击 -Unpair Devi…...

Python爬虫-贝壳新房
前言 本文是该专栏的第32篇,后面会持续分享python爬虫干货知识,记得关注。 本文以某房网为例,如下图所示,采集对应城市的新房房源数据。具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。接下来,跟着笔者直接往下看正文详细内容。(附带完整代码) 正文 地…...

Canvas绘制图片和区域
如何使用Canvas在图片上绘制区域? 一. 首先,我们需要初始化三个canvas画布(初始化Canvas) initCanvas() {// 初始化canvas画布let canvasWrap document.getElementsByClassName("canvas-wrap");this.wrapWidth canva…...

Day10—Spark SQL基础
Spark SQL介绍 Spark SQL是一个用于结构化数据处理的Spark组件。所谓结构化数据,是指具有Schema信息的数据,例如JSON、Parquet、Avro、CSV格式的数据。与基础的Spark RDD API不同,Spark SQL提供了对结构化数据的查询和计算接口。 Spark …...

开源技术:在线教育系统源码及教育培训APP开发指南
本篇文章,小编将探讨如何利用开源技术开发在线教育系统及教育培训APP,旨在为有志于此的开发者提供全面的指导和实践建议。 一、在线教育系统的基本构架 1.1架构设计 包括前端、后端和数据库三个主要部分。 1.2前端技术 在前端开发中,HTML…...

[C++][设计模式][观察者模式]详细讲解
目录 1.动机2.模式定义3.要点总结4.代码感受1.代码一1.FileSplitter.cpp2.MainForm.cpp 2.代码二1.FileSplitter.cpp2.MainForm.cpp 1.动机 在软件构建过程中,需要为某些对象建立一种“通知依赖关系” 一个对象(目标对象)的状态发生改变,所有的依赖对象…...

Adobe Acrobat 编辑器软件下载安装,Acrobat 轻松编辑和管理各种PDF文件
Adobe Acrobat,它凭借卓越的功能和丰富的工具,为用户提供了一个全面的解决方案,用于查看、创建、编辑和管理各种PDF文件。 作为一款专业的PDF阅读器,Adobe Acrobat能够轻松打开并展示各种格式的PDF文档,无论是文字、图…...

eVTOL飞机:技术挑战、应用机遇和运动的作用
最近,航空业的嗡嗡声围绕着电动空中出租车、空中拼车、无人驾驶航空货物运送等。这些概念都依赖于一类称为eVTOL的飞机,eVTOL是电动垂直起降的缩写。 与直升机类似,但没有噪音和排放,eVTOL可以在不需要简易机场的情况下飞行、悬停…...

【python】flask中如何向https服务器传输信息
【背景】 用flask做一个支持流媒体传输的网页,如何将信息post给流媒体服务器呢? 【方法】 简单例子,视图函数这么写: url = "https://yourip/mytext" headers = {Content-Type:application/octet-stream} @app.route(/,methods=["POST"...

计算机网络 —— 应用层(FTP)
计算机网络 —— 应用层(FTP) FTP核心特性:运作流程: FTP工作原理主动模式被动模式 我门今天来看应用层的FTP(文件传输协议) FTP FTP(File Transfer Protocol,文件传输协议&#x…...

zookeeper + kafka消息队列
zookeeper kafka 消息队列 一、消息队列简介 1、什么是消息队列 消息队列(Message Queue)是一种用于跨进程或分布式系统中传递消息的通信机制。消息队列在异步通信、系统解耦、负载均衡和容错方面具有重要作用。 (1)特性 异步…...

Python高级编程:深度学习基础
Python高级编程:深度学习基础 在前几篇文章中,我们探讨了Python的基础语法、面向对象编程、标准库、第三方库、并发编程、异步编程、网络编程与网络爬虫、数据库操作与ORM、数据分析与数据可视化以及机器学习基础。在这篇文章中,我们将深入探讨Python在深度学习领域的应用。…...

如何从magento1迁移到magento2
m2相较m1 变化可以说非常大,相当于从头到位都改写一遍,更现代化,更优雅。除了数据库表变化不是很大。 主要迁移的内容有: 1,主题 2,插件(自己开发的或者第三方插件) 3,数据库 主题 不能迁移到m…...

【Nginx】Nginx安装及简单使用
https://www.bilibili.com/video/BV1F5411J7vK https://www.kuangstudy.com/bbs/1353634800149213186 https://stonecoding.net/system/nginx/nginx.html https://blog.csdn.net/qq_40492693/article/details/124453090 Nginx 是一个高性能的 HTTP 和反向代理 Web 服务器。其特…...

【Linux系列】find命令使用与用法详解
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

Apple - DNS Service Discovery Programming Guide
本文翻译整理自:DNS Service Discovery Programming Guide(更新日期:2013-08-08 https://developer.apple.com/library/archive/documentation/Networking/Conceptual/dns_discovery_api/Introduction.html#//apple_ref/doc/uid/TP30000964 文…...

如何高效地为pip换源:详细操作指南
在Python开发中,pip是我们不可或缺的包管理工具。然而,默认的官方源下载速度较慢,尤其是在国内使用时可能会遇到网络问题。为了提高下载速度,我们可以通过更换国内的镜像源来解决这一问题。本文将详细介绍如何高效地为pip换源&…...

免费ddns工具,快解析DNS解析使用教程
DDNS(Dynamic Domain Name Server),中文叫动态域名解析,主要用于没有固定公网ip的网络环境下,使用一个固定的域名,解析动态变化的ip地址,达到远程访问的目的。 众所周知,目前公网ip资源非常紧缺…...