- 译者序
- 前言
- 第1章 问答环节
- 第2章 Python 如何运行程序
- 第3章 如何运行程序
- 第4章 介绍 Python 对象类型
- 第5章 数字
- 第6章 动态类型简介
- 第7章 字符串
- 第8章 列表与字典
- 第9章 元组、文件及其他
- 第10章 Python 语句简介
- 第11章 赋值、表达式和打印
- 第12章 if 测试和语法规则
- 第13章 while 和 for 循环
- 第14章 迭代器和解析,第一部分
- 第15章 文档
- 第16章 函数基础
- 第17章 作用域
- 第18章 参数
- 第19章 函数的高级话题
- 第20章 迭代和解析,第二部分
- 第21章 模块:宏伟蓝图
- 第22章 模块代码编写基础
- 第23章 模块包
- 第24章 高级模块话题
- 第25章 OOP:宏伟蓝图
- 第27章 更多实例
- 第28章 类代码编写细节
- 第29章 运算符重载
- 第30章 类的设计
- 第31章 类的高级主题
- 第32章 异常基础
- 第34章 异常对象
- 第35章 异常的设计
- 第36章 Unicode 和字节字符串
- 字符串基础知识
- Python 的字符串类型
- 文本和二进制文件
- Python 3.0 中的字符串应用
- 转换
- 编码 Unicode 字符串
- 编码非ASCII文本
- 编码和解码非ASCII文本
- 其他 Unicode 编码技术
- 转换编码
- 在 Python 2.6 中编码 Unicode 字符串
- 源文件字符集编码声明
- 使用 Python 3.0 Bytes 对象
- 序列操作
- 创建 bytes 对象的其他方式
- 混合字符串类型
- 使用 Python 3.0(和 Python 2.6)bytearray 对象
- 使用文本文件和二进制文件
- Python 3.0 中的文本和二进制模式
- 类型和内容错误匹配
- 使用 Unicode 文件
- 在 Python 3.0 中处理 BOM
- Python 2.6 中的 Unicode 文件
- Python 3.0 中其他字符串工具的变化
- Struct二进制数据模块
- pickle对象序列化模块
- XML解析工具
- 本章小结
- 本章习题
- 习题解答
- 第37章 管理属性
- 第38章 装饰器
- 第39章 元类
- 附录A 安装和配置
- 附录B 各部分练习题的解答
- 作者介绍
- 封面介绍
OOP和组合:有一个关系
组合也反映了各组成部分之间的关系,通常称为“有一个”(has-a)关系。有些OOP设计书籍把组合称为聚合(aggregation),或者使用聚合描述容器和所含物之间较弱的依赖关系来区分这两个术语。本书中,“组合”就是指内嵌对象集合体。组合类一般都提供自己的接口,并通过内嵌的对象来实现接口。
既然我们已经有了员工,就把他们放到比萨店,开始忙吧。我们的比萨店是一个组合对象,有个烤炉,也有服务生和主厨这些员工。当顾客来店下单时,店里的组件就会开始行动:服务生接下订单,主厨制作比萨,等等。下面的例子(文件pizzashop.py)模拟了这个场景中所有的对象和关系。
PizzaShop类是容器和控制器,其构造函数会创建上一节所编写的员工类实例并将其嵌入。此外,Oven类也是在这里定义的。当此模块的自我测试程序代码调用PizzaShoporder方法时,内嵌对象会按照顺序进行工作。注意:每份订单创建了新的Customer对象,而且把内嵌的Server对象传给Customer方法。顾客是流动的,但是,服务生是比萨店的组合成分。另外,员工也涉及了继承关系,组合和继承是互补的工具。当执行这个模块时,我们的比萨店处理了两份订单:一份来自Homer,另一份来自Shaggy。
同样地,这只是一个用来模拟的例子,但是,对象和交互足以代表组合的工作。其简明的原则就是,类可以表示任何用一句话表达的对象和关系。只要用类取代名词,用方法取代动词,就有第一手的设计方案了。
重访流处理器
就更为现实的组合范例而言,可以回忆第22章介绍OOP时,写的通用数据流处理器函数的部分代码。
在这里,不是使用简单函数,而是编写类,使用组合机制工作,来提供更强大的结构并支持继承。下面的文件streams.py示范了一种编写类的方式。
这个类定义了一个转换器方法,它期待子类来填充。这是我们在第28章中介绍的抽象超类模式的一个例子(第7部分更多地介绍断言)。以这种方式编写代码,读取器和写入器对象会内嵌在类实例当中(组合),我们是在子类内提供转换器的逻辑,而不是传入一个转换器函数(继承)。文件converters.py显示了这种方法。
在这里,Uppercase类继承了类处理的循环逻辑(以及其超类内所写的其他任何事情)。它只需定义其所特有的事件:数据转换逻辑。当这个文件执行时,会创建并执行实例,而该实例再从文件spam.txt中读取,把该文件对应的大写版本输出到stdout流。
要处理不同种类的流,可以把不同种类的对象传入类的构造调用中。在这里,我们使用了输出文件,而不是流。
但是,就像之前所建议的,我们可以传入包装在类中的任何对象(该对象定义了所需要的输入和输出方法接口)。以下是简单例子,传入写入器类(把文字嵌HTML标签中)。
即使原始的Processor超类内的核心处理逻辑什么也不知道,如果跟随这个例子的控制流程,就会发现得到了大写转换(通过继承)以及HTML格式(通过组合)。处理代码只在意写入器的write方法,而且又定义一个名为convert的方法,并不在意这些调用在做什么。这种逻辑的多态和封装远超过类的威力。
Processor超类只提供文件扫描循环。在更实际的工作中,我们可能会对它进行扩充,使其子类能支持其他程序设计工具,而且在这个流程中,将其变为成熟的软件框架。在超类中编写一次这种工具,就可以在所有程序中重复使用。即使是这个简单的例子,因为类打包了不少东西并能继承,我们所需做的代码编写就是HTML格式这一步。其余都是免费的。
看看组合的另一个例子,可以参考第31章结尾的练习题9以及其附录B的解法。这个例子类似于比萨店的例子。本书中把焦点放在继承上,因为这是Python语言本身提供的OOP的主要工具。但是,在实际中,组合和继承用的一样多,都是组织类结构的方式,尤其是在较大型系统中。正如我们所见的,继承和组合通常是互补的(偶尔是互换的)技术。不过,因为组合是设计的话题,不在Python语言和本书的范围内,这个话题的其他内容请参考其他资源。
为什么要在意:类和持续性
本书这一部分内容中几次提到了pickle机制,因为它和类实例合起来使用效果很好。实际上,这些工具往往足够吸引人,可以促进类的通用用法——通过pickle或shelve一个类实例,我们得到了包含数据和逻辑的组合的数据存储。
例如,除了可以模拟真实世界的交互外,在这里开发的比萨店类,也可以作为持续保存餐馆数据库的基础。类实例可以利用Python的pickle或shelve模块,通过单个步骤储存到磁盘上。在第27章中的OOP教程中,我们使用shelve来存储类的实例,而对象的pickle接口很容易使用:
pickle机制把内存中的对象转换成序列化的字节流,可以保存在文件中,也可通过网络发送出去。解除pickle状态则是从字节流转换回同一个内存中的对象,Shelve也类似。但是它会自动把对象pickle生成按键读取的数据库,而此数据库会导出类似于字典的接口。
上例中,使用类来模拟员工意味着我们只需做一点工作,就可以得到员工和商店的简单数据库:把这种实例对象pickle至文件,使其在Python程序执行时都能够永续保存。
这一次性地把整个复合的shop对象保存到一个文件中。为了在另一个会话或程序中再次找回它,只需要一个步骤就够了。实际上,以这种方式存储的对象保存了状态和行为:
参考标准链接库手册和之后的范例,进一步了解关于pickle的内容。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论