8.10 总结
我们介绍了如何使用装饰器修改函数和类的定义。也介绍了如何将一个庞大的类分解成互相关联的模块的mixin。
这所有的技术都是为了分离业务相关的功能和通用的功能,例如安全、审计或者日志。我们会区分继承自类的功能和不属于继承的额外关注点的方面。继承的功能是显式设计的一部分。它们是继承结构中的一部分,它们定义了一个对象是什么。其他的方面可以是mixin或者装饰,它们定义了一个对象是如何工作的。
8.10.1 设计要素和折中方案
在大多数情况下,is-a和acts-as的区别很清楚。继承的功能是全局问题域的一部分。当讨论模拟21点时,例如牌、手、下注、加牌和叫停很明显是问题域的一部分。类似地,数据的收集和对结果的统计分析是解决方案的一部分。其他的东西,例如日志、调试和审计并不是问题域的一部分,而是和解决方案所用的技术有关。
尽管大多数情况这些东西非常清晰,但是继承和装饰方面之间的分别有可能不是很清楚。在一些情况下,它可能会由一种审美判断决定。通常,当创建与特定问题无关的框架和基础架构类时,做这种决定会变得很难,通用的策略如下。
- 首先,问题的核心方面会需要类的定义。许多类都是继承自特定的问题,然后组成可以让多态按我们预期的方式进行工作的类结构。
- 其次,一些方面会需要定义mixin类。当有一些多维的方面时,这件事常会发生。对于一种设计,可能有独立的坐标或者维度。每个维度都是多态的一种选择。当介绍21点时,有两种策略:打牌策略和下注策略。这两个策略是独立的,或许也可以看作是一种全局玩家设计的mixin元素。
当定义独立的mixin时,可以有独立的继承结构。对于21点的策略,可以定义一个和打牌策略无关的多态结构。然后,我们可以定义同时拥有两种结构中mixin元素的玩家。
方法通常从类定义中创建。它们是主类或者mixin类的一部分。正如之前所介绍的,我们有3种设计策略:封装、扩展和创造。我们可以通过用一个类“封装”另一个类引入新的功能。在一些情况下,我们会发现我们被强制要求暴露一些只是简单地委托给底层类的方法。当我们使用了太多的委托之后,边界就会变得模糊;一个装饰器或者mixin可能是更好的选择。在一些其他的情况下,封装一个类可能比引入一个mixin类更加清楚。
与问题正交的方面经常可以用装饰器解决。装饰器可以被用于引入不属于is-a关系的功能。
8.10.2 展望
下面的章节会关注一些不同的内容。我们已经介绍了几乎所有Python内置的特殊方法。下面的5章内容会专注于对象的持久化和序列化。我们会以不同的外部标记法为开始介绍对象的序列化和保存,包括JSON、YAML、Pickle、CSV和XML。
序列化和持久化会带来更面向对象的类设计方法。我们会介绍对象关系以及它们是如何表示的。我们也会介绍序列化和反序列化对象带来的开销以及不被信任的源反序列化对象带来的安全问题。
