├── README.md ├── images ├── c13_1.png ├── c13_2.png ├── c5_1.png ├── chapter2_1.png └── the_qrcode_for_qq_group.png ├── 第一章 工厂模式.md ├── 第三章-原型模式.md ├── 第二章-构造器模式.md ├── 第五章-装饰器模式.md ├── 第八章-模型-视图-控制器模式.md ├── 第十三章-观察者模式.md └── 第四章-适配器模式.md /README.md: -------------------------------------------------------------------------------- 1 | # Mastering.Python.Design.Patterns 2 | 3 | 《精通Python设计模式》 4 | -------------------------------------------------------------------------------- /images/c13_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/Mastering.Python.Design.Patterns/f7e0b6b85d0031b02fc9712a80b098c958ba12b6/images/c13_1.png -------------------------------------------------------------------------------- /images/c13_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/Mastering.Python.Design.Patterns/f7e0b6b85d0031b02fc9712a80b098c958ba12b6/images/c13_2.png -------------------------------------------------------------------------------- /images/c5_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/Mastering.Python.Design.Patterns/f7e0b6b85d0031b02fc9712a80b098c958ba12b6/images/c5_1.png -------------------------------------------------------------------------------- /images/chapter2_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/Mastering.Python.Design.Patterns/f7e0b6b85d0031b02fc9712a80b098c958ba12b6/images/chapter2_1.png -------------------------------------------------------------------------------- /images/the_qrcode_for_qq_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/Mastering.Python.Design.Patterns/f7e0b6b85d0031b02fc9712a80b098c958ba12b6/images/the_qrcode_for_qq_group.png -------------------------------------------------------------------------------- /第一章 工厂模式.md: -------------------------------------------------------------------------------- 1 | 第一章 工厂模式 2 | ============ 3 | 创造模式处理一个对象的创建。创造模式的目的是为了在不按照约定而直接地创建的地方提供可选择的情况。 4 | 5 | 在工厂模式中,客户端查询一个对象而不知道这个对象来自哪里(即,哪一个类被用来生成它)。在一个工厂背后的思想是简化一个对象的创建。如果这个结果是通过一个中心函数来完成,相比之下要让一个客户端直接地使用类实例化来创建对象,跟踪哪一个对象被创建则会更容易些。通过分离要使用的代码,工厂减少了一个应用维护的复杂性。 6 | 7 | 工厂通常以两张形式出现:**工厂模式**,是一个每次输入参数都返回一个不同的方法(在Python术语中称为函数);抽象工厂,它是一组工厂方法用于创建相关产品的系列。 8 | 9 | 工厂方法 10 | -------------- 11 | ******** 12 | 在工厂方法中,我们执行一个单独的函数,传递一个参数,它提供了我们想要的是*什么* 信息。我们不要求知道任何关于这个对象的*如何*实现的细节,以及它来自*哪里*。 13 | 14 | ### 真实的例子 15 | 一个工厂方法模式现实中的使用是用在塑料玩具厂。建模粉用于制造塑料玩具是相同的,但不同的外形可以使用不同的塑料模具生产它。这就像有一个工厂模式它有一个可以输入我们想要的外形名称的地方,而且输出是我们所要求的塑料外形。玩具制造的例子如下图表所示。 16 | ### 软件例子 17 | Django框架使用工厂方法模式创建一个表单的字段。Django的forms模块支持创建不同的种类的字段(CharField, EmailField)和自定义的参数(max_length, required)。 18 | 19 | ### 使用案例 20 | 如果你意识到你不能够追踪应用的对象创建,那是因为这些代码被写在很多地方而不是一个单独的函数或者方法,你就应该考虑使用那个工厂方法模式。工厂方法使一个对象的创建集中化,追踪对象也变得非常简单。注意,它是完完全全可以创建不止一个工厂方法的。逻辑上,每一个工厂方法组的对象创建都是相似的。例如,一个工厂方法可以负责连接不同的数据库(MySQL, SQLite),另外一个工厂方法可以负责创建你所请求的几何对象(圆形,三角形),等等。 21 | 22 | 工厂方法在你想要从对象使用中分离对象创建时也是大有裨益的。我们在创建一个对象时不合并或者绑定到一个指定的类,通过调用一个函数我们只提供我们所想要的部分信息。这意味着将改变引入到函数很简单而且不要求任何对所使用代码的改变。 23 | 24 | 另外的用法值得一提是一个应用的提高性能和内存使用的关联。工厂方法可以由只创建一个绝对必要的新对象来提高性能和内存利用。当我们创建对象时使用一个直接的类实例,每次一个新对象(除非,类使用内部缓存,不过通常没有这种情况)都要额外的内存来分配。我们可以看到在下面代码(文件id.py)的实践中,它创建同一个类A的两个实例,使用id()函数去对比这些实例的内存地址。 25 | 26 | 这些地址也在输出中打印出来,所以我们可以检验它们。如下,内存地址的实际情况是两个有明显区别的对象的以不同方法来创建: 27 | 28 | Class A(object): 29 | pass 30 | 31 | if __name__ == '__main__': 32 | a = A() 33 | b = A() 34 | 35 | print(id(a) == id(b)) 36 | print(a, b) 37 | 38 | 39 | 在电脑上执行id.py的给出如下输入内容: 40 | python3 id.py 41 | False 42 | <__main__.A object at 0x7f5771de8f60> <__main__.A object at 0x7f5771df2208>” 43 | 44 | 注意,如果你执行文件所见到的地址和我所见到不相同,那是因为它们依赖当前内存布局和分配。但是结果必须是一样:两个地址应该是不一样的。如果你在Python的读取-求职-打印 循环(REPL)(交互提示环境)中写和执行代码会有异常发生,但是针对REPL的优化是通常不可能有的。 45 | 46 | ### 实现 47 | 数据表现有很多形式。存储和重取数据有两种主要的文件类型:人类可读的文件和二进制文件。人类可读的文件例子有XML,Atom,YAML和JSON。二进制文件的例子有被SQLite所使用的 .sq3 文件格式,用于听音乐的 .mp3 文件格式。 48 | 49 | 这个例子中,我们会关注两个流行的人类可读格式:XML和JSON。尽管,人类可读文件通常解析较慢于二进制文件,但是它们让数据交换,检查,和修改变得非常容易。因此,建议选择使用人类可读文件,除非有其他的限制让你不允许你这样做(主要地是难以接收的性能和专有的二进制格式)。 50 | 51 | 这个问题中,我们在XML和JSON文件里已经有一些存储的数据,我们想要解析它们,重新取回一些信息。与此同时,我们想要集中化客户这些扩展服务的连接(以及未来所有的连接)。我们会使用工厂方法解决这个问题。这个例子只关注于XML和JSON,但是添加更多的服务支持也应该是简单明了的。 52 | 53 | 首先,让我们看看数据文件。如下,XML文件person.xml,基于Wikipedia的例子,包含单独的信息(firstName, lastName, gender,等等): 54 | 55 | ```xml 56 | 57 | 58 | John 59 | Smith 60 | 25 61 |
62 | 21 2nd Street 63 | New York 64 | NY 65 | 10021 66 |
67 | 68 | 212 555-1234 69 | 646 555-4567 70 | 71 | 72 | male 73 | 74 |
75 | 76 | Jimy 77 | Liar 78 | 19 79 |
80 | 18 2nd Street 81 | New York 82 | NY 83 | 10021 84 |
85 | 86 | 212 555-1234 87 | 88 | 89 | male 90 | 91 |
92 | 93 | Patty 94 | Liar 95 | 20 96 |
97 | 18 2nd Street 98 | New York 99 | NY 100 | 10021 101 |
102 | 103 | 212 555-1234 104 | 001 452-8819 105 | 106 | 107 | female 108 | 109 |
110 |
111 | ``` 112 | 113 | JSON文件donut.json,来自Github上面的Adobe账户, 包含甜甜圈信息(type,即价格和单元,ppu, topping,等等)如下: 114 | 115 | ```json 116 | [ 117 | { 118 | "id": "0001", 119 | "type": "donut", 120 | "name": "Cake", 121 | "ppu": 0.55, 122 | "batters": { 123 | "batter": [ 124 | { "id": "1001", "type": "Regular" }, 125 | { "id": "1002", "type": "Chocolate" }, 126 | { "id": "1003", "type": "Blue berry" }, 127 | { "id": "1004", "type": "Devil's Food" } 128 | ] 129 | }, 130 | "topping": [ 131 | { "id": "5001", "type": "None" }, 132 | { "id": "5002", "type": "Glazed" }, 133 | { "id": "5005", "type": "Sugar" }, 134 | { "id": "5007", "type": "Powdered Sugar" }, 135 | { "id": "5006", "type": "Chocolate with Sprinkles" }, 136 | { "id": "5003", "type": "Chocolate" }, 137 | { "id": "5004", "type": "Maple" } 138 | ] 139 | }, 140 | { 141 | "id": "0002", 142 | "type": "donut", 143 | "name": "Raised", 144 | "ppu": 0.55, 145 | "batters": { 146 | "batter": [ 147 | { "id": "1001", "type": "Regular" } 148 | ] 149 | }, 150 | "topping": [ 151 | { "id": "5001", "type": "None" }, 152 | { "id": "5002", "type": "Glazed" }, 153 | { "id": "5005", "type": "Sugar" }, 154 | { "id": "5003", "type": "Chocolate" }, 155 | { "id": "5004", "type": "Maple" } 156 | ] 157 | }, 158 | { 159 | "id": "0003", 160 | "type": "donut", 161 | "name": "Old Fashioned", 162 | "ppu": 0.55, 163 | "batters": { 164 | "batter": [ 165 | { "id": "1001", "type": "Regular" }, 166 | { "id": "1002", "type": "Chocolate" } 167 | ] 168 | }, 169 | "topping": [ 170 | { "id": "5001", "type": "None" }, 171 | { "id": "5002", "type": "Glazed" }, 172 | { "id": "5003", "type": "Chocolate" }, 173 | { "id": "5004", "type": "Maple" } 174 | ] 175 | } 176 | ] 177 | ``` 178 | 179 | 我们会对XML和JSON使用Python发行版中的两个库的一部分:xml.etree.ElementTree和json: 180 | 181 | ```python 182 | import xml.etree.ElementTree as etree 183 | import json 184 | ``` 185 | 186 | JSONConnector类解析JSON文件,含有一个将所有数据作为一个字典(dict)返回的parsed_data()方法。property装饰器用来使parsed_data()作为一个正常变量出现而不是作为一个方法出现: 187 | 188 | ```python 189 | Class JSONConnector: 190 | def __init__(self, filepath): 191 | self.data = dict() 192 | with open(filepath, mode='r', encoding='utf-8') as f: 193 | self.data = json.load(f) 194 | 195 | @property 196 | def parsed_data(self): 197 | return self.data 198 | ``` 199 | 200 | 201 | XMLConnector类解析XML文件并含有把所有数据作为一个xml.etree.Element的列表的一个parsed_data()方法: 202 | 203 | ```python 204 | Class XMLConnector: 205 | 206 | def __init__(self, filepath): 207 | self.tree = etree.parse(filepath) 208 | 209 | @property 210 | def parsed_data(self): 211 | return self.tree 212 | ``` 213 | 214 | 215 | connection_factory()函数是一个工厂方法。它基于输入文件的扩展,返回一个JSONConnector或者XMLConnector的实例: 216 | 217 | ```python 218 | def connection_factory(filepath): 219 | if filepath.endwith('json'): 220 | connector = JSONConnector 221 | elif filepath.endwith('xml'): 222 | connector = XMLConnector 223 | else: 224 | raise ValueError('Cannot connect to {}'.format(filepaht)) 225 | return connector(filepath) 226 | 227 | ``` 228 | 229 | 抽象工厂 230 | ------------ 231 | ****** 232 | 抽象工厂设计模式是工厂模式的归纳。基本上,抽象工厂是一组(逻辑上的)工厂方法,这里每个工厂模式都负责生成一种不同的对象。 233 | 234 | ### 真实的例子 235 | 抽象工厂用在车辆织造中。相同的机械装置用来冲压不同的车辆模型的部件(门,面板,车身罩体,挡泥板,以及各种镜子)。聚合了不同机械装置的模型是可配置的,而且在任何时候都可以轻松变更。在下图中我们可以看到一个车辆制造的抽象工厂的例子。 236 | 237 | ### 软件例子 238 | **django_factory** 包是一个为了在测试中创建Django模型的抽象工厂实现。它用来创建支持指定测试属性的模型实例。这是很重要的因为测试变得可读,以及避免共享不必要的代码。 239 | 240 | ## 使用案例 241 | 因为抽象工模式是工厂方法模式的归纳,它具有同样的优点:使得追踪一个对象创建更容易,它让对象的使用和创建分离开,并且给我们内存使用以及应用的性能提高的可能。 242 | 243 | 但是问题来了:我们如何知道什么时候使用工厂模式还是抽象工厂?答案是我们通常以更简单的工厂模式开始。如果我们发现应用需要很多的工厂模式,要让它变得有意义就要合并同族对象,那么我们就使用它。抽象工厂的好处站在用户的角度来看不是很明显,但是在使用工厂模式时,通过改变活动的工厂方法它给了我们修改动态的应用(运行中的)的能力。经典的例子是给予改变一个应用的外观和感觉(例如,Apple以及Windows这样的系统,等等),对于用户来说就是在应用使用时,不需要终止它,启动它。 244 | 245 | ## 实现 246 | 为了阐明抽象工厂模式,我将再次使用我最喜爱的例子之一。想象我们正在开发一款游戏,或者我们想把一款小游戏加入到自己的应用中来娱乐用户。我们想要至少包含两个游戏,一个给小孩子玩,一个给成人玩。基于用户输入,我们要决定在运用时创建哪一个游戏。一个抽象工厂便能做到游戏的创建部分。 247 | 248 | 让我们从儿童游戏开始吧。这个游戏叫做FrogWorld。主角是一个喜欢吃昆虫的青蛙。每个主角都需要的一个响亮的名号,在运行时,我们的这个例子中名字是由用户给出的。如下所示, interact_with()方法用于描述青蛙和障碍物(例如,虫子,迷宫以及别的青蛙)的互动: 249 | 250 | ```python 251 | Class Frog: 252 | def __init__(self, name): 253 | self.name = name 254 | 255 | def __str__(self): 256 | return self.name 257 | 258 | def interact_with(self, obstacle): 259 | print('{} the Frog encounters {} and {}!'.format(self, obstacle, obstacle.action())) 260 | ``` 261 | 262 | 可以有很多种类的障碍,不过对于我们的这个例子来说可以只有一个Bug。青蛙遇到一个虫子,只有一个行为被支持:吃掉它! 263 | 264 | ```python 265 | Class Bug: 266 | def __str__(self): 267 | return 'a bug' 268 | 269 | def action(self): 270 | return 'eats it' 271 | ``` 272 | 273 | FrogWorld类是一个抽象工厂。它的主要责任是游戏的主角和障碍物。保持创建方法的独立和方法名称的普通(例如,make_character(), make _obstacle())让我们可以动态的改变活动工厂(以及活动的游戏)而不设计任何的代码变更。如下所示,在一个静态类型语言中,抽象工厂可以是一个有着空方法的抽象类/接口,但是在Python中不要求这么做,因为类型在运行时才被检查。 274 | 275 | ```python 276 | Class FrogWorld: 277 | def __inti__(self, name): 278 | print(self) 279 | self.player_name = name 280 | 281 | def __str__(self): 282 | return '\n\n\t------ Frog World ------' 283 | 284 | def make_character(self): 285 | return Forg(self.player_name) 286 | 287 | def make_obstacle(self): 288 | return Bug() 289 | ``` 290 | 291 | 292 | 游戏WizarWorld也类似。唯一的不同是巫师不吃虫子而是与半兽人这样的怪物战斗! 293 | 294 | ```python 295 | Class Wizard: 296 | def __init__(self, name): 297 | self.name = name 298 | 299 | def __str__(self): 300 | return self.name 301 | 302 | def interact_with(self, obstacle): 303 | print('{} the Wizard batteles against {} and {}!'.format(self, obstacle, obstacle.action())) 304 | 305 | 306 | Class Ork: 307 | def __str__(self): 308 | return 'an evial ork' 309 | 310 | def action(self): 311 | return 'kills it' 312 | 313 | 314 | 315 | Class WizardWorld: 316 | def __init__(self, name): 317 | print(self) 318 | self.player_name = name 319 | 320 | def __str__(self): 321 | return '\n\n\t------ Wizard World ------' 322 | 323 | def make_character(self): 324 | return Wizard(self.player_name) 325 | 326 | def make_obstacle(self): 327 | return Ork() 328 | ``` 329 | 330 | GameEnviroment是我们游戏的主要入口。它接受factory作为输入,然后用它创建游戏的世界。play()方法发起创造的hero和obstacle之间的交互如下: 331 | 332 | ```python 333 | Class GameEnviroment: 334 | def __init__(self, factory): 335 | self.hero = factory.make_character() 336 | self.obstacle = factory.make_obstacle() 337 | 338 | def play(self): 339 | self.hero.interact_with(self.obstacle) 340 | ``` 341 | 342 | validate_age()函数提示用户输入一个有效的年龄。若年龄无效,则返回一个第一个元素设置为False的元组。如下所示,若输入的年龄没问题,元组的第一个元素设置为True,以及我们所关心的该元组的第二个元素,即用户给定的年龄: 343 | 344 | ```python 345 | def validate_age(name): 346 | try: 347 | age = input('Welcome {}. How old are you?'.format(name)) 348 | age = int(age) 349 | except ValueError as err: 350 | print("Age {} is valid, plz try again...".format(age)) 351 | return (False, age) 352 | return (True, age) 353 | ``` 354 | 355 | 356 | 最后,尤其是main()函数的出现。它询问了用户的名字和年龄,然后由用户的年龄来决定哪一个游戏应该被运行: 357 | 358 | ```python 359 | def main(): 360 | name = input("Hello. What's your name?") 361 | valid_input = False 362 | while not valid_input: 363 | valid_input, age = validate_age(name) 364 | game = FrogWorld if age < 18 else WizardWorld 365 | enviroment = GameEnviroment(game(name)) 366 | enviroment.play() 367 | ``` 368 | 369 | 抽象工厂实现(abstrac_factory.py)的完整代码如下: 370 | 371 | 该程序的简单输出如下: 372 | 373 | 试着扩展游戏使它更加复杂。你的思想有多远就可以走多远:更多的障碍物,更多的敌人,以及任何你喜欢的东西。 374 | 375 | # 总结 376 | 在这一章,我们已经见过如何使用工厂模式和抽象工厂设计模式。两种模式都可以在我们需要时使用(a)追踪对象创建,(b)分离对象的使用和创建,甚至(c)改善一个应用的性能和资源使用。情形(c)并不在章节中阐明。你可以把它当作一个不错的练习。 377 | 378 | 工厂方法设计模式以一个不属于任何类的独立函数形式实现,它负责单一种类的对象(模型,接口,等等)的创建。我们看了工厂方法如何关联到玩具厂,注意到它如何被Django用来创建不同的表单字段,讨论了它的其他可能使用场合。像一个例子那样,我们实现了一个具有访问XML和JSON格式文件的工厂方法。 379 | 380 | 抽象工厂设计模式通过一组属于一个单独类的工厂方法,它用于创建关联对象的家族(汽车的零部件,游戏的环境,等等)。我们注意到抽象工厂如何与汽车制造关联起来,Django包django_factory如何使用它去创建洁净测试,并覆盖了它的使用案例。抽象工厂的实现是一个向我们如何在一个单一类中使用多个相关工厂的小游戏。 381 | 382 | 在下一章,我们会谈及生成器模式,它是另外一种可以用于复杂对象创建的精细控制的创建模式。 383 | 384 | -------------------------------------------------------------------------------- /第三章-原型模式.md: -------------------------------------------------------------------------------- 1 | # 第三章-原型模式 2 | **************** 3 | Sometimes, we need to create an exact copy of an object. For instance, assume 4 | that you want to create an application for storing, sharing, and editing (such as modifying, adding notes, and removing) culinary recipes. User Bob finds a cake recipe and after making a few modifications he thinks that his cake is delicious, and he wants to share it with his friend, Alice. But what does sharing a recipe mean? 5 | If Bob wants to do some further experimentation with his recipe after sharing it with Alice, will the new changes also be visible in Alice's recipe? Can Bob keep two copies of the cake recipe? His delicious cake recipe should remain unaffected by any changes made in the experimental cake recipe. 6 | 7 | 有时候,我们需要创建一个对象的完全拷贝。例如,假设你想要创建一个能够存储、分享、和编辑的(比如,修改,添加笔记、删除笔记)菜谱应用。用户Bod发现了一本糕点食谱,之后对他做出了一些自认为可以让蛋糕美味的轻微修改,而且他还想和好友爱丽丝分享这份食谱。那么分享食谱意味着什么呢?如果鲍勃想要在把食谱分享给爱丽丝之后进行深入实验,那么对食谱的新变更也可以出现在爱丽丝的食谱中吗?鲍勃可以拥有蛋糕食谱的双份拷贝吗?他的美味蛋糕食谱应该不受到任何试验性蛋糕食谱修改的影响。 8 | 9 | Such problems can be solved by allowing the users to have more than one independent copy of the same recipe. Each copy is called a clone, because it is an exact copy of the original object at a specific point in time. The time aspect is important, since it affects what the clone contains. For example, if Bob shares the cake recipe with Alice before making his own improvements to achieve perfection, Alice will never be able to bake her own version of the delicious cake that Bob created! She will only be able to bake the original cake recipe found by Bob. 10 | 11 | 此类问题可以通过让用户使用不止一份独立的相同拷贝来解决。每个拷贝都称作克隆,因为这份拷贝是在一个指定对时间内的原始对象拷贝。时间方面很重要,因为它影响着将要克隆的内容。例如,如果鲍勃在改进食谱已实现完美之前就把蛋糕食谱分享给了爱丽丝,那么爱丽丝就永远不能够烘培鲍勃所创建的美味蛋糕的私有定制版本!她只能够烘培由鲍勃创建的原始版本蛋糕食谱中的蛋糕。 12 | 13 | Note the difference between a copy and a reference. If we have two references to the same cake recipe, whatever changes Bob makes to the recipe will be visible to Alice's version of the recipe, and vice versa. What we want is both Bob and Alice to have their own copy, so that they can make independent changes without affecting each other's recipe. Bob actually needs two copies of the cake recipe: the delicious version and the experimental version. 14 | 15 | 注意拷贝和引用之间的不同。如果对相同的糕点食谱做了两个引用,不论Bob对食谱做了什么更改,Alice的食谱中都可以看到,反之亦然。要是我们想让Bob和Alice都拥有各自的拷贝,那么他们就可以各自变更而不影响到对方的食谱。Bob实际上需要糕点食谱的两份拷贝:美味版和体验版。 16 | 17 | The difference between a reference and a copy is shown in the following figure: 18 | 19 | 引用和复制之间的区别如下图所示: 20 | 21 | ![img](images/) 22 | 23 | On the left part, we can see two references. Both Alice and Bob refer to the same recipe, which essentially means that they share it and all modifications are visible by both. On the right part, we can see two different copies of the same recipe. In this case, independent modifications are allowed and the changes of Alice do not affect the changes of Bob, and vice versa. 24 | 25 | 在最后一部分,我们看到了两个引用。Alice和Bod引用了相同的食谱,这基本上就意味着他们共享了所有的变更,而且这些变更彼此课件。在右边部分,我们可以看到相同食谱的两份不同拷贝。这种情况下,单独的修改是被允许的,Alice的修改不会影响到Boo,反之亦然。 26 | 27 | The **Prototype design pattern** helps us with creating object clones. In its simplest version, the Prototype pattern is just a clone() function that accepts an object as an input parameter and returns a clone of it. In Python, this can be done using the copy.deepcopy() function. Let's see an example. In the following code (file clone.py), there are two classes, A and B. A is the parent class and B is the derived class. In the main part, we create an instance of class B b, and use deepcopy() to create a clone of b named c. The result is that all the members of the hierarchy (at the point of time the cloning happens) are copied in the clone c. As an interesting exercise, you can try using deepcopy() with composition instead of inheritance which is shown in the following code: 28 | 29 | 原型设计模式有助于我们创建一个对象的克隆。在其最简单版本中,原型模式只是一个接受对象作为输入参数并返回这个对象克隆的clone()函数。在Python中,可以使用copy.deepcopy()函数实现此目的。让我们看一个例子。在下面的代码中(文件clone.py),由两个类A和B。A是一个父类而B是一个派生类。在主体部分中,我们创建了一个类B的实例b,并使用了deepcopy()创建了一个叫做c的实例b的克隆。 30 | 31 | ```python 32 | import copy 33 | 34 | 35 | class A: 36 | def __init__(self): 37 | self.x = 18 38 | self.msg = 'Hello' 39 | 40 | 41 | class B(A): 42 | def __init__(self): 43 | A.__init__(self) 44 | self.y = 34 45 | def __str__(self): 46 | return '{}, {}, {}'.format(self.x, self.msg, self.y) 47 | 48 | 49 | if __name__ == '__main__': 50 | b = B() 51 | c = copy.deepcopy(b) 52 | print([str(i) for i in (b, c)]) 53 | print([i for i in (b, c)]) 54 | ``` 55 | 56 | When executing clone.py on my computer, I get the following: 57 | 58 | 当你执行clone.py后,输出内容是: 59 | 60 | ```python 61 | 62 | >>> python3 clone.py 63 | ['18, Hello, 34', '18, Hello, 34'] 64 | [<__main__.B object at 0x7f92dac33240>, <__main__.B object at 65 | 0x7f92dac33208>] 66 | ``` 67 | 68 | Although your output of the second line will most likely not be the same as mine, what's important is to notice that the two objects reside in two different memory addresses (the 0x... part). This means that the two objects are two independent copies. 69 | 70 | 虽然你的第二行的输出和我的有着很大的不同,重要的在于注意这两个对象位于两个不同的内存地址中(0x...那一部分)。这就意味着两个对象是两份独立的副本。 71 | 72 | In the Implementation section, later in this chapter, we will see how to use `copy. deepcopy()` with some extra boilerplate code wrapped in a class, for keeping a registry of the objects that are cloned. 73 | 74 | 在本章稍后的实现小节中,我们会看到如何使用`copy. deepcopy()` 75 | 76 | ## A real-life example 77 | The Prototype design pattern is all about cloning an object. Mitosis, the process in a cell division by which the nucleus divides resulting in two new nuclei, each of which has exactly the same chromosome and DNA content as the original cell, is an example of biological cloning [j.mp/mmitosis]. 78 | 79 | 原型设计模式全都是关于克隆一个对象的。 80 | 81 | The following figure, provided by www.sourcemaking.com, shows an example of the mitotic division of a cell [j.mp/pprotpat]: 82 | 83 | 在下面的由www.sourcemaking.com提供的图表中, 84 | 85 | 图片:略 86 | 87 | Another popular example of (artificial) cloning is Dolly, the sheep [j.mp/wikidolly]. 88 | 89 | 另外一个流行的克隆案例是多莉羊🐑。 90 | 91 | ## A software example 92 | There are many Python applications that make use of the Prototype pattern [j.mp/ pythonprot], but it is almost never referred to as Prototype since cloning objects 93 | is a built-in feature of the language. 94 | 95 | 有很多的Python应用使用了原型模式,但几乎从不提及原型,因为克隆对象是这门语言的一个内置功能。 96 | 97 | One application that uses Prototype is the **Visualization Toolkit (VTK)** [j.mp/pyvto]. VTK is an open source cross-platform system for 3D computer graphics, image processing, and visualization. VTK uses Prototype for creating clones of geometrical elements such as points, lines, hexahedrons, and so forth [j.mp/vtkcell]. 98 | 99 | Another project that uses Prototype is **music21**. According to the project's page, "music21 is a set of tools for helping scholars and other active listeners answer questions about music quickly and simply" [j.mp/pmusic21]. The music21 toolkit uses Prototype for copying musical notes and scores [j.mp/py21code]. 100 | 101 | ## Use cases 102 | The Prototype pattern is useful when we have an existing object and we want to create an exact copy of it. A copy of an object is usually required when we know that parts of the object will be modified but we want to keep the original object untouched. In such cases, it doesn't make sense to recreate the original object from scratch [j.mp/protpat]. 103 | 104 | 当我们拥有一个现有对象,而且想要创建一个此对象的完全拷贝时原型模式就很有用了。 105 | 106 | Another case where Prototype comes in handy is when we want to duplicate a complex object. By duplicating a complex object, we can think of an object that is populated from a database and has references to other objects that are also populated from a database. It is a lot of effort to create an object clone by querying the database(s) multiple times again. Using Prototype for such cases is more convenient. 107 | 108 | So far, we have covered only the reference versus copy issue, but a copy can be further divided into a deep copy versus a shallow copy. A deep copy is what we 109 | have seen so far: all data of the original object are simply copied in the clone, without making any exceptions. A shallow copy relies on references. We can introduce data sharing, and techniques like copy-on-write to improve the performance (such as clone creation time) and the memory usage. Using shallow copies might be worthwhile if the available resources are limited (such as embedded systems) or performance is critical (such as high-performance computing). 110 | 111 | 到目前为止,我们只说到了引用与复制的问题,但是复制又被进一步分为深拷贝和浅拷贝。我们目前所见到的深拷贝是: 112 | 113 | In Python, we can do shallow copies using the copy.copy() function. Quoting the official Python documentation, the differences between a shallow copy (copy. copy()) and a deep copy (copy.deepcopy()) in Python are [j.mp/py3copy] 114 | as follows: 115 | 116 | 在Python中,我们可以使用函数copy.copy()执行浅拷贝。引用Python官方文档来说,Python中的浅拷贝和深拷贝的区别在于以下两点: 117 | 118 | - "A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original. 119 | 120 | - A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original." 121 | 122 | - “” 123 | - 124 | 125 | Can you think of any examples where using shallow copies is better than using deep copies? 126 | 127 | ## Implementation 实现 128 | In programming, it is not uncommon for a book to be available in multiple editions. For example, the classic textbook on C programming The C Programming Language by Kernighan and Ritchie is available in two editions. The first edition was published in 1978. At that time, C was not standardized. The second edition of the book was published 10 years later and covers the standard (ANSI) version of C. What are the differences between the two editions? To mention a few, the price, the length (number of pages), and the publication date. But there are also many similarities: the authors, the publishers, and the tags/keywords that describe the book are exactly the same. This indicates that creating a new book from scratch is not always the best approach. If we know that there are many similarities between two book editions, we can use cloning and modify only the different parts of the new edition. 129 | 130 | Let's see how we can use the Prototype pattern for creating an application that shows book information. We begin with the representation of a book. Apart from the usual initialization, the Book class demonstrates an interesting technique. It shows how we can avoid the telescopic constructor problem. In the `__init__()` method, only three parameters are fixed: name, authors, and price. But clients can pass more parameters in the form of keywords (name=value) using the rest variable-length list. The line `self.__dict__.update(rest)` adds the contents of rest to the internal dictionary of the Book class to make them part of it. 131 | 132 | But there's a catch. Since we don't know all the names of the added parameters, we need to access the internal dict for making use of them in `__str__()`. And since the contents of a dictionary do not follow any specific order, we use an OrderedDict to force an order; otherwise, every time the program is executed, different outputs will be shown. Of course, you should not take my words for granted. As an exercise, remove the usage of OrderedDict and sorted() and run the example to see if I'm right: 133 | 134 | ```python 135 | class Book: 136 | def __init__(self, name, authors, price, **rest): 137 | '''Examples of rest: publisher, length, tags, publication 138 | date''' 139 | self.name = name 140 | self.authors = authors 141 | self.price = price # in US dollars 142 | self.__dict__.update(rest) 143 | def __str__(self): 144 | mylist=[] 145 | ordered = OrderedDict(sorted(self.__dict__.items())) 146 | for i in ordered.keys(): 147 | mylist.append('{}: {}'.format(i, ordered[i])) 148 | if i == 'price': 149 | mylist.append('$') 150 | mylist.append('\n') 151 | return ''.join(mylist) 152 | 153 | ``` 154 | 155 | The `Prototype` class implements the Prototype design pattern. The heart of the Prototype class is the clone() method, which does the actual cloning using the familiar copy.deepcopy() function. But the Prototype class does a bit more than supporting cloning. It contains the register() and unregister() methods, which can be used to keep track of the objects that are cloned in a dictionary. Note that this is just a convenience, and not a necessity. 156 | 157 | 158 | Moreover, the `clone()` method uses the same trick that `__str__()` uses in the Book class, but this time for a different reason. Using the variable-length list attr, we can pass only the variables that really need to be modified when cloning an object as follows: 159 | 160 | ```python 161 | class Prototype: 162 | def __init__(self): 163 | self.objects = dict() 164 | def register(self, identifier, obj): 165 | self.objects[identifier] = obj 166 | def unregister(self, identifier): 167 | del self.objects[identifier] 168 | def clone(self, identifier, **attr): 169 | found = self.objects.get(identifier) 170 | if not found: 171 | raise ValueError('Incorrect object identifier:{}'.format(identifier)) 172 | obj = copy.deepcopy(found) 173 | obj.__dict__.update(attr) 174 | return obj 175 | ``` 176 | 177 | The `main()` function shows The C Programming Language book cloning example mentioned at the beginning of this section in practice. When cloning the first edition of the book to create the second edition, we only need to pass the modified values of the existing parameters. But we can also pass extra parameters. In this case, edition is a new parameter that was not needed in the first book but is useful information for the clone: 178 | 179 | ```python 180 | def main(): 181 | b1 = Book('The C Programming Language', ('Brian W. Kernighan', 182 | 'Dennis M.Ritchie'), price=118, publisher='Prentice Hall', 183 | length=228, publication_date='1978-02-22', tags=('C', 184 | 'programming', 'algorithms', 'data structures')) 185 | prototype = Prototype() 186 | cid = 'k&r-first' 187 | prototype.register(cid, b1) 188 | b2 = prototype.clone(cid, name='The C Programming Language(ANSI)', price=48.99, length=274, 189 | publication_date='1988-04-01', edition=2) 190 | for i in (b1, b2): 191 | print(i) 192 | print("ID b1 : {} != ID b2 : {}".format(id(b1), id(b2))) 193 | ``` 194 | 195 | 196 | Notice the usage of the id() function which returns the memory address of an object. When we clone an object using a deep copy, the memory addresses of the clone must be different from the memory addresses of the original object. 197 | 198 | The `prototype.py` file is as follows: 199 | 200 | ```python 201 | import copy 202 | from collections import OrderedDict 203 | 204 | 205 | class Book: 206 | def __init__(self, name, authors, price, **rest): 207 | '''Examples of rest: publisher, length, tags, publication 208 | date''' 209 | self.name = name 210 | self.authors = authors 211 | self.price = price # in US dollars 212 | self.__dict__.update(rest) 213 | def __str__(self): 214 | mylist=[] 215 | ordered = OrderedDict(sorted(self.__dict__.items())) 216 | for i in ordered.keys(): 217 | mylist.append('{}: {}'.format(i, ordered[i])) 218 | if i == 'price': 219 | mylist.append('$') 220 | mylist.append('\n') 221 | return ''.join(mylist) 222 | 223 | 224 | class Prototype: 225 | def __init__(self): 226 | self.objects = dict() 227 | def register(self, identifier, obj): 228 | self.objects[identifier] = obj 229 | def unregister(self, identifier): 230 | del self.objects[identifier] 231 | def clone(self, identifier, **attr): 232 | found = self.objects.get(identifier) 233 | if not found: 234 | raise ValueError('Incorrect object identifier:{}'.format(identifier)) 235 | obj = copy.deepcopy(found) 236 | obj.__dict__.update(attr) 237 | return obj 238 | 239 | 240 | def main(): 241 | b1 = Book('The C Programming Language', ('Brian W. Kernighan', 242 | 'Dennis M.Ritchie'), price=118, publisher='Prentice Hall', 243 | length=228, publication_date='1978-02-22', tags=('C', 244 | 'programming', 'algorithms', 'data structures')) 245 | prototype = Prototype() 246 | cid = 'k&r-first' 247 | prototype.register(cid, b1) 248 | b2 = prototype.clone(cid, name='The C Programming Language (ANSI)', price=48.99, length=274, 249 | publication_date='1988-04-01', edition=2) 250 | for i in (b1, b2): 251 | print(i) 252 | print("ID b1 : {} != ID b2 : {}".format(id(b1), id(b2))) 253 | 254 | 255 | if __name__ == '__main__': 256 | main() 257 | ``` 258 | 259 | The output of id() depends on the current memory allocation of the computer and you should expect it to differ on every execution of this program. But no matter what the actual addresses are, they should not be the same in any chance. 260 | 261 | A sample output when I execute this program on my machine is as follows: 262 | 263 | ```python 264 | >>> python3 prototype.py 265 | authors: ('Brian W. Kernighan', 'Dennis M. Ritchie') 266 | length: 228 267 | name: The C Programming Language 268 | price: 118$ 269 | publication_date: 1978-02-22 270 | publisher: Prentice Hall 271 | tags: ('C', 'programming', 'algorithms', 'data structures') 272 | 273 | authors: ('Brian W. Kernighan', 'Dennis M. Ritchie') 274 | edition: 2 275 | length: 274 276 | name: The C Programming Language (ANSI) 277 | price: 48.99$ 278 | publication_date: 1988-04-01 279 | publisher: Prentice Hall 280 | tags: ('C', 'programming', 'algorithms', 'data structures') 281 | ID b1 : 140004970829304 != ID b2 : 140004970829472 282 | ``` 283 | 284 | Indeed, Prototype works as expected. The second edition of The C Programming Language book reuses all the information that was set in the first edition, and all the differences that we defined are only applied to the second edition. The first edition remains unaffected. Our confidence can be increased by looking at the output of the id() function: the two addresses are different. 285 | 286 | As an exercise, you can come up with your own example of Prototype. A few ideas are as follows: 287 | 288 | - The recipe example that was mentioned in this chapter 289 | - The database-populated object that was mentioned in this chapter 290 | - Copying an image so that you can add your own modifications without touching the original 291 | 292 | ## Summary 总结 293 | In this chapter, we have seen how to use the Prototype design pattern. Prototype is used for creating exact copies of objects. Creating a copy of an object can actually mean two things: 294 | 295 | 本章,我们见到了如何如何使用原型设计模式。原型用来创建精确地对象副本。创建一个对象的副本实际上意味着两件事情: 296 | 297 | - Relying on references, which happens when a shallow copy is created 298 | - 当执行浅拷贝时,视所引用的原始内容而定。 299 | 300 | - Duplicating everything, which happens when a deep copy is created 301 | - 在执行深拷贝时,重复所有内容。 302 | 303 | In the first case, we want to focus on improving the performance and the memory usage of our application by introducing data sharing between objects. But we need to be careful about modifying data, because all modifications are visible to all copies. Shallow copies were not introduced in this chapter, but you might want to experiment with them. 304 | 305 | 在第一个例子中,我们希望将注意力放在通过引入对象之间的数据共享对以提高性能以及内存利用上面。但是修改数据时我们需要特别小心,因为这些修改对于所有的复制都是可见的。本章我们不会介绍浅拷贝的,不过你可以自己了解了解。 306 | 307 | In the second case, we want to be able to make modifications to one copy without affecting the rest. That is useful for cases like the cake-recipe example that we have seen. Here, no data sharing is done and so we need to be careful about the resource consumption and the overhead that is introduced by our clones. 308 | 309 | 在第二个例子中,我们徐昂能够修改一个副本而不会影响到余下的副本。 310 | 311 | We showed a simple example of a deep copying which in Python is done using the copy.deepcopy() function. We also mentioned examples of cloning found in real life, focusing on mitosis. 312 | 313 | 我们演示了在Python中使用copy.deepcopy()函数的深拷贝的简单例子。我们也谈到了真实生活中存在的一个关于克隆寻找的例子,即细胞的有丝分裂。 314 | 315 | Many software projects use Prototype, but in Python it is not mentioned as such because it is a built-in feature. Among them are the VTK, which uses Prototype for creating clones of geometrical elements, and music21, which uses it for duplicating musical scores and notes. 316 | 317 | 很多的软件想都用到了原型,但是在Python中它却并未被提及,因为它是一个内建的功能。 318 | 319 | Finally, we discussed the use cases of Prototype and implemented a program that supports cloning books so that all information that does not change in a new edition can be reused, but at the same time modified information can be updated and new information can be added. 320 | 321 | 最后,我们讨论了原型的使用案例,实现了一个支持克隆登记的例子,这样在一个能够重复使用的新版本中所有的信息都不会被改变,但是在同一时间修改的信息能够被更新而且新信息也可以增加。 322 | 323 | Prototype is the last creational design pattern covered in this book. The next chapter begins with Adapter, a structural design pattern that can be used to make two incompatible software interfaces compatible. 324 | 325 | 原型 326 | 327 | 328 | 329 | -------------------------------------------------------------------------------- /第二章-构造器模式.md: -------------------------------------------------------------------------------- 1 | # 第二章 构造器模式 2 | ***************** 3 | 4 | Imagine that we want to create an object that is composed of multiple parts and the composition needs to be done step by step. The object is not complete unless all its parts are fully created. That's where the Builder design pattern can help us. The Builder pattern separates the construction of a complex object from its representation. By keeping the construction separate from the representation, the same construction can be used to create several different representations [GOF95, page 110], [j.mp/builderpat]. 5 | 6 | 想象一下,我们想要一个由多个部分合成的对象,而且合成需要一步步的来操作。这个对象在其自身所有部分都完全创建之前是不完整的。这就是构造器模式能够帮助我们的地方。构造器模式通过自己的表现将一个复杂对象的构造进行分离。通过使用表现的构造分离,相同的构造介意被用到多个不同到表现上。 7 | 8 | A practical example can help us understand what the purpose of the Builder pattern is. Suppose that we want to create an HTML page generator, the basic structure (construction part) of an HTML page is always the same: it begins with and finishes with ; inside the HTML section are the and elements, inside the head section are the and elements, and so forth. But the representation of the page can differ. Each page has its own title, its own headings, and different contents. Moreover, the page is usually built in steps: one function adds the title, another adds the main heading, another the footer, and so on. Only after the whole structure of a page is complete can it be shown to the client using a final render function. We can take it even further and extend the HTML generator so that it can generate totally different HTML pages. One page might contain tables, another page might contain image galleries, yet another page contains the contact form, and so on. 9 | 10 | 实际的例子可以帮助我们理解使用构造器的目的是什么。假设我们想要创建一个HTML页面生成器,基本的HTML页面结构都是相同的:以开始并以结束;内部HTML区域是和元素,内部头区域是元素,等等。但是页面的表现又是不同的。每个页面都拥有自己的标题,自己的头,以及不同的内容。此外,页面通常的组成有多个步骤:一个函数添加标题,另一个添加主要头部,另外一个添加footer,等等。仅在整个页面合成完毕后,才使用最后的渲染函数将页面显示给用户。我们可以做到跟进一步,去扩展HTML生成器,这样它就可以生成完全不同的HTML页面了。一个页面或许包含表格、一个页面可能含有图片展示,另外一个页面还可能包含联系人表单,等等。 11 | 12 | The HTML page generation problem can be solved using the Builder pattern. In this pattern, there are two main participants: the builder and the director. The builder 13 | is responsible for creating the various parts of the complex object. In the HTML example, these parts are the title, heading, body, and the footer of the page. The director controls the building process using a builder instance. The HTML example means for calling the builder's functions for setting the title, the heading, and so on. Using a different builder instance allows us to create a different HTML page without touching any code of the director. 14 | 15 | HTML页面生成器的问题可以使用构造器模式来解决。在这个模式中有两个主要的参与元素:构造器和经手人。构造器负责创建负责对象的多个部分。在HTML示例中,这些部分为主题,头部,主体,以及页面脚注。经手人使用构造器实例控制构建过程。HTML的例子中的部分为,标题,头部,主体,以及页面注脚。管理者使用一个构造器实例来控制构建过程。HTML的例子意味着要调用构造器的函数来设置标题,头部,等等。使用不同的构造器实例可以让我们不接触任何管理器的代码便可以创建不同的HTML页面。 16 | 17 | ## 真实的例子 18 | The Builder design pattern is used in fast-food restaurants. The same procedure is always used to prepare a burger and the packaging (box and paper bag), even if there are many different kinds of burgers (classic, cheeseburger, and more) and different packages (small-sized box, medium-sized box, and so forth). The difference between a classic burger and a cheeseburger is in the representation, and not in the construction procedure. The director is the cashier who gives instructions about what needs to be prepared to the crew, and the builder is the person from the crew that takes care of the specific order. The following figure provided by *www. sourcemaking.com* shows a **Unified Modeling Language (UML)** sequence diagram of the communication that takes place between the customer (client), the cashier (director), and the crew (builder) when a kid's menu is ordered [j.mp/builderpat]. 19 | 20 | 构造器模式被用在了快餐店上。相同的步骤一直用在了准备汉堡和打包上(盒子、纸袋),即使存在不同类型的汉堡(原味,起司,等等)以及不同的包装(小号的盒子,中号的盒子,等等)原味汉堡和起司汉堡之间不同在于外观,而不是制造过程上。管理器是给全体工作人员发出需要准备什么食材的收银员,构造器是员工之中负责特殊订单的人。下面是由 *www. sourcemaking.com* 提供的 **Unified Modeling Language (UML)** 图片,它展示了当产生一份儿童餐订单时,一张放置于消费者(用户),收银员(管理器),和员工(构造器)之间的沟通顺序图解。 21 | 22 | ![img](images/chapter2_1.png) 23 | 24 | ## 与软件相关的例子 25 | The HTML example that was mentioned at the beginning of the chapter is actually used by **django-widgy**, a third-party tree editor for Django that can be used as a **Content Management System (CMS)**. The django-widgy editor contains a page builder that can be used for creating HTML pages with different layouts [j.mp/widgypb]. 26 | 27 | 本章开始提到的HTML例子实际上是使用的是django-widgy,这是一个第三方的Django树形编辑器包,它能够当作CMS来使用。django-widgy包含了一个构造器,它能够用来创建不同布局的HTML页面。 28 | 29 | The **django-query-builder** library is another third-party Django library that relies on the Builder pattern. The django-query-builder library can be used for building SQL queries dynamically. Using this, we can control all aspects of a query and create a different range of queries, from simple to very complex [j.mp/djangowidgy]. 30 | 31 | django-query-builder库是另外一个第三方的Django库,它依赖于构造器模式。django-query-builder库用来构建动态SQL查询。使用这个库,我们可以控制查询和创建一组不同查询的方方面面,从简单的到复杂都可以。 32 | 33 | ## 使用案例 34 | We use the Builder pattern when we know that an object must be created in multiple steps, and different representations of the same construction are required. These requirements exist in many applications such as page generators (like the HTML page generator mentioned in this chapter), document converters [GOF95, page 110], and User Interface (UI) form creators [j.mp/pipbuild]. 35 | 36 | 在我们知道对象必须用到多个步骤才能创建,以及需要相同构造的不同表现时才会使用构造器模式。这些需求存在于很多的应用中,比如页面生成器(类似于本章提到的页面生成器),文档转换器,以及用户界面(UI)的表单创建器。 37 | 38 | Some resources mention that the Builder pattern can also be used as a solution to the telescopic constructor problem [j.mp/wikibuilder]. The telescopic constructor problem occurs when we are forced to create a new constructor for supporting different ways of creating an object. The problem is that we end up with many constructors and long parameter lists, which are hard to manage. An example of the telescopic constructor is listed at the stackoverflow website [j.mp/sobuilder]. Fortunately, this problem does not exist in Python, because it can be solved in at least two ways: 39 | 40 | 有些资料提到构造器模式也可以作为伸缩构造器问题的一个解决方案。当我们被迫创建一个新的构造器以支持一个对象的不同创建方式,便会出现伸缩构造器问题。其问题在于,在终结时我们还有很多的构造器和长参数列表,这将难以管理。伸缩构造器的例子被列在了stackoveflow网站。幸运的是,在Python这个问题并不存在,因为它是至少可以使用两种方法来解决: 41 | 42 | - With named parameters [j.mp/sobuipython] 43 | - With argument list unpacking [j.mp/arglistpy] 44 | 45 | - 使用命名参数 46 | - 使用参数列表解包 47 | 48 | At this point, the distinction between the Builder pattern and the Factory pattern might not be very clear. The main difference is that a Factory pattern creates an object in a single step, whereas a Builder pattern creates an object in multiple steps, and almost always through the use of a director. Some targeted implementations of the Builder pattern like Java's **StringBuilder** bypass the use of a director, but that's the exception to the rule. 49 | 50 | 在这一阶段,构造器模式和工厂模式之间的区别并不非常明显。主要的区别在于工厂模式以单步形式创建一个对象,而构造器模式则以踱步形式创建一个对象,并且几乎都是通过管理者的使用来实现的。一些以构造器模式为主要目标的实现,比如Java的**StringBuilder** 就忽略了管理者的使用,不过这是个例外。 51 | 52 | Another difference is that while a Factory pattern returns a created object immediately, in the Builder pattern the client code explicitly asks the director 53 | to return the final object when it needs it [GOF95, page 113], [j.mp/builderpat]. 54 | 55 | 另外一个区别是工厂模式立即返回创建的对象,而在构造器模式中用户代码显式地向经理人请求,在有需要时返回最终的对象。 56 | 57 | The new computer analogy might help to distinguish between a Builder pattern and a Factory pattern. Assume that you want to buy a new computer. If you decide to buy a specific preconfigured computer model, for example, the latest Apple 1.4 GHz Mac mini, you use the Factory pattern. All the hardware specifications are already predefined by the manufacturer, who knows what to do without consulting you. The manufacturer typically receives just a single instruction. Code-wise, this would look like the following (*apple-factory.py*): 58 | 59 | 新计算机的分析有助于区别构造器模式和工厂模式。假设你需要买一台新电脑。如果你决定买预先配置好的电脑,例如最新的Apple 1.4Ghz Mac mini,你使用的是工厂模式。所有硬件规格都已经由厂商预定义了,厂家不用问你也知道该干什么。厂商通常只会收到一条指令。代码使人明了,其内容如下(*apple-factory.py*): 60 | 61 | ```python 62 | MINI14 = '1.4GHz Mac mini' 63 | 64 | 65 | class AppleFactory: 66 | class MacMini14: 67 | def __init__(self): 68 | self.memory = 4 # in gigabytes 使用吉字节表示 69 | self.hdd = 500 # in gigabytes 使用吉字节表示 70 | self.gpu = 'Intel HD Graphics 5000' 71 | 72 | def __str__(self): 73 | info = ('Model: {}'.format(MINI14), 74 | 'Memory: {}GB'.format(self.memory), 75 | 'Hard Disk: {}GB'.format(self.hdd), 76 | 'Graphics Card: {}'.format(self.gpu)) 77 | return '\n'.join(info) 78 | 79 | def build_computer(self, model): 80 | if (model == MINI14): 81 | return self.MacMini14() 82 | else: 83 | print("I don't know how to build {}".format(model)) 84 | 85 | if __name__ == '__main__': 86 | afac = AppleFactory() 87 | mac_mini = afac.build_computer(MINI14) 88 | print(mac_mini) 89 | ``` 90 | 91 | >#### 注释 92 | >Notice the nested *MacMini14 class*. This is a neat way of forbidding the direct instantiation of a class. 93 | >注意嵌套的*MacMini14 class*。 94 | 95 | Another option is buying a custom PC. In this case, you use the Builder pattern. You are the director that gives orders to the manufacturer (builder) about 96 | your ideal computer specifications. Code-wise, this looks like the following (*computer-builder.py*): 97 | 98 | 另外一个选择买一台定制的PC。在这个例子中,你可以使用构造器模式。你是给制造商(builder)下发个人定制电脑的经理人。关于代码,其内容如下(*computer-builder.py*): 99 | 100 | ```python 101 | class Computer: 102 | def __init__(self, serial_number): 103 | self.serial = serial_number 104 | self.memory = None # in gigabytes 使用吉字节表示 105 | self.hdd = None # in gigabytes 使用吉字节表示 106 | self.gpu = None 107 | def __str__(self): 108 | info = ('Memory: {}GB'.format(self.memory), 109 | 'Hard Disk: {}GB'.format(self.hdd), 110 | 'Graphics Card: {}'.format(self.gpu)) 111 | return '\n'.join(info) 112 | 113 | 114 | class ComputerBuilder: 115 | def __init__(self): 116 | self.computer = Computer('AG23385193') 117 | def configure_memory(self, amount): 118 | self.computer.memory = amount 119 | def configure_hdd(self, amount): 120 | self.computer.hdd = amount 121 | def configure_gpu(self, gpu_model): 122 | self.computer.gpu = gpu_model 123 | 124 | 125 | class HardwareEngineer: 126 | def __init__(self): 127 | self.builder = None 128 | 129 | def construct_computer(self, memory, hdd, gpu): 130 | self.builder = ComputerBuilder() 131 | [step for step in (self.builder.configure_memory(memory), 132 | self.builder.configure_hdd(hdd), 133 | self.builder.configure_gpu(gpu))] 134 | 135 | @property 136 | def computer(self): 137 | return self.builder.computer 138 | 139 | 140 | def main(): 141 | engineer = HardwareEngineer() 142 | engineer.construct_computer(hdd=500, memory=8, gpu='GeForce GTX 650 Ti') 143 | computer = engineer.computer 144 | print(computer) 145 | 146 | if __name__ == '__main__': 147 | main() 148 | ``` 149 | 150 | 151 | The basic changes are the introduction of a builder *ComputerBuilder*, a director *HardwareEngineer*, and the step-by-step construction of a computer, which now supports different configurations (notice that *memory*, *hdd*, and *gpu* are parameters and not preconfigured). What do we need to do if we want to support the construction of tablets? Implement this as an exercise. 152 | 153 | 基本的改变是构造器*ComputerBuilder*的命令,管理器*HardwareEngineer*,以及发具体命令的计算机,现在计算机可以支持不同的配置(注意参数memory,hdd,和gpu都未预先配置)。如果我们想要支持平板的质量该怎么做呢?你把实现这个目标当作练习。 154 | 155 | You might also want to change the computer *serial_number* into something that is different for each computer, because as it is now it means that all computers will have the same serial number (which is impractical). 156 | 157 | 你或许想过为每台电脑更改为不同的“序列号”,因为这样,现在就意味着所有的点都拥有相同的序列号了(这也是不切实际的)。 158 | 159 | ## Implementation 实现 160 | Let's see how we can use the Builder design pattern to make a pizza ordering application. The pizza example is particularly interesting because a pizza is prepared in steps that should follow a specific order. To add the sauce, you first need to prepare the dough. To add the topping, you first need to add the sauce. And you can't start baking the pizza unless both the sauce and the topping are placed on the dough. Moreover, each pizza usually requires a different baking time, depending 161 | on the thickness of its dough and the topping used. 162 | 163 | 让我们来看一看,我们该如何使用构造器模式来编写一个披萨下单应用。披萨示例非常有趣,因为披萨要按照指定的订单一步一步地准备。要添加酱料的话,首先你需要准备生面团。要添加浇头,首先你需要放酱料。除非你把酱料和浇头都放到生面团上面否则你是没办法烘培披萨的。此外,通常,视面饼的厚度和浇头的使用,每个披萨都需要的不同的烘培时间。 164 | 165 | We start with importing the required modules and declaring a few Enum parameters [j.mp/pytenum] plus a constant that are used many times in the application. The STEP_DELAY constant is used to add a time delay between the different steps of preparing a pizza (prepare the dough, add the sauce, and so on) as follows: 166 | 167 | 我们从导入所需的模块开始,然后声明了几个Enum参数[j.mp/pytenum],添加了在应用中多次应用的常量。如下, STEP_DELAY常量被用来在准备披萨(准备生面团,添加酱料,等等)的不同步骤中添加一个时间延迟: 168 | 169 | ```python 170 | from enum import Enum 171 | PizzaProgress = Enum('PizzaProgress', 'queued preparation baking ready') 172 | PizzaDough = Enum('PizzaDough', 'thin thick') 173 | PizzaSauce = Enum('PizzaSauce', 'tomato creme_fraiche') 174 | PizzaTopping = Enum('PizzaTopping', 'mozzarella double_mozzarell bacon ham mushrooms red_onion oregano') 175 | STEP_DELAY = 3 # in seconds for the sake of the example 176 | ``` 177 | 178 | Our end product is a *pizza*, which is described by the *Pizza* class. When using the Builder pattern, the end product does not have many responsibilities, since it is 179 | not supposed to be instantiated directly. A builder creates an instance of the end product and makes sure that it is properly prepared. That is why the *Pizza* class is so minimal. It basically initializes all data to sane default values. An exception is the *prepare_dough()* method. The *prepare_dough()* method is defined in the *Pizza* class instead of a builder for two reasons: 180 | 181 | 我们最终的产品是‘披萨’,它由*Pizza*类描述。在使用构造器模式时,终端产品并没有太多的责任,因为它不支持直接实例化。构造器创建终端产品的实例,以保证披萨正确备料了。这就是为什么*Pizza*类这么细小的原因。基本上这个类初始化所有数据,以唤醒默认值。处理异常的方法是*prepare_dough()*。*prepare_dough()*方法定义在了*Pizza*类而不是构造器中有两个理由: 182 | 183 | - To clarify the fact that the end product is typically minimal does not mean that you should never assign it any responsibilities 184 | - To promote code reuse through composition [GOF95, page 32] 185 | 186 | - 要说明的是,终端产品通常是最小化的,但是这并不意味着你从此就不能让它承担任何的职责 187 | - 通过使用合成来提升代码的复用性 188 | 189 | ```python 190 | class Pizza: 191 | def __init__(self, name): 192 | self.name = name 193 | self.dough = None 194 | self.sauce = None 195 | self.topping = [] 196 | 197 | def __str__(self): 198 | return self.name 199 | 200 | def prepare_dough(self, dough): 201 | self.dough = dough 202 | print('preparing the {} dough of your {}...'.format(self.dough.name, self)) 203 | time.sleep(STEP_DELAY) 204 | print('done with the {} dough'.format(self.dough.name)) 205 | 206 | ``` 207 | 208 | There are two builders: one for creating a margarita pizza (*MargaritaBuilder*) and another for creating a creamy bacon pizza (*CreamyBaconBuilder*). Each builder creates a Pizza instance and contains methods that follow the pizza-making procedure: *prepare_dough()*, *add_sauce()*, *add_topping()*, *and bake()*. To be precise, *prepare_dough()* is just a wrapper to the *prepare_dough()* method of the *Pizza* class. Notice how each builder takes care of all the pizza-specific details. For example, the topping of the margarita pizza is *double mozzarella* and *oregano*, while the topping of the creamy bacon pizza is *mozzarella*, *bacon*, *ham*, *mushrooms*, *red onion*, and *oregano* as follows: 209 | 210 | 有两个构造器:一个用来创建玛格丽塔披萨(*MargaritaBuilder*),另外一个用来创建奶油培根披萨(*CreamyBaconBuilder*)。每个构造器创建一个Pizza实例,以及包含了遵循披萨制作流程的方法:*prepare_dough()*, *add_sauce()*, *add_topping()*, *and bake()*。准确地来说,*prepare_dough()*只是一个应用于*Pizza*类的方法*prepare_dough()*的包装器。请注意每个构造器是如何处理定制披萨的细节。例如,马格丽特披萨的浇头是‘双份白干奶酪’和‘牛至叶’,而奶油培根披萨的浇头则是‘白干奶酪’,‘培根’,‘火腿’,‘蘑菇’,‘紫皮洋葱’,和‘牛至叶’,一如下面所示: 211 | 212 | ```python 213 | class MargaritaBuilder: 214 | def __init__(self): 215 | self.pizza = Pizza('margarita') 216 | self.progress = PizzaProgress.queued 217 | self.baking_time = 5 # in seconds for the sake of the example 218 | # 因为考虑到示例所以这里使用了秒 219 | def prepare_dough(self): 220 | self.progress = PizzaProgress.preparation 221 | self.pizza.prepare_dough(PizzaDough.thin) 222 | def add_sauce(self): 223 | print('adding the tomato sauce to your margarita...') 224 | self.pizza.sauce = PizzaSauce.tomato 225 | time.sleep(STEP_DELAY) 226 | print('done with the tomato sauce') 227 | def add_topping(self): 228 | print('adding the topping (double mozzarella, oregano) to your margarita') 229 | self.pizza.topping.append([i for i in 230 | (PizzaTopping.double_mozzarella, PizzaTopping.oregano)]) 231 | time.sleep(STEP_DELAY) 232 | print('done with the topping (double mozzarella, oregano)') 233 | def bake(self): 234 | self.progress = PizzaProgress.baking 235 | print('baking your margarita for {} seconds'.format(self.baking_time)) 236 | time.sleep(self.baking_time) 237 | self.progress = PizzaProgress.ready 238 | print('your margarita is ready') 239 | 240 | 241 | class CreamyBaconBuilder: 242 | def __init__(self): 243 | self.pizza = Pizza('creamy bacon') 244 | self.progress = PizzaProgress.queued 245 | self.baking_time = 7 # in seconds for the sake of the example 246 | def prepare_dough(self): 247 | self.progress = PizzaProgress.preparation 248 | self.pizza.prepare_dough(PizzaDough.thick) 249 | def add_sauce(self): 250 | print('adding the crème fraîche sauce to your creamy bacon') 251 | self.pizza.sauce = PizzaSauce.creme_fraiche 252 | time.sleep(STEP_DELAY) 253 | print('done with the crème fraîche sauce') 254 | def add_topping(self): 255 | print('adding the topping (mozzarella, bacon, ham, mushrooms, red onion, oregano) to your creamy bacon') 256 | self.pizza.topping.append([t for t in 257 | (PizzaTopping.mozzarella, PizzaTopping.bacon, 258 | PizzaTopping.ham,PizzaTopping.mushrooms, 259 | PizzaTopping.red_onion, PizzaTopping.oregano)]) 260 | time.sleep(STEP_DELAY) 261 | print('done with the topping (mozzarella, bacon, ham, mushrooms, red onion, oregano)') 262 | def bake(self): 263 | self.progress = PizzaProgress.baking 264 | print('baking your creamy bacon for {} seconds'.format(self.baking_time)) 265 | time.sleep(self.baking_time) 266 | self.progress = PizzaProgress.ready 267 | print('your creamy bacon is ready') 268 | ``` 269 | 270 | The director in this example is the waiter. The core of the *Waiter* class is the *construct_pizza()* method, which accepts a builder as a parameter and executes all the pizza preparation steps in the right order. Choosing the appropriate builder, which can even be done in runtime, gives us the ability to create different pizza styles without modifying any code of the director (*Waiter*). The Waiter class also contains the *pizza()* method, which returns the end product (prepared pizza) as a variable to the caller as follows: 271 | 272 | 这个例子中的管理器是服务员。*Waiter*类的核心是*construct_pizza()*方法,这个方法接受一个构造器并作为参数,然后按照正确的顺序执行所有的披萨预备工作。选择合适的构造器,甚至可以在运行时实现,构造器给予我们能够不用修改管理器(Waiter)的任何代码就可以常见不同风味的披萨。 273 | 274 | ```python 275 | class Waiter: 276 | def __init__(self): 277 | self.builder = None 278 | def construct_pizza(self, builder): 279 | self.builder = builder 280 | [step() for step in (builder.prepare_dough, 281 | builder.add_sauce, builder.add_topping, builder.bake)] 282 | @property 283 | def pizza(self): 284 | return self.builder.pizza 285 | ``` 286 | 287 | 288 | The *validate_style()* function is similar to the *validate_age()* function as described in Chapter 1, The Factory Pattern. It is used to make sure that the user gives valid input, which in this case is a character that is mapped to a pizza builder. The m character uses the *MargaritaBuilder* class and the c character uses the *CreamyBaconBuilder* class. These mappings are in the builder parameter. A tuple is returned, with the first element set to *True* if the input is valid, or *False* if it is invalid as follows: 289 | 290 | *validate_style()*函数类似于在第一章-工厂模式中描述过的*validate_age()*函数。它用来保证用户给出有效的输入,而在这个例子它是一个映射到披萨供应商的字符。字符m使用的*MargaritaBuilder*类,字符c使用*CreamyBaconBuilder*类。 291 | 292 | ```python 293 | def validate_style(builders): 294 | try: 295 | pizza_style = input('What pizza would you like,[m]argarita or [c]reamy bacon? ') 296 | builder = builders[pizza_style]() 297 | valid_input = True 298 | except KeyError as err: 299 | print('Sorry, only margarita (key m) and creamy bacon (key c) are available') 300 | return (False, None) 301 | return (True, builder) 302 | ``` 303 | 304 | 305 | The last part is the *main()* function. The main() function contains a code for instantiating a pizza builder. The pizza builder is then used by the Waiter director for preparing the pizza. The created pizza can be delivered to the client at any later point: 306 | 307 | 最后部分是*main()*函数。main()函数包含了初始化披萨构造器的代码。披萨构造器之后被Waiter经理人用来预备披萨。创建的披萨可以在之后的任意时刻送达用户: 308 | 309 | ```python 310 | def main(): 311 | builders = dict(m=MargaritaBuilder, c=CreamyBaconBuilder) 312 | valid_input = False 313 | while not valid_input: 314 | valid_input, builder = validate_style(builders) 315 | print() 316 | waiter = Waiter() 317 | waiter.construct_pizza(builder) 318 | pizza = waiter.pizza 319 | print() 320 | print('Enjoy your {}!'.format(pizza)) 321 | ``` 322 | 323 | To put all these things together, here's the complete code of this example (*builder.py*): 324 | 325 | 将所有代码放在一起,下面是该例子(*builder.py*)的完整代码: 326 | 327 | ```python 328 | from enum import Enum 329 | 330 | 331 | import time 332 | PizzaProgress = Enum('PizzaProgress', 'queued preparation baking ready') 333 | PizzaDough = Enum('PizzaDough', 'thin thick') 334 | PizzaSauce = Enum('PizzaSauce', 'tomato creme_fraiche') 335 | PizzaTopping = Enum('PizzaTopping', 'mozzarella double_mozzarella bacon ham mushrooms red_onion oregano') 336 | 337 | STEP_DELAY = 3 338 | 339 | 340 | class Pizza: 341 | def __init__(self, name): 342 | self.name = name 343 | self.dough = None 344 | self.sauce = None 345 | self.topping = [] 346 | 347 | def __str__(self): 348 | return self.name 349 | 350 | # in seconds for the sake of the 351 | def prepare_dough(self, dough): 352 | self.dough = dough 353 | print('preparing the {} dough of your {}...'.format(self.dough.name, self)) 354 | time.sleep(STEP_DELAY) 355 | print('done with the {} dough'.format(self.dough.name)) 356 | 357 | 358 | class MargaritaBuilder: 359 | def __init__(self): 360 | self.pizza = Pizza('margarita') 361 | self.progress = PizzaProgress.queued 362 | self.baking_time = 5 # in seconds for the sake of the 363 | 364 | def prepare_dough(self): 365 | self.progress = PizzaProgress.preparation 366 | self.pizza.prepare_dough(PizzaDough.thin) 367 | 368 | def add_sauce(self): 369 | print('adding the tomato sauce to your margarita...') 370 | self.pizza.sauce = PizzaSauce.tomato 371 | time.sleep(STEP_DELAY) 372 | print('done with the tomato sauce') 373 | 374 | def add_topping(self): 375 | print('adding the topping (double mozzarella, oregano) to your margarita') 376 | self.pizza.topping.append([i for i in 377 | (PizzaTopping.double_mozzarella, PizzaTopping.oregano)]) 378 | time.sleep(STEP_DELAY) 379 | print('done with the topping (double mozzarrella, oregano)') 380 | 381 | def prepare_dough(self): 382 | self.progress = PizzaProgress.preparation 383 | self.pizza.prepare_dough(PizzaDough.thin) 384 | 385 | def add_sauce(self): 386 | print('adding the tomato sauce to your margarita...') 387 | self.pizza.sauce = PizzaSauce.tomato 388 | time.sleep(STEP_DELAY) 389 | print('done with the tomato sauce') 390 | 391 | def add_topping(self): 392 | print('adding the topping (double mozzarella, oregano) to your margarita') 393 | self.pizza.topping.append([i for i in 394 | (PizzaTopping.double_mozzarella, PizzaTopping.oregano)]) 395 | time.sleep(STEP_DELAY) 396 | print('done with the topping (double mozzarrella, oregano)') 397 | 398 | 399 | class CreamyBaconBuilder: 400 | def __init__(self): 401 | self.pizza = Pizza('creamy bacon') 402 | self.progress = PizzaProgress.queued 403 | self.baking_time = 7 # in seconds for the sake of the 404 | 405 | def prepare_dough(self): 406 | self.progress = PizzaProgress.preparation 407 | self.pizza.prepare_dough(PizzaDough.thick) 408 | 409 | def add_sauce(self): 410 | print('adding the crème fraîche sauce to your creamy bacon') 411 | self.pizza.sauce = PizzaSauce.creme_fraiche 412 | time.sleep(STEP_DELAY) 413 | print('done with the crème fraîche sauce') 414 | 415 | def add_topping(self): 416 | print('adding the topping (mozzarella, bacon, ham, mushrooms, red onion, oregano) to your creamy bacon') 417 | self.pizza.topping.append([t for t in 418 | (PizzaTopping.mozzarella, PizzaTopping.bacon, 419 | PizzaTopping.ham,PizzaTopping.mushrooms, 420 | PizzaTopping.red_onion, PizzaTopping.oregano)]) 421 | time.sleep(STEP_DELAY) 422 | print('done with the topping (mozzarella, bacon, ham, mushrooms, red onion, oregano)') 423 | 424 | def bake(self): 425 | self.progress = PizzaProgress.baking 426 | print('baking your creamy bacon for {} seconds'.format(self.baking_time)) 427 | time.sleep(self.baking_time) 428 | self.progress = PizzaProgress.ready 429 | print('your creamy bacon is ready') 430 | 431 | 432 | class Waiter: 433 | def __init__(self): 434 | self.builder = None 435 | def construct_pizza(self, builder): 436 | self.builder = builder 437 | [step() for step in (builder.prepare_dough, 438 | builder.add_sauce, builder.add_topping, builder.bake)] 439 | @property 440 | def pizza(self): 441 | return self.builder.pizza 442 | def validate_style(builders): 443 | try: 444 | pizza_style = input('What pizza would you like,[m]argarita or [c]reamy bacon? ') 445 | builder = builders[pizza_style]() 446 | valid_input = True 447 | except KeyError as err: 448 | print('Sorry, only margarita (key m) and creamy bacon (key c) are available') 449 | return (False, None) 450 | return (True, builder) 451 | 452 | 453 | def main(): 454 | builders = dict(m=MargaritaBuilder, c=CreamyBaconBuilder) 455 | valid_input = False 456 | while not valid_input: 457 | valid_input, builder = validate_style(builders) 458 | print() 459 | waiter = Waiter() 460 | waiter.construct_pizza(builder) 461 | pizza = waiter.pizza 462 | print() 463 | print('Enjoy your {}!'.format(pizza)) 464 | if __name__ == '__main__': 465 | main() 466 | ``` 467 | 468 | A sample output of this example is as follows: 469 | 470 | 这个例子的输出如下: 471 | 472 | ```python 473 | >>> python3 builder.py 474 | What pizza would you like, [m]argarita or [c]reamy bacon? r 475 | Sorry, only margarita (key m) and creamy bacon (key c) are available 476 | What pizza would you like, [m]argarita or [c]reamy bacon? m 477 | preparing the thin dough of your margarita... 478 | done with the thin dough 479 | adding the tomato sauce to your margarita... 480 | done with the tomato sauce 481 | adding the topping (double mozzarella, oregano) to your margarita 482 | done with the topping (double mozzarella, oregano) 483 | baking your margarita for 5 seconds 484 | your margarita is ready 485 | Enjoy your margarita! 486 | ``` 487 | 488 | Supporting only two pizza types is a shame. Implement a Hawaiian pizza builder. Consider using inheritance after thinking about the advantages and disadvantages. Check the ingredients of a typical Hawaiian pizza and decide which class you need to extend: *MargaritaBuilder* or *CreamyBaconBuilder*? Perhaps both [j.mp/pymulti]? 489 | 490 | 只能支持两种披萨类型是种耻辱。我们要实现一个夏威夷披萨生产商。在考虑了优缺点之后,我们决定使用继承。 491 | 492 | In the book, Effective Java (2nd edition), Joshua Bloch describes an interesting variation of the Builder pattern where calls to builder methods are chained. This is accomplished by defining the builder itself as an inner class and returning itself from each of the setter-like methods on it. The *build()* method returns the final object. This pattern is called the Fluent Builder. Here's a Python implementation, which 493 | was kindly provided by a reviewer of the book: 494 | 495 | 在高效Java(第二版)这本书中,作者Joshua Bloch描述了 496 | 497 | ```python 498 | class Pizza: 499 | def __init__(self, builder): 500 | self.garlic = builder.garlic 501 | self.extra_cheese = builder.extra_cheese 502 | def __str__(self): 503 | garlic = 'yes' if self.garlic else 'no' 504 | cheese = 'yes' if self.extra_cheese else 'no' 505 | info = ('Garlic: {}'.format(garlic), 506 | 'Extra cheese: {}'.format(cheese)) 507 | return '\n'.join(info) 508 | class PizzaBuilder: 509 | def __init__(self): 510 | self.extra_cheese = False 511 | self.garlic = False 512 | def add_garlic(self): 513 | self.garlic = True 514 | return self 515 | def add_extra_cheese(self): 516 | self.extra_cheese = True 517 | return self 518 | def build(self): 519 | return Pizza(self) 520 | if __name__ == '__main__': 521 | pizza = 522 | Pizza.PizzaBuilder().add_garlic().add_extra_cheese().build() 523 | print(pizza) 524 | ``` 525 | 526 | Adapt the pizza example to make use of the Fluent Builder pattern. Which version of the two do you prefer? What are the pros and cons of each version? 527 | 528 | ## 总结 529 | In this chapter, we have seen how to use the Builder design pattern. We use the Builder pattern for creating an object in situations where using the Factory pattern (either a Factory Method or an Abstract Factory) is not a good option. A Builder pattern is usually a better candidate than a Factory pattern when: 530 | 531 | - We want to create a complex object (an object composed of many parts and created in different steps that might need to follow a specific order). 532 | - Different representations of an object are required, and we want to keep the construction of an object decoupled from its representation 533 | - We want to create an object at one point in time but access it at a later point 534 | 535 | - 我们想创建一个复杂的对象(对象由很多部分合成,而且按照不同的步骤创建,这就需要遵循一个特定的顺序)。 536 | - 537 | - 538 | 539 | We saw how the Builder pattern is used in fast-food restaurants for preparing meals, and how two third-party Django packages, django-widgy and django-query- builder, use it for generating HTML pages and dynamic SQL queries, respectively. We focused on the differences between a Builder pattern and a Factory pattern, and gave a preconfigured (Factory) versus customer (Builder) computer order analogy to clarify them. 540 | 541 | 我们见过了在快餐店如何使用构造器来为备餐服务,以及两个第三方店Django,django-widgy 和 django-query- builder如何分别地被用来生成HTML页面和动态的SQL查询。我们关注了构造器模式和工厂模式之间的区别, 542 | 543 | In the implementation part, we have seen how to create a pizza ordering application, which has preparation dependencies. There are many recommended interesting exercises in this chapter, including implementing a Fluent Builder. 544 | 545 | 在实现部分,我们见到了如何创建一个披萨订餐应用,这个应用有一些前置以来。在这一章,推荐了很多有趣的联系,其中就包括实现一个流体构造器。 546 | 547 | In the next chapter, you will learn about the last creational design pattern covered in this book: the Prototype pattern, which is used for cloning an object. 548 | 549 | 在下一章,你会学习到 550 | 551 | 552 | -------------------------------------------------------------------------------- /第五章-装饰器模式.md: -------------------------------------------------------------------------------- 1 | 第五章-装饰器模式 2 | ************** 3 | 4 | Whenever we want to add extra functionality to an object, we have a number of different options. We can: 5 | 6 | 任何时候,只要我们想给一个对象添加附加的功能,就有多个不同的选项供我们选择。我们可以选择: 7 | 8 | - Add the functionality directly to the class the object belongs to, if it makes sense (for example, add a new method) 9 | - Use composition 10 | - Use inheritance 11 | 12 | - 直接对类的对象添加功能,如果可行的话(例如,添加一个新方法) 13 | - 合成 14 | - 继承 15 | 16 | Composition should generally be preferred over inheritance, because inheritance makes code reuse harder, it's static, and applies to an entire class and all instances of it [GOF95, page 31], [j.mp/decopat]. 17 | 18 | 通常对合成的选择应该优先于继承,因为继承会使代码很难重复使用,而且继承是静态的,它会被应用到整个类,以及类的全部实例。 19 | 20 | Design patterns offer us a fourth option that supports extending the functionality of an object dynamically (in runtime): Decorators. A Decorator pattern can add responsibilities to an object dynamically, and in a transparent manner (without affecting other objects) [GOF95, page 196]. 21 | 22 | 设计模式给我们动态地(运行时)提供了扩展一个对象的第四个选择:装饰器。装饰器模式能够动态地给一个对象添加信任,而且是使用透明模式(不会影响到其他的对象)。 23 | 24 | In many programming languages, the Decorator pattern is implemented using sub-classing (inheritance) [GOF95, page 198]. In Python, we can (and should) use the built-in decorator feature. A Python decorator is a specific change to the syntax of Python that is used for extending the behavior of a class, method, or function without using inheritance. In terms of implementation, a Python decorator is a callable (function, method, class) that accepts a function object fin as input, and returns another function object fout [j.mp/conqdec]. This means that any callable that has these properties can be treated as a decorator. We have already seen how to use the built-in property decorator that makes a method appear as a variable in Chapter 1, The Factory Pattern and Chapter 2, The Builder Pattern. In the implementation section, we will learn how to implement and use our own decorators. 25 | 26 | 在很多的编程语言中,装饰器模式是通过使用子类化(继承)实现的[GOF95,198页]。在Python中我们可以(而且应该)使用内建的装饰器功能。Python装饰器是专门用来改变Python语法的,它用来扩展一个类的行为,方法,或者函数不需要用到继承。就实现的术语观点来看,Python装饰器是一个可调用的对象(函数、方法、类)能够接受函数对象作为输入,并返回另外一个不同的函数。这就意味着任何一个拥有这些特性的可调用对象都可以被当作装饰器。我们已经在第一章《工厂模式》和第二章《构造器模式》见过了如何使用内建的特性装饰器将一个方法以变量的形式出现。在具体实现部分,我们会学习到如何实现并利用装饰器。 27 | 28 | There is no one-to-one relationship between the Decorator pattern and Python decorators. Python decorators can actually do much more than the Decorator pattern. One of the things they can be used for, is to implement the Decorator pattern [Eckel08, page 59], [j.mp/moinpydec]. 29 | 30 | Python的装饰器和装饰器模式之间不存在一对一的关系。Python装饰器实际上能做的事情比装饰器模式多得多。使用Python装饰器的其中一个目的就是实现装饰器模式[Eckel08, 59页]。 31 | 32 | ## 真实事例 33 | The fact that the pattern is called Decorator does not mean that it should be used only for making things look prettier. The Decorator pattern is generally used for extending the functionality of an object. Real examples of such extensions are: adding a silencer to a gun, using different camera lenses (in cameras with removable lenses), and so on. 34 | 35 | 模式被称做装饰器实际上并不意味着它只应该让事情更好些。通常装饰器模式用来扩展一个对象的功能。这样的扩展的真实例子是:给一把枪添加一个消音器,使用不同的相机镜头(可移除的镜头),等等。 36 | 37 | The following gure, provided by sourcemaking.com, shows how we can decorate a gun with special accessories to make it silent, more accurate, and devastating [j.mp/ decopat]. Note that the gure uses sub-classing, but in Python, this is not necessary because we can use the built-in decorator feature of the language. 38 | 39 | 由`sourcemaking.com`提供的下图,展示了我们如何利用特殊的部件来`装饰`一把枪以使枪消音,更加精确,具有杀伤性。注意此图使用的是子类化,不过在Python中,这不是必须的,因为我们可以利用该语言的内建装饰器功能。 40 | 41 | ![img](images/c5_1.png) 42 | 43 | ## 软件示例 44 | The Django framework uses decorators to a great extent. An example is the View decorator. Django's View decorators can be used for [j.mp/djangodec]: 45 | 46 | Django框架使用装饰器来实现良好的扩展。例子就是视图装饰器。Django的`视图`装饰器可以用于: 47 | 48 | - Restricting access to views based on the HTTP request 49 | - Controlling the caching behavior on speci c views 50 | - Controlling compression on a per-view basis 51 | - Controlling caching based on speci c HTTP request headers 52 | 53 | - 限制基于HTTP请求的视图 54 | - 控制特定视图的缓存行为 55 | - 控制每个视图基础的压缩 56 | - 控制依据特定HTTP请求偷渡的缓存 57 | 58 | The Grok framework also uses decorators for achieving different goals such as [j.mp/grokdeco]: 59 | 60 | Grok框架也使用装饰器来实现不同的目标: 61 | 62 | - Registering a function as an event subscriber 63 | - Protecting a method with a speci c permission 64 | - Implementing the Adapter pattern 65 | 66 | - 函数注册为一个时间订阅器 67 | - 对方法应用一个特定的权限 68 | - 实现适配器模式 69 | 70 | ## 使用案例 71 | The Decorator pattern shines when used for implementing cross-cutting concerns [Lott14, page 223], [j.mp/wikicrosscut]. Examples of cross-cutting concerns are: 72 | 73 | 装饰器模式用于实现`横切关注`时特别出色。横切关注的例子有: 74 | 75 | - Data validation 76 | - Transaction processing (A transaction in this case is similar to a database transaction, in the sense that either all steps should be completed successfully, or the transaction should fail.) 77 | - Caching 78 | - Logging 79 | - Monitoring 80 | - Debugging 81 | - Business rules 82 | - Compression 83 | - Encryption 84 | 85 | - 数据验证 86 | - 事物处理(这种场景下的事物类似于数据库事物,感觉上所有的步骤要么都应该是成功,要么事物应该失败。) 87 | - 缓存 88 | - 登录 89 | - 监控 90 | - 调试 91 | - 业务规则 92 | - 压缩 93 | - 加密 94 | 95 | In general, all parts of an application that are generic and can be applied to many other parts of it, are considered cross-cutting concerns. 96 | 97 | 通常来说,一个应用的所有部分都是通用的,而且能够被运用于该应用的其它部分,就被认为是关注切面的。 98 | 99 | Another popular example of using the Decorator pattern is Graphical User Interface (GUI) toolkits. In a GUI toolkit, we want to be able to add features such as borders, shadows, colors, and scrolling to individual components/widgets [GOF95, page 196]. 100 | 101 | 另外一个流行的使用装饰器模式的例子是图形化用户界面(GUI)工具套件。在GUI工具套件中,我们想要能够独立的对组件/部件添加诸如,边框,阴影,色彩,以及滚动。 102 | 103 | ## 实现 104 | Python decorators are generic and very powerful. You can nd many examples of how they can be used at the decorator library of python.org [j.mp/pydeclib]. In this section, we will see how we can implement a memoization decorator [j.mp/ memoi]. All recursive functions can bene t from memoization, so let's pick the popular Fibonacci sequence example. Implementing the recursive algorithm of Fibonacci is straight forward, but it has major performance issues, even for small values. First, let's see the naive implementation ( le fibonacci_naive.py). 105 | 106 | Python装饰器通用而且非常强大。你可以在python.org上的装饰器库找到很多如何使用的例子。在本节,我们会看到如何如何实现一个记忆器装饰器。所有的递归函数都可以从记忆器中获益,所以让我们选择非常流行的斐波那契序列示例。实现斐波那契的递归算法简单明了,但是存在重大的性能问题,即便是对于很小的值。首先,让我们来看一看原始实现(文件fibonacci_naive.py)。 107 | 108 | ```python 109 | def fibonacci(n): 110 | assert(n >= 0), 'n must be >= 0' 111 | return n if n in (0, 1) else fibonacci(n-1) + fibonacci(n-2) 112 | if __name__ == '__main__': 113 | from timeit import Timer 114 | t = Timer('fibonacci(8)', 'from __main__ import fibonacci') 115 | print(t.timeit()) 116 | ``` 117 | 118 | A sample execution of this example shows how slow this implementation is. It takes 17 seconds to calculate the eighth Fibonacci number. The same execution gives the following output: 119 | 120 | 该例子的执行显示了此种实现是如何的慢。它花了17秒计算8个斐波那契数。相同的执行给出如下输出: 121 | 122 | ```shell 123 | >>> python3 fibonacci_naive.py 124 | 16.669270177000726 125 | ``` 126 | 127 | Let's use memoization to see if it helps. In the following code, we use a dict for caching the already computed values of the Fibonacci sequence. We also change the parameter passed to the fibonacci() function. We want to calculate the hundredth Fibonacci number instead of the eighth. 128 | 129 | 我们使用记忆器来看看是否有些帮助。在下面的代码中,我们使用字典缓存斐波那契序列已经计算过的值。我们还改变了传递到fibonacci()函数的参数。我们想要计算上百个斐波那契数而不是区区八个。 130 | 131 | ```python 132 | known = {0:0, 1:1} 133 | 134 | 135 | def fibonacci(n): 136 | assert(n >= 0), 'n must be >= 0' 137 | if n in known: 138 | return known[n] 139 | res = fibonacci(n-1) + fibonacci(n-2) 140 | known[n] = res 141 | return res 142 | 143 | if __name__ == '__main__': 144 | from timeit import Timer 145 | t = Timer('fibonacci(100)', 'from __main__ import fibonacci') print(t.timeit()) 146 | ``` 147 | 148 | Executing the memoization-based code shows that performance improves dramatically, and is acceptable even for computing large values. A sample execution is as follows: 149 | 150 | 执行基于记忆器的代码显示了性能戏剧性地提升了,而且甚至可以计算较大的值。样例执行如下: 151 | 152 | ```shell 153 | >>> python3 fibonacci.py 154 | 0.31532211999729043 155 | ``` 156 | 157 | But there are already a few problems with this approach. While the performance is not an issue any longer, the code is not as clean as it is when not using memoization. And what happens if we decide to extend the code with more math functions and turn it into a module? Let's assume that the next function we decide to add is nsum(), which returns the sum of the rst n numbers. Note that this function is already available in the math module as fsum(), but we can easily think of other functions that are not available in the standard library and would be useful for our module (for example Pascal's triangle, the sieve of Eratosthenes, and so on). The code of the nsum() function using memoization ( le mymath.py) is given as follows: 158 | 159 | 但这种方式存在已知的几个问题。当性能不再是问题时,代码没能够像未使用记忆器时那样简洁。如果我们决定用更多的math函数来扩展,并把它加入到模块中会发生什么呢?我们假设接下来的函数我们决定添加的是nsum(),它放了第一个n数字的总和。注意这个函数 160 | 161 | ```python 162 | known_sum = {0:0} 163 | def nsum(n): 164 | assert(n >= 0), 'n must be >= 0' 165 | if n in known_sum: 166 | return known_sum[n] 167 | res = n + nsum(n-1) 168 | known_sum[n] = res 169 | return res 170 | ``` 171 | 172 | Do you notice the problem already? We ended up with a new dict called known_sum which acts as our cache for nsum, and a function that is more complex than it would be without using memoization. Our module is becoming unnecessarily complex. Is it possible to keep the recursive functions as simple as the naive versions, but achieve a performance similar to the performance of the functions that use memoization? Fortunately, it is, and the solution is to use the Decorator pattern. 173 | 174 | 你注意到了么已知的问题么?我们以一个称作known_sum新字典结束,它作为nsum的缓存器,而且函数比起没有使用记忆器的更为复杂。我们的模块正在变的无必要的复杂。是否有可能让递归函数和它的原始版本一样的简单,而获得的性能类似于使用了记忆器函数的性能?幸运的是,答案是肯定的,解决方案是使用装饰器模式。 175 | 176 | First, we create a memoize() decorator as shown in the following example. Our decorator accepts the function fn that needs to be memoized, as an input. It uses a dict named known as the cache. The functools.wraps() function is a function that is used for convenience when creating decorators. It is not mandatory but a good practice to use since it makes sure that the documentation and the signature of the function that is decorated, are preserved [j.mp/funcwraps]. The argument list *args, is required in this case because the functions that we want to decorate accept input arguments. It would be redundant to use it if fibonacci() and nsum() didn't require any arguments, but they require n. 177 | 178 | 首先,我们创建展示在下面例子中的memoize()装饰器。我们的装饰器接受需要被记住的函数fn,以作为输入。它使用被称作cacha的字典。functools.wraps()函数是一个在创建装饰器时 179 | 180 | ```python 181 | import functools 182 | def memoize(fn): 183 | known = dict() 184 | @functools.wraps(fn) 185 | def memoizer(*args): 186 | if args not in known: 187 | known[args] = fn(*args) 188 | return known[args] 189 | return memoizer 190 | ``` 191 | 192 | Now, we can use our memoize() decorator with the naive version of our functions. This has the benefit of readable code without performance impacts. We apply a decorator using what is known as decoration (or decoration line). A decoration uses the @name syntax, where name is the name of the decorator that we want to use. It is nothing more than syntactic sugar for simplifying the usage of decorators. We can even bypass this syntax and execute our decorator manually, but that is left as an exercise for you. Let's see how the memoize() decorator is used with our recursive functions in the following example: 193 | 194 | 现在,我们可以对函数的原始版本使用memoize()函数了。这可以带来代码可阅读伤的好处,而不影响到性能。 195 | 196 | ```python 197 | @memoize 198 | def nsum(n): 199 | '''Returns the sum of the first n numbers''' 200 | assert(n >= 0), 'n must be >= 0' 201 | return 0 if n == 0 else n + nsum(n-1) 202 | 203 | 204 | @memoize 205 | def fibonacci(n): 206 | '''Returns the nth number of the Fibonacci sequence''' 207 | assert(n >= 0), 'n must be >= 0' 208 | return n if n in (0, 1) else fibonacci(n-1) + fibonacci(n-2) 209 | ``` 210 | 211 | The last part of the code shows how to use the decorated functions and measure their performance. measure is a list of dict used to avoid code repetition. Note how `__name__` and `__doc__` show the proper function names and documentation values, respectively. Try removing the @functools.wraps(fn) decoration from memoize(), and see if this is still the case: 212 | 213 | 代码的最后一部分展示了如何使用被装饰的函数来测量函数的性能。测量是一个避免代码重复的字典列表。注意,`__name__`和`__doc__`如何分别正确的显示函数名和文档的值。试着从memoize()移除@functools.wraps(fn)装饰器,看一看是否还是同样的结果: 214 | 215 | ```python 216 | if __name__ == '__main__': 217 | from timeit import Timer 218 | measure = [ {'exec':'fibonacci(100)', 'import':'fibonacci', 219 | 'func':fibonacci},{'exec':'nsum(200)', 'import':'nsum', 220 | 'func':nsum} ] 221 | for m in measure: 222 | t = Timer('{}'.format(m['exec']), 'from __main__ import {}'.format(m['import'])) 223 | print('name: {}, doc: {}, executing: {}, time: {}'.format(m['func'].__name__, m['func'].__doc__, 224 | m['exec'], t.timeit())) 225 | ``` 226 | 227 | Let's see the complete code of our math module ( le mymath.py) and a sample output when executing it. 228 | 229 | 我们来看看math模块的完整代码(文件mymath.py),以及在执行时的样例输出。 230 | 231 | ```python 232 | import functools 233 | 234 | 235 | def memoize(fn): 236 | known = dict() 237 | @functools.wraps(fn) 238 | def memoizer(*args): 239 | if args not in known: 240 | known[args] = fn(*args) 241 | return known[args] 242 | return memoizer 243 | 244 | 245 | @memoize 246 | def nsum(n): 247 | '''Returns the sum of the first n numbers''' 248 | assert(n >= 0), 'n must be >= 0' 249 | return 0 if n == 0 else n + nsum(n-1) 250 | 251 | 252 | @memoize 253 | def fibonacci(n): 254 | '''Returns the nth number of the Fibonacci sequence''' 255 | assert(n >= 0), 'n must be >= 0' 256 | return n if n in (0, 1) else fibonacci(n-1) + fibonacci(n-2) 257 | 258 | 259 | if __name__ == '__main__': 260 | from timeit import Timer 261 | measure = [ {'exec':'fibonacci(100)', 'import':'fibonacci', 262 | 'func':fibonacci}, {'exec':'nsum(200)', 'import':'nsum', 263 | 'func':nsum} ] 264 | for m in measure: 265 | t = Timer('{}'.format(m['exec']), 'from __main__ import{}'.format(m['import'])) 266 | print('name: {}, doc: {}, executing: {}, time: {}'.format(m['func'].__name__, m['func'].__doc__, 267 | m['exec'], t.timeit())) 268 | ``` 269 | 270 | Note that the execution times might differ in your case. 271 | 272 | 注意,执行时间可能和你的有所不同。 273 | 274 | ```shell 275 | >>> python3 mymath.py 276 | name: fibonacci, doc: Returns the nth number of the Fibonacci 277 | sequence, executing: fibonacci(100), time: 0.4169441329995607 278 | name: nsum, doc: Returns the sum of the first n numbers, 279 | executing: nsum(200), time: 0.4160157349997462 280 | ``` 281 | 282 | Nice. Readable code and acceptable performance. Now, you might argue that this is not the Decorator pattern, since we don't apply it in runtime. The truth is that a decorated function cannot be undecorated; but you can still decide in runtime if the decorator will be executed or not. That's an interesting exercise left for you. 283 | 284 | 非常好。可阅读的代码,加上可接受的性能。现在,你或许要同我争论,这不在是装饰器模式了,因为我们在运行时没有使用它。真相是被装饰器过的函数不能够被取消装饰;但在运行时你仍旧可以决定装饰器是否要执行。这就是留给你的一个有趣的练习。 285 | 286 | >#### Tips 287 | >Hint: Use a decorator that acts as a wrapper which decides whether or not the real decorator is executed based on some condition. 288 | 289 | >#### 提示 290 | >线索:使用装饰器作为包装器,取决于真实的装饰器是否是按条件执行的。 291 | 292 | Another interesting property of decorators that is not covered in this chapter is that, you can decorate a function with more than one decorator. So here's another exercise: create a decorator that helps you to debug recursive functions, and apply it on nsum() and fibonacci(). In what order are the multiple decorators executed? 293 | 294 | 装饰器的另外一个有趣的属性并不在本章讨论范围之内,即,你可以对一个函数应用不止一个装饰器。所以这里还有另外一个练习:创建一个帮助你调试递归函数的装饰器,并把它运用到nsum() 和 fibonacci()。其中多个装饰器的执行顺序是什么? 295 | 296 | If you have not had enough with decorators, I have one last exercise for you. The memoize() decorator does not work with functions that accept more than one argument. How can we verify that? After verifying it, try nding a way of fixing this issue. 297 | 298 | 如果你对装饰器了解的还不够多,我留给你最后一个练习。memoize()运用于函数时接受超过一个参数就不会工作。我们如何验证这种情况?在得到验证之后,你可以试找出一个修正这个问题的办法。 299 | 300 | ## 总结 301 | This chapter covered the Decorator pattern and its relation to the Python programming language. We use the Decorator pattern as a convenient way of extending the behavior of an object without using inheritance. Python extends the Decorator concept even more, by allowing us to extend the behavior of any callable (function, method, or class) without using inheritance or composition. We can use the built-in decorator feature of Python. 302 | 303 | 本章内容覆盖了装饰器模式以及它与Python编程语言的关系。我们使用装饰器模式来作为扩展一个对象的行为的很方便的方式,而不使用继承。Python更深入的扩展了装饰器,通过允许我们扩展任意可调用对象的行为(函数,方法,或者类),而不使用继承或者合成。我们使用Python内建装饰器的功能。 304 | 305 | We have seen a few examples of objects that are decorated in reality, like guns and cameras. From a software point of view, both Django and Grok use decorators for achieving different goals, such as controlling HTTP compression and caching. 306 | 307 | 我们已经见过了真实的几个被装饰对象的例子,比如,枪和照相机。从软件的角度来看,Django和Grok都适用装饰器来实现不同的目的,比如控制HTTP压缩和缓存。 308 | 309 | The Decorator pattern is a great solution for implementing cross-cutting concerns, because they are generic and do not fit well into the OOP paradigm. We mentioned many categories of cross-cutting concerns in the Use cases section. In fact, in the Implementation section a cross-cutting concern was demonstrated: memoization. We saw how decorators can help us to keep our functions clean, without sacrificing performance. 310 | 311 | 装饰器模式是实现关注切面的一个很好的解决方案,因为它们通用,而且并不很适合OOP范式。我们在使用案例小节提到了很多的关注切面的种类。实际上,在实现小节演示了一个关注切面的例子:记忆器。我们见到了装饰器如何帮助我们保证代码的整洁,而不用付出性能上的代价。 312 | 313 | The recommended exercises in this chapter can help you understand decorators 314 | even better, so that you can use this very powerful tool for solving many common (and perhaps less common) programming problems. The next chapter covers the Facade pattern, which is a convenient way of simplifying access to a complex system. 315 | 316 | 本章的建议练习能够帮助你更好地理解装饰器,所以你可以使用这个非常强大的工具来解决很多常见的(可能更少见的)编程问题。在下一章,我们要讨论Facade模式,它是一个简化对负责系统访问的便利方法。 317 | 318 | -------------------------------------------------------------------------------- /第八章-模型-视图-控制器模式.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /第十三章-观察者模式.md: -------------------------------------------------------------------------------- 1 | 第十三章-观察者模式 The Observer Pattern 2 | ************************************** 3 | Sometimes, we want to update a group of objects when the state of another 4 | object changes. A very popular example lies in the Model-View-Controller (MVC) pattern. Assume that we are using the data of the same model in two views, for instance in a pie chart and in a spreadsheet. Whenever the model is modified, 5 | both the views need to be updated. That's the role of the Observer design pattern [Eckel08, page 213]. 6 | 7 | 有时候,我们想要在另外一个对象改变时更新一组对象。一个非常流行的例子就是模型-视图-控制器(MVC)模式。假设,我们在两个视图中使用了相同模型的数据,比如,一个表格中的饼状图。不论模型是否被修改,这两个视图都需要更新。这正是观察者设计模式所扮演的角色。 8 | 9 | The Observer pattern describes a publish-subscribe relationship between a single object, : the publisher, which is also known as the subject or observable, and one or more objects, : the subscribers, also known as observers. In the MVC example, the publisher is the model and the subscribers are the views. However, MVC is not the only publish-subscribe example. Subscribing to a news feed such as RSS or Atom is another example. Many readers can subscribe to the feed typically using a feed reader, and every time a new item is added, they receive the update automatically. 10 | 11 | 观察者模式描述了单一对象之间的发布-订阅关系,发布者也称作主题或者可观察对象,并且拥有一个或者更多的对象,订阅者,也称作观察者。在MVC例子中,发布者为模型,订阅者为视图。不过,MVC并不是唯一的发布-订阅例子。订阅新闻聚合,比如RSS或者Atom就是另外一个例子了。很多读者通常使用资源阅读器订阅资源,每次添加新资源时,他们都可以自动地收到更新。 12 | 13 | The ideas behind Observer are the same as the ideas behind MVC and the separation of concerns principle, that is, to increase decoupling between the publisher and subscribers, and to make it easy to add/remove subscribers at runtime. Additionally, the publisher is not concerned about who its observers are. It just sends notifications to all the subscribers [GOF95, page 327]. 14 | 15 | 观察者背后的思想是和MVC背后的思想以及关注原则的分离是相同的,即,加强发布者和订阅者之间的耦合拆解,并在运行时可以很容易地添加/删除订阅者。此外,发布者并不关心自己的观察者是谁。它只会把通知发送给全部的订阅者。 16 | 17 | ## A real-life example 真实事例 18 | In reality, an auction resembles Observer. Every auction bidder has a number paddle that is raised whenever they want to place a bid. Whenever the paddle is raised by a bidder, the auctioneer acts as the subject by updating the price of the bid and broadcasting the new price to all bidders (subscribers). 19 | 20 | 在现实中,拍卖就很像观察者。每个拍卖投标者都拥一定数量的不论何时他们想要出价时要举起的报价牌。不论什么时候,报价牌被投标者举起时,拍卖主持人在更新投标价格时扮演者被监视对象的角色,同时会把新报价广播给全部的投标者(订阅者)。 21 | 22 | The following figure, courtesy of www.sourcemaking.com, [j.mp/observerpat], shows how the Observer pattern relates to an auction: 23 | 24 | 下图,由www.sourcemaking.com赞助,它展示了观察者模式是如何关联到拍卖上面的: 25 | 26 | ![img](images/c13_1.png) 27 | 28 | ## A software example 软件示例 29 | The django-observer package [j.mp/django-obs] is a third-party Django package that can be used to register callback functions that are executed when there are changes in several Django fields. Many different types of fields are supported (CharField, IntegerField, and so forth). 30 | 31 | django-observer包是一个第三方Django包,它用作在Django字段发生多处改变时注册被执行的回调函数。这个包支持很多不同类型的字段(CharField, IntegerField,等等)。 32 | 33 | RabbitMQ is a library that can be used to add asynchronous messaging support to an application. Several messaging protocols are supported, such as HTTP and AMQP. RabbitMQ can be used in a Python application to implement a publish-subscribe pattern, which is nothing more than the Observer design pattern [j.mp/rabbitmqobs]. 34 | 35 | RabbitMQ 是一个可以用来对一个应用添加异步消息支持的库。它支持多种消息协议,比如HTTP和AMQP。RabbitMQ用在Python应用中可以实现发布-订阅模式,比起观察者设计模式它什么也是不。 36 | 37 | ## Use cases 使用案例 38 | We generally use the Observer pattern when we want to inform/update one or more objects (observers/subscribers) about a change that happened to another object (subject/publisher/observable). The number of observers as well as who the observers are may vary and can be changed dynamically (at runtime). 39 | 40 | 我们一般在想要通知/更新一个或者更多对象有变更发生在了另外一个对象(主题/发布者/可订阅对象)身上。观察者的数量也随着观察者在改变,而且可以动态地改变(在运行时)。 41 | 42 | We can think of many cases where Observer can be useful. One such case was already mentioned at the start of this chapter: news feeds. Whether it is RSS, Atom, or another format, the idea is the same; you follow a feed, and every time it is updated, you receive a notification about the update [Zlobin13, page 60]. 43 | 44 | 我可以思考一下,在很多场合观察者都是非常有用的。其中一个此类场景在本章开始就已经提到了:新闻订阅。不论是RSS,Atom,或者其它格式,其思想都是一样的;你关注了订阅,每次订阅更新时,你就可以收到更新通知。 45 | 46 | The same concept exists in social networking. If you are connected to another person using a social networking service, and your connection updates something, you are notified about it. It doesn't matter if the connection is a Twitter user that you follow, a real friend on Facebook, or a business colleague on LinkedIn. 47 | 48 | 同样的概念也存在社交网络中。如果你使用社交网络服务连接到了另外一个人,只要你的连接对象有内容更新,那么你也会得到通知。如果的连接的人是推特用户,脸书上的真实朋友,或者LinkedIn上的业务伙伴。 49 | 50 | Event-driven systems are another example where Observer can be (and usually is) used. In such systems, listeners are used to "listen" for specific events. The listeners are triggered when an event they are listening to is created. This can be typing a specific key (of the keyboard), moving the mouse, and more. The event plays the role of the publisher and the listeners play the role of the observers. The key point in this case is that multiple listeners (observers) can be attached to a single event (publisher) [j.mp/magobs]. 51 | 52 | 时间驱动的系统是另外一种情况,这里观察者也可(通常)被使用。在此类系统中,侦听者用来“侦听”特定的事件。这可以是特定(键盘)按键的键入,移动鼠标,等等。事件扮演了发布者的角色,侦听者扮演了观察者的角色。这种情况的关键点在于存在多个侦听者(以及观察者)加入到了单一的事件(发布者)中。 53 | 54 | ## Implementation 实现 55 | In this section, we will implement a data formatter. The ideas described here are based on the ActiveState Python Observer code recipe [j.mp/pythonobs]. There is a default formatter that shows a value in the decimal format. However, we 56 | can add/register more formatters. In this example, we will add a hex and binary formatter. Every time the value of the default formatter is updated, the registered formatters are notified and take action. In this case, the action is to show the new value in the relevant format. 57 | 58 | 在本节,我们要实现一个数据格式器。 59 | 60 | Observer is actually one of the patterns where inheritance makes sense. We can have a base *Publisher* class that contains the common functionality of adding, removing, and notifying observers. Our DefaultFormatter class derives from Publisher 61 | and adds the formatter-specific functionality. We can dynamically add and remove observers on demand. The following class diagram shows an instance of the example using two observers: *HexFormatter* and BinaryFormatter. Note that, because class diagrams are static, they cannot show the whole lifetime of a system, only the state of it at a specific point in time. 62 | 63 | 观察者实际上是通过继承实现的模式其中一种。我们可以基本的*Publisher*类,它包含了常用的添加,移除,以及通知观察者的功能。我们的DefaultFormatter类派生自Publisher,而且添加了格式器专用的功能。我们可以按照需求动态地添加和移除观察者。下面的类图表展示了使用两个观察者例子的实例:*HexFormatter* 和BinaryFormatter。注意,因为类图表是静态的,所以它们不能够显示整个系统的生命周期内的东西,只展示了在特定时间内的部分状态。 64 | 65 | ![img](images/c13_2.png) 66 | 67 | We begin with the Publisher class. The observers are kept in the observers list. The `add()` method registers a new observer, or throws an error if it already exists. The `remove()` method unregisters an existing observer, or throws an exception if it does not exist. Finally, the `notify()` method informs all observers about a change: 68 | 69 | 我们从Publisher类开始。观察者保存在了observers列表中。`add()`方法注册了一个新的观察者,或者在观察者已经存在的情况下抛出一个错误。 `remove()`注销一个现有的观察者,或者在没有观察者存在时抛出一个异常。最后,`notify()`方法对所有的观察者通知变更的发生: 70 | 71 | ```python 72 | class Publisher: 73 | def __init__(self): 74 | self.observers = [] 75 | def add(self, observer): 76 | if observer not in self.observers: 77 | self.observers.append(observer) 78 | else: 79 | print('Failed to add: {}'.format(observer)) 80 | def remove(self, observer): 81 | try: 82 | self.observers.remove(observer) 83 | except ValueError: 84 | print('Failed to remove: {}'.format(observer)) 85 | def notify(self): 86 | [o.notify(self) for o in self.observers] 87 | ``` 88 | 89 | Let us continue with the DefaultFormatter class. The first thing that `__init__()` does is call `__init__()` method of the base class, since this is not done automatically in Python. A DefaultFormatter instance has name to make it easier for us to track its status. We use name mangling in the _data variable to state that it should not be accessed directly. Note that this is always possible in Python [Lott14, page 54] but fellow developers have no excuse for doing so, since the code already states that they should not. There is a serious reason for using name mangling in this case. Stay tuned. DefaultFormatter treats the _data variable as an integer, and the default value is zero: 90 | 91 | 我们继续编写DefaultFormatter类。 `__init__()`做的第一件事就是屌用基类的 `__init__()`方法,因为在Python中这个操作并不会自动完成。DefaultFormatter实力拥有name,以便让我们更容易地跟踪它的状态。我们在_data变量中使用名称扭曲来声明这个变量不应该被直接访问。 92 | 93 | ```python 94 | class DefaultFormatter(Publisher): 95 | def __init__(self, name): 96 | Publisher.__init__(self) 97 | self.name = name 98 | self._data = 0 99 | ``` 100 | 101 | 102 | The `__str__()` method returns information about the name of the publisher and the value of `_data. type(self).__name__` is a handy trick to get the name of a class without hardcoding it. It is one of those things that make the code less readable but easier to maintain. It is up to you to decide if you like it or not: 103 | 104 | `__str__()`方法返回发布者的名称信息, `_data. type(self).__name__`的值是一个获取类名称而不用硬编码的技巧。这是让代码可读性下降,但是易于维护的做法中其中一个。这样做,喜不喜欢完全由你自己决定: 105 | 106 | ```python 107 | def __str__(self): 108 | return "{}: '{}' has data = {}".format(type(self).__name__, self.name, self._data) 109 | ``` 110 | 111 | There are two data() methods. The first one uses the @property decorator to give read access to the` _data` variable. Using this, we can just execute object.data instead of object.data(): 112 | 113 | 有两个data()方法。第一个方法使用@property装饰器给出了对` _data`变量的读访问。使用该装饰器,我们只要执行object.data而不是object.data()就行了。 114 | 115 | ```python 116 | @property 117 | def data(self): 118 | return self._data 119 | ``` 120 | 121 | The second data() method is more interesting. It uses the @setter decorator, which is called every time the assignment (=) operator is used to assign a new value to the _data variable. This method also tries to cast a new value to an integer, and does exception handling in case this operation fails: 122 | 123 | 第二个data()方法就更有意思了。它使用了 @setter 装饰器,该装饰器在每次赋值运算符(=)用来将一个新值赋予_data变量时被屌用。该方法也试着把一个新值转换为整数,并在操作失败的情况下执行异常处理: 124 | 125 | ```python 126 | @data.setter 127 | def data(self, new_value): 128 | try: 129 | self._data = int(new_value) 130 | except ValueError as e: 131 | print('Error: {}'.format(e)) 132 | else: 133 | self.notify() 134 | ``` 135 | 136 | The next step is to add the observers. The functionality of HexFormatter and BinaryFormatter is very similar. The only difference between them is how they format the value of data received by the publisher, that is, in hexadecimal and binary, respectively: 137 | 138 | 下一步是添加观察者。HexFormatter和BinaryFormatter的功能非常相似。唯一区别在于,它们之间如何格式化发布者接收到值,即,分别使用了十进制和二进制: 139 | 140 | ```python 141 | class HexFormatter: 142 | def notify(self, publisher): 143 | print("{}: '{}' has now hex data = {}".format(type(self).__name__, 144 | publisher.name, hex(publisher.data))) 145 | 146 | 147 | class BinaryFormatter: 148 | def notify(self, publisher): 149 | print("{}: '{}' has now bin data = {}".format(type(self).__name__, 150 | publisher.name, bin(publisher.data))) 151 | ``` 152 | 153 | No example is fun without some test data. The main() function initially creates a DefaultFormatter instance named test1 and afterwards attaches (and detaches) the two available observers. Exception handling is also exercised to make sure that the application does not crash when erroneous data is passed by the user. Moreover, things such as trying to add the same observer twice or removing an observer that does not exist should cause no crashes: 154 | 155 | 例子在离开测试数据后会显得毫无乐趣。main()函数初始化时创建了DefaultFormatter的命名为test1实例,之后附加(和移除)了两个可用的观察者。异常处理也被使用了,以保证用户在传输错误数据时不会让应用崩溃。此外,添加相同的观察者两次,或者移除一个不存在的观察者这类尝试并不会引起崩溃: 156 | 157 | 158 | ```python 159 | def main(): 160 | df = DefaultFormatter('test1') 161 | print(df) 162 | print() 163 | hf = HexFormatter() 164 | df.add(hf) 165 | df.data = 3 166 | print(df) 167 | print() 168 | bf = BinaryFormatter() 169 | df.add(bf) 170 | df.data = 21 171 | print(df) 172 | print() 173 | df.remove(hf) 174 | df.data = 40 175 | print(df) 176 | print() 177 | df.remove(hf) 178 | df.add(bf) 179 | df.data = 'hello' 180 | print(df) 181 | print() 182 | df.data = 15.8 183 | print(df) 184 | ``` 185 | 186 | Here's how the full code of the example (`observer.py`) looks: 187 | 188 | 这里给出了下例子`observer.py`的完整代码: 189 | 190 | ```python 191 | class Publisher: 192 | def __init__(self): 193 | self.observers = [] 194 | 195 | def add(self, observer): 196 | if observer not in self.observers: 197 | self.observers.append(observer) 198 | else: 199 | print('Failed to add: {}'.format(observer)) 200 | 201 | def remove(self, observer): 202 | try: 203 | self.observers.remove(observer) 204 | except ValueError: 205 | print('Failed to remove: {}'.format(observer)) 206 | 207 | def notify(self): 208 | [o.notify(self) for o in self.observers] 209 | 210 | 211 | class DefaultFormatter(Publisher): 212 | def __init__(self, name): 213 | Publisher.__init__(self) 214 | self.name = name 215 | self._data = 0 216 | 217 | def __str__(self): 218 | return "{}: '{}' has data = {}".format(type(self).__name__, self.name, self._data) 219 | 220 | @property 221 | def data(self): 222 | return self._data 223 | 224 | @data.setter 225 | def data(self, new_value): 226 | try: 227 | self._data = int(new_value) 228 | except ValueError as e: 229 | print('Error: {}'.format(e)) 230 | else: 231 | self.notify() 232 | 233 | 234 | class HexFormatter: 235 | def notify(self, publisher): 236 | print("{}: '{}' has now hex data = {}".format(type(self).__name__, publisher.name, hex(publisher.data))) 237 | 238 | 239 | class BinaryFormatter: 240 | def notify(self, publisher): 241 | print("{}: '{}' has now bin data = {}".format(type(self).__name__, publisher.name, bin(publisher.data))) 242 | 243 | 244 | def main(): 245 | df = DefaultFormatter('test1') 246 | print(df) 247 | 248 | 249 | print() 250 | hf = HexFormatter() 251 | df.add(hf) 252 | df.data = 3 253 | print(df) 254 | print() 255 | bf = BinaryFormatter() 256 | df.add(bf) 257 | df.data = 21 258 | print(df) 259 | print() 260 | df.remove(hf) 261 | df.data = 40 262 | print(df) 263 | print() 264 | df.remove(hf) 265 | df.add(bf) 266 | df.data = 'hello' 267 | print(df) 268 | print() 269 | df.data = 15.8 270 | print(df) 271 | if __name__ == '__main__': 272 | main() 273 | ``` 274 | 275 | Executing observer.py gives the following output: 276 | 277 | 执行observer.py会输出下面的结果: 278 | 279 | ```python 280 | >>> python3 observer.py 281 | DefaultFormatter: 'test1' has data = 0 282 | HexFormatter: 'test1' has now hex data = 0x3 283 | DefaultFormatter: 'test1' has data = 3 284 | HexFormatter: 'test1' has now hex data = 0x15 285 | BinaryFormatter: 'test1' has now bin data = 0b10101 286 | DefaultFormatter: 'test1' has data = 21 287 | BinaryFormatter: 'test1' has now bin data = 0b101000 288 | DefaultFormatter: 'test1' has data = 40 289 | Failed to remove: <__main__.HexFormatter object at 0x7f30a2fb82e8> 290 | Failed to add: <__main__.BinaryFormatter object at 0x7f30a2fb8320> 291 | Error: invalid literal for int() with base 10: 'hello' 292 | BinaryFormatter: 'test1' has now bin data = 0b101000 293 | DefaultFormatter: 'test1' has data = 40 294 | BinaryFormatter: 'test1' has now bin data = 0b1111 295 | DefaultFormatter: 'test1' has data = 15 296 | ``` 297 | 298 | What we see in the output is that as the extra observers are added, more (and relevant) output is shown, and when an observer is removed, it is not notified any longer. That's exactly what we want: runtime notifications that we are able to enable/disable on demand. 299 | 300 | 我们所见的结果是,加入了额外的观察者,更多(相关的)输出结果被展示,当一个观察者被移除之后,不在会有任何的通知了。这正是我们想要的:对于运行时的通知,我们能够需求启用/禁用。 301 | 302 | The defensive programming part of the application also seems to work fine. Trying to do funny things such as removing an observer that does not exist or adding the same observer twice is not allowed. The messages shown are not very user-friendly but I leave that up to you as an exercise. Runtime failures of trying to pass a string when the API expects a number are also properly handled without causing the application to crash/terminate. 303 | 304 | 应用的保护性程序部分看样子也运行良好。你可以试着做些有趣的事情,比如移除一个不存在的观察者,或者不被允许的相同观察者二次添加。消息显示并不十分友好,我把它留作你的练习题。 305 | 306 | This example would be much more interesting if it were interactive. Even a simple menu that allows the user to attach/detach observers at runtime and modify the value of DefaultFormatter would be nice because the runtime aspect becomes much more visible. Feel free to do it. 307 | 308 | 如果可以交互的话,这个例子会变的更加有趣。可以是一个简单的允许用户在运行时添加/移除观察者的按钮,修改DefaultFormatter的值也特别方便,因为运行层面变得更具有可访问性。有空的时候可以做一下。 309 | 310 | Another nice exercise is to add more observers. For example, you can add an octal formatter, a roman numeral formatter, or any other observer that uses your favorite representation. Be creative and have fun! 311 | 312 | 另外一个不错的练习是添加更多的观察者。例如,你可以添加八进制格式器,罗马数字格式器,或者是其它的任何你想要表现的观察者。发挥你的创造性,然后享受在其中吧! 313 | 314 | ## Summary 315 | In this chapter, we covered the Observer design pattern. We use Observer when we want to be able to inform/notify all stakeholders (an object or a group of objects) when the state of an object changes. An important feature of observer is that the number of subscribers/observers as well as who the subscribers are may vary and can be changed at runtime. 316 | 317 | 在本章,我们讨论了观察者设计模式。我们在想要在一个对象的状态改变时通知/提醒全部的参与者(一个对象,或者一组对象)。观察者的一个重要的功能是它 318 | 319 | To understand Observer, you can think of an auction, with the bidders being the subscribers and the auctioneer being the publisher. This pattern is used quite a lot in the software world. 320 | 321 | 要理解观察者,你可以想一想拍卖,投标人作为订阅者,而拍卖主持人则作为发布者。该模式在软件世界中被经常使用。 322 | 323 | In general, all systems that make use of the MVC pattern are event-based. As specific examples, we mentioned: 324 | 325 | 通常,所有使用MVC模式的系统都是基于事件的。如指定的例子那样,我们提及的内容有: 326 | 327 | - django-observer, a third-party Django library used to register observers that are executed when fields are modified. 328 | - The Python bindings of RabbitMQ. We referred to a specific example of RabbitMQ used to implement the publish-subscribe (aka Observer) pattern. 329 | 330 | - django-observer,一个第三方的Django库,用来注册当字段被修改时要执行的观察者。 331 | - RabbitMQ的Python绑定。我们用一个专门的RabbitMQ的例子来实现发布-订阅(也被称作观察者)模式。 332 | 333 | In the implementation example, we saw how to use Observer to create data formatters that can be attached and detached at runtime to enrich the behavior of an object. Hopefully, you will find the recommended exercises interesting. 334 | 335 | 在具体视线的例子中,我们看到了如何使用观察者创建能够在运行时添加或者移除一个对象的行为的数据格式器。但愿,你能找到练习题的乐趣所在。 336 | 337 | The next chapter introduces the State design pattern, which can be used to implement a core computer science concept: state machines. 338 | 339 | 接下来的章节介绍了状态设计模式,它可以用来实现一个计算机科学概念的核心:状态机。 340 | -------------------------------------------------------------------------------- /第四章-适配器模式.md: -------------------------------------------------------------------------------- 1 | # The Adapter Pattern 2 | # 第四章 适配器模式 3 | Structural design patterns deal with the relationships between the entities (such as classes and objects) of a system. A structural design pattern focuses on providing a simple way of composing objects for creating new functionality [GOF95, page 155], [j.mp/structpat]. 4 | 5 | 结构化设计模式处理一个系统的对象之间的关系。 6 | 7 | Adapter is a structural design pattern that helps us make two incompatible interfaces compatible. First, let's answer what incompatible interfaces really mean. If we have an old component and we want to use it in a new system, or a new component that we want to use in an old system, the two can rarely communicate without requiring any code changes. But changing the code is not always possible, either because we don't have access to it (for example, the component is provided as an external library) or because it is impractical. In such cases, we can write an extra layer that makes all the required modi cations for enabling the communication between the two interfaces. This layer is called the Adapter. 8 | 9 | 适配器是一个有助于我们把两个不适配的结构变得可适配的结构化设计模式。首先,让我们来回答不适配借口的真实含义。如果我们拥有一个旧组件,而且我们想要把它用于一个新系统,或者是我们想要在一个旧系统中使用新组建,那么这两种方式下,很少有不要求不做任何代码改变的情况。但是改变代码也不少总是可行的,除非是因为我们不想访问他(例如,组建作为一个扩展库而被提供),或者是因为根本不可行。遇到这些情况,我们可以编写一个能够 10 | 11 | E-commerce systems are known examples. Assume that we use an e-commerce system that contains a calculate_total(order) function. The function calculates the total amount of an order, but only in Danish Kroner (DKK). It is reasonable for our customers to ask us to add support for more popular currencies, such as United States Dollars (USD) and Euros (EUR). If we own the source code of the system we can extend it by adding new functions for doing the conversions from DKK to USD and from DKK to EUR. But what if we don't have access to the source code of the application because it is provided to us only as an external library? In this case, we can still use the library (for example, call its methods), but we cannot modify/extend it. The solution is to write a wrapper (also known as Adapter) that converts the data from the given DKK format to the expected USD or EUR format. 12 | 13 | 电商系统是一个已知的例子。假设我们使用一个包含了calculate_total(order)函数的电商系统。该函数封装了一个订单的总价,但是只用于丹麦克朗(DKK)。我们的客户要求我们添加对更多流行货币对支持是合理的,比如美国美元(USD),和欧元(EUR)。如果我们拥有系统的源代码,我们可以通过添加把DKK转换到USD的新函数来扩展系统。但是,如果我们没有访问应用的源代码权限,因为这个系统仅支持扩展库,该怎么办呢?遇到这种情况,我们仍旧使用库(例如,调用系统的方法),但是不能修改/扩展它。解决办法是编写一个包装器(也被称作适配器)把数据从给定的DKK格式转换到希望使用的USD或者EUR格式。 14 | 15 | The Adapter pattern is not useful only for data conversions. In general, if you want to use an interface that expects function_a() but you only have function_b(), you can use an Adapter to convert (adapt) function_b() to function_a() [Eckel08, page 207], [j.mp/adapterpat]. This is not only true for functions but also for function parameters. An example is a function that expects the parameters x, y, and z but you only have a function that works with the parameters x and y at hand. We will see how to use the Adapter pattern in the implementation section. 16 | 17 | 适配器模式不仅仅只可用于数据转换。通常,如果你想要使用一个 18 | 19 | ## A real-life example 真实事例 20 | Probably all of us use the Adapter pattern every day, but in hardware instead of software. If you have a smartphone or a tablet, you need to use something (for example, the lightning connector of an iPhone) with a USB adapter for connecting 21 | it to your computer. If you are traveling from most European countries to the UK, you need to use a plug adapter for charging your laptop. The same is true if you are traveling from Europe to USA, or the other way around. Adapters are everywhere! 22 | 23 | 可能我们整天都在使用适配器模式,不过用的是硬件而不是软件。如果你拥有一部智能手机或者一台平板,那么你需要使用为了把设备连接到电脑而实用一个USB适配器。如果从欧洲的大多数国家启程去到英国旅行的话,你需要为你的笔记本电脑使用一个电源适配器。如果你从欧洲到美国旅行,或者周边国家时,也是同样的结果。适配器无处不在! 24 | 25 | The following image, courtesy of sourcemaking.com, shows several examples of hardware adapters [j.mp/adapterpat]: 26 | 27 | 下面,由sourcemaking.com提供的图片,展示了硬件适配器的几个例子: 28 | 29 | ![img](images/) 30 | 31 | ## A software example 软件示例 32 | Grok is a Python framework that runs on top of Zope 3 and focuses on agile development. The Grok framework uses Adapters for making it possible for existing objects to conform to speci c APIs without the need to modify them [j.mp/grokada]. 33 | 34 | Grok是一个运行在Zope 3基础支行的Python框架,它旨在于进行敏捷开发。Grok框架为了让现有项目是适应特定API而不对API修改,而使用了适配器。 35 | 36 | The Python Traits package also uses the Adapter pattern for transforming an object that does not implement of a speci c interface (or set of interfaces) to an object that does [j.mp/pytraitsad]. 37 | 38 | Python的包Traits也为了把一个对象进行转换,而又不为这个对象实现专门的接口便使用适配器模式。 39 | 40 | ## Use cases 用法案例 41 | The Adapter pattern is used for making things work after they have been implemented [j.mp/adapterpat]. Usually one of the two incompatible interfaces is either foreign or old/legacy. If the interface is foreign, it means that we have no access to the source code. If it is old it is usually impractical to refactor it. We can take it even further and argue that altering the implementation of a legacy component to meet our needs is not only impractical, but it also violates the open/close principle [j.mp/adaptsimp]. The open/close principle is one of the fundamental principles of Object-Oriented design (the O of SOLID). It states that a software entity should 42 | be open for extension, but closed for modi cation. That basically means that we should be able to extend the behavior of an entity without making source code modi cations. Adapter respects the open/closed principle [j.mp/openclosedp]. 43 | 44 | 适配器模式在实现之后用来让事务工作起来。通常两个不兼容的接口中的其中一个不是外部的,就是老旧的。如果接口是外部的,这意味着我们不能访问源代码。如果是旧的话,一般不实际的做法是重构它。我们进一步来探讨改变旧代码组建的实现来实现我们的需求不仅仅是不切实际的,而且这还违反了开发/关闭原则。开发/关闭原则是面向对象设计的一个基础原则。它声明了,软件对象应该为扩展而开发,且对修改进行关闭。这基本就是说,我们应该能够扩展一个对象的行为,而不用对源代码进行修改。适配器同样遵守开发/关闭原则。 45 | 46 | Therefore, using an Adapter for making things work after they have been implemented is a better approach because it: 47 | 48 | 因此,请在有了更好的实现之后使用适配器,因为: 49 | 50 | - Does not require access to the source code of the foreign interface 51 | - Does not violate the open/closed principle 52 | 53 | - 不要求访问外部接口的源代码 54 | - 不违反开放/关闭原则 55 | 56 | ## Implementation 实现 57 | There are many ways of implementing the Adapter design pattern in Python [Eckel08, page 207]. All the techniques demonstrated by Bruce Eckel use inheritance, but Python provides an alternative, and in my opinion, a more idiomatic way of implementing an Adapter. The alternative technique should be familiar to you, since it uses the internal dictionary of a class, and we have seen how to do that in Chapter 3, The Prototype Pattern. 58 | 59 | 在Python中有很多实现适配器模式的方法。所有的技术都由布鲁斯-埃寇使用继承来阐明,但是Python提供了一个选择,在我看来是一种更为理想的实现适配器的办法。另外一种技术你应该是熟悉的,因此它使用的是类的内部字典,而且在第三章-原型模式中,我们已经见过如何具体操作了。 60 | 61 | Let's begin with the what we have part. Our application has a Computer class that shows basic information about a computer. All the classes of this example, including the Computer class are very primitive, because we want to focus on the Adapter pattern and not on how to make a class as complete as possible. 62 | 63 | 我们从自己所实现的部分开始。我们的应用拥有一个展示电脑基本信息的Computer类。该例子的所有类,包括Computer类在内都非常的简陋,因为我们想关注在适配模式上,而不是如何将一个类尽可能的编写完整。 64 | 65 | ```python 66 | class Computer: 67 | def __init__(self, name): 68 | self.name = name 69 | def __str__(self): 70 | return 'the {} computer'.format(self.name) 71 | def execute(self): 72 | return 'executes a program' 73 | ``` 74 | 75 | In this case, the execute() method is the main action that the computer can perform. This method is called by the client code. 76 | 77 | 在这种情况下,execute()方式是电脑可以执行主要动作。该方法被称作客户端代码。 78 | 79 | Now we move to the what we want part. We decide to enrich our application with more functionality, and luckily, we nd two interesting classes implemented in two different libraries that are unrelated with our application: Synthesizer and Human. In the Synthesizer class, the main action is performed by the play() method. In the Human class, it is performed by the speak() method. To indicate that the two classes are external, we place them in a separate module, as shown: 80 | 81 | 现在,进入我们我们想要使用的代码。我们决定使用更多的功能扩展我们的应用,幸运的是,我们找到了两个有趣的使用不同和我们的应用无关的库实现的类:Synthesizer 和 Human。在Synthesizer,主要的行为是由play()方法执行的。在Human类中,主要是由speak()执行的。为了表明这两个类是来自外部的,我们把它放到一个独立的模块中,一如下所示: 82 | 83 | ```python 84 | class Synthesizer: 85 | def __init__(self, name): 86 | self.name = name 87 | 88 | def __str__(self): 89 | return 'the {} synthesizer'.format(self.name) 90 | 91 | def play(self): 92 | return 'is playing an electronic song' 93 | 94 | 95 | class Human: 96 | def __init__(self, name): 97 | self.name = name 98 | 99 | def __str__(self): 100 | return '{} the human'.format(self.name) 101 | 102 | def speak(self): 103 | return 'says hello' 104 | ``` 105 | 106 | So far so good. But, we have a problem. The client only knows how to call the execute() method, and it has no idea about play() or speak(). How can we make the code work without changing the Synthesizer and Human classes? Adapters to the rescue! We create a generic Adapter class that allows us to adapt a number of objects with different interfaces, into one uni ed interface. The obj argument of the `__init__()` method is the object that we want to adapt, and adapted_methods is a dictionary containing key/value pairs of method the client calls/method that should be called. 107 | 108 | 到目前为止还好。但是,我们有一个问题。用户仅知道如何调用execute()方法,而不知道如何使用play()或者speak()。我们如何让代码工作而又不改变类Synthesizer和Human?适配器就是拯救这个问题的!我们创建一个允许我们让不同的接口的多个对象适配到一个统一的接口的通用适配器类。`__init__()`的obj参数是我们想要去适配的对象,而adapted_methods则是一个包含 109 | 110 | ```python 111 | class Adapter: 112 | def __init__(self, obj, adapted_methods): 113 | self.obj = obj 114 | self.__dict__.update(adapted_methods) 115 | 116 | def __str__(self): 117 | return str(self.obj) 118 | ``` 119 | 120 | Let's see how we can use the Adapter pattern. An objects list holds all the objects. The compatible objects that belong to the Computer class need no adaptation. We can add them directly to the list. The incompatible objects are not added directly. They are adapted using the Adapter class. The result is that the client code can continue using the known execute() method on all objects without the need to be aware of any interface differences between the used classes. 121 | 122 | 让我们来看看如何使用适配器模式。一个对象列表拥有多个对象。属于Computer类的合适对象不需要去适配。我们直接把它们加入到列表。不合适的对象不会直接添加。它们要使用Adapter类进行适配。其结果是客户端代码可以继续在所有对象上使用 execute() 方法,而不需要关心所使用类之间任何接口上的不同。 123 | 124 | ```python 125 | def main(): 126 | objects = [Computer('Asus')] 127 | synth = Synthesizer('moog') 128 | objects.append(Adapter(synth, dict(execute=synth.play))) 129 | human = Human('Bob') 130 | objects.append(Adapter(human, dict(execute=human.speak))) 131 | for i in objects: 132 | print('{} {}'.format(str(i), i.execute())) 133 | ``` 134 | 135 | Let's see the complete code of the Adapter pattern example (files external.py and adapter.py) as follows: 136 | 137 | 如下,让我们看看适配器模式例子的完整代码文件(文件external.py和adapter.py): 138 | 139 | ```python 140 | class Synthesizer: 141 | def __init__(self, name): 142 | self.name = name 143 | 144 | def __str__(self): 145 | return 'the {} synthesizer'.format(self.name) 146 | 147 | def play(self): 148 | return 'is playing an electronic song' 149 | 150 | 151 | class Human: 152 | def __init__(self, name): 153 | self.name = name 154 | 155 | def __str__(self): 156 | return '{} the human'.format(self.name) 157 | 158 | def speak(self): 159 | return 'says hello' 160 | 161 | 162 | from external import Synthesizer, Human 163 | 164 | 165 | class Computer: 166 | def __init__(self, name): 167 | self.name = name 168 | 169 | def __str__(self): 170 | return 'the {} computer'.format(self.name) 171 | 172 | def execute(self): 173 | return 'executes a program' 174 | 175 | 176 | class Adapter: 177 | def __init__(self, obj, adapted_methods): 178 | self.obj = obj 179 | self.__dict__.update(adapted_methods) 180 | 181 | def __str__(self): 182 | return str(self.obj) 183 | 184 | 185 | def main(): 186 | objects = [Computer('Asus')] 187 | synth = Synthesizer('moog') 188 | objects.append(Adapter(synth, dict(execute=synth.play))) 189 | human = Human('Bob') 190 | objects.append(Adapter(human, dict(execute=human.speak))) 191 | for i in objects: 192 | print('{} {}'.format(str(i), i.execute())) 193 | 194 | 195 | if __name__ == "__main__": 196 | main() 197 | ``` 198 | 199 | The output when executing the example is: 200 | 201 | 执行这个例子输出结果是: 202 | 203 | ```shell 204 | >>> python3 adapter.py 205 | the Asus computer executes a program 206 | the moog synthesizer is playing an electronic song 207 | Bob the human says hello 208 | ``` 209 | 210 | We managed to make the Human and Synthesizer classes compatible with the interface expected by the client, without changing their source code. This is nice. 211 | Here's a challenging exercise for you. There is a problem with this implementation. While all classes have a name attribute, the following code fails: 212 | 213 | 我们希望由用户来管理 214 | 215 | ```python 216 | for i in objects: 217 | print(i.name) 218 | ``` 219 | 220 | First of all, why does this code fail? Although this makes sense from a coding point of view, it does not make sense at all for the client code which should not be aware of details such as what is adapted and what is not adapted. We just want to provide a uniform interface. How can we make this code work? 221 | 222 | 首先要说的是,这个代码为何会运行失败呢? 223 | 224 | >### Tip 225 | >Hint: Think of how you can delegate the non-adapted parts to the object contained in the Adapter class. 226 | 227 | >### 提示 228 | >想一想你该如何对包含在Adapter类中对象分配不适配的部分。 229 | 230 | ## Summary 总结 231 | This chapter covered the Adapter design pattern. We use the Adapter pattern for making two (or more) incompatible interfaces compatible. As a motivation, an e-commerce system that should support multiple currencies was mentioned. 232 | We use adapters every day for interconnecting devices, charging them, and so on. 233 | 234 | 本章讨论了适配器设计模式。我们使用适配器让两个(或者更多个)不兼容的接口变得兼容。从出发动机而言,一个电商系统应该支持早前提到的多种货币。我们每天都在使用适配器,比如为相互连接的设备进行充电,等等。 235 | 236 | Adapter makes things work after they have been implemented. The Grok Python framework and the Traits package use the Adapter pattern for achieving API conformance and interface compatibility, respectively. The open/close principle is strongly connected with these aspects. 237 | 238 | 适配器在被实现后才可以让事务工作起来。Python框架Grok,和Traits包分别利用适配器模式实现API一致,和接口上的兼容性。 239 | 240 | In the implementation section, we saw how to achieve interface conformance using the Adapter pattern without modifying the source code of the incompatible model. This is achieved through a generic Adapter class that does the work for us. Although we could use sub-classing (inheritance) to implement the Adapter pattern in the traditional way in Python, this technique is a great alternative. 241 | 242 | 在实现部分,我们见到了如何使用适配器模式实现接口一致,而不修改不兼容模型的源码。 243 | 244 | In the next chapter, we will see how we can use the Decorator pattern to extend the behavior of an object without using sub-classing. 245 | 246 | 在下面的章节中,我们会看到如何使用装饰器模式扩展一个对象的行为,而不使用子类化。 247 | --------------------------------------------------------------------------------