前记 设计模式都是前人根据代码经验汇总而来的, 我们需要学习如何使用设计模式, 但是切忌被设计模式囚禁住我们的思想 设计模式虽然都是在描述编程的,但是很多也是来源于生活的实例,就拿一台单反来举例:
工厂模式 单反的外观由很多塑料来制作的,其中塑料属于原料,但通过不同的模具生产出不一样的模块。
造者模式 组成一台单反有很多原件,需要机器或工人按照规划好的程序,把部件组装起来,只有部件组装完成,单反才可以使用。
抽象工厂模式 单反里面有m档,a档p档s档等,选择不同的档,就有不一样的功能
单例模式 单反中有个反光镜,拍摄前,反光镜把景像反射到取景器,拍摄时,反光镜把景物反射给cmos,然后进行拍摄,其中景象的呈现只能2选1,不能两个都选取
原型模式 现在单反一开始有个raw格式,这是刚出来的格式,但是jpg格式是通过一定的程序修图后呈现出来的图片,这时,raw格式的图片不会被影响。
适配器模式 单反通过更改不同的镜头,可以用来拍摄微距,或者拍星空和人像等等
修饰器模式 通过在单反镜头前面加个不同uv镜片,可以让拍出来的照片可以减光,或者灯光有星芒。
外观模式 自动挡在点击拍摄时,单反自己进行对焦,决定用什么快门,光圈等等,用户不知道这些是如何执行,只知道按了拍摄按钮
状态模式 单反的闪光灯,在选择自动,打开,或关闭时。闪关灯在拍照时有不同的执行的操作,切闪光灯的这三个选项还可以进行切换。
备注 如果后面带有星号就是认为比较重要的模式,虽然这个是阅读完《python编程实战》的设计模式部分,《精通python设计模式》,以及c语言和java语言版本的《设计模式》后才写出来的,但电脑里有《精通python设计模式》的电子书和代码,所以很多都引用了这本书(之所以会读这样多相同的书,是因为在备考ccna时很闷,有时就翻一翻其他书看一看)
2020-12-19代码进行了重构
创建型模式 工厂模式 (*)
在工厂设计模式中,客户端可以请求一个对象,而无需知道这个对象来自哪里;也就是,使用哪个类来生成这个对象。工厂背后的思想是简化对象的创建。与客户端自己基于类实例化直接创建对象相比,基于一个中心化函数来实现,更易于追踪创建了哪些对象。通过将创建对象的代码和使用对象的代码解耦,工厂能够降低应用维护的复杂度
工厂通常有两种形式:一种是工厂方法(Factory Method),它是一个方法(或以地道的Python术语来说,是一个函数),对不同的输入参数返回不同的对象,第二种是抽象工厂,它是一组用于创建一系列相关事物对象的工厂方法
现实中用到工厂方法模式思想的一个例子是塑料玩具制造。制造塑料玩具的压塑粉都是一样的,但使用不同的塑料模具就能产出不同的外形。比如,有一个工厂方法,输入是目标外形(鸭子或小车)的名称,输出则是要求的塑料外形
工厂模式使用场景
如果因为应用创建对象的代码分布在多个不同的地方,而不是仅在一个函数/方法中,你发现没法跟踪这些对象,那么应该考虑使用工厂方法模式。工厂方法集中地在一个地方创建对象,使对象跟踪变得更容易。注意,创建多个工厂方法也完全没有问题,实践中通常也这么做,对相似的对象创建进行逻辑分组,每个工厂方法负责一个分组。例如,有一个工厂方法负责连接到不同的数据库(MySQL、 SQLite),另一个工厂方法负责创建要求的几何对象(圆形、三角形),等等。 若需要将对象的创建和使用解耦,工厂方法也能派上用场。创建对象时,我们并没有与某个特定类耦合/绑定到一起,而只是通过调用某个函数来提供关于我们想要什么的部分信息。这意味着修改这个函数比较容易,不需要同时修改使用这个函数的代码。 另外一个值得一提的应用案例与应用性能及内存使用相关。工厂方法可以在必要时创建新的对象,从而提高性能和内存使用率。若直接实例化类来创建对象,那么每次创建新对象就需要分配额外的内存(除非这个类内部使用了缓存,一般情况下不会这样)。
代码示例(来自于《精通python设计模式》): 最主要的是connection_factory函数里,根据不同的文件,选择不同的方法加工,再传回去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 import xml.etree.ElementTree as etreeimport jsonclass JSONConnector : def __init__ (self, filepath ): self.data = dict () with open (filepath, mode='r' , encoding='utf-8' ) as f: self.data = json.load(f) @property def parsed_data (self ): return self.dataclass XMLConnector : def __init__ (self, filepath ): self.tree = etree.parse(filepath) @property def parsed_data (self ): return self.treedef connection_factory (filepath ): if filepath.endswith('json' ): connector = JSONConnector elif filepath.endswith('xml' ): connector = XMLConnector else : raise ValueError('Cannot connect to {}' .format (filepath)) return connector(filepath)def connect_to (filepath ): factory = None try : factory = connection_factory(filepath) except ValueError as ve: print(ve) return factorydef main (): sqlite_factory = connect_to('data/person.sq3' ) print() xml_factory = connect_to('data/person.xml' ) xml_data = xml_factory.parsed_data liars = xml_data.findall(".//{}[{}='{}']" .format ('person' , 'lastName' , 'Liar' )) print('found: {} persons' .format (len (liars))) for liar in liars: print('first name: {}' .format (liar.find('firstName' ).text)) print('last name: {}' .format (liar.find('lastName' ).text)) [print('phone number ({})' .format (p.attrib['type' ]), p.text) for p in liar.find('phoneNumbers' )] print() json_factory = connect_to('data/donut.json' ) json_data = json_factory.parsed_data print('found: {} donuts' .format (len (json_data))) for donut in json_data: print('name: {}' .format (donut['name' ])) print('price: ${}' .format (donut['ppu' ])) [print('topping: {} {}' .format (t['id' ], t['type' ])) for t in donut['topping' ]]if __name__ == '__main__' : main()
建造者模式(*)
假设我们想要创建一个HTML页面生成器, HTML页面的基本结构(构造组件)通常是一样的:以<html>开始</html>结束,在HTML部分中有<head>和</head>元素,在head部分中又有<title>和</title>元素,等等;但页面在表现上可以不同。每个页面有自己的页面标题、文本标题以及不同的<body/>内容。此外,页面通常是经过多个步骤创建完成的:有一个函数添加页面标题,另一个添加主文本标题,还有一个添加页脚,等等。仅当一个页面的结构全部完成后,才能使用一个最终的渲染函数将该页面展示在客户端。我们甚至可以更进一步扩展这个HTML生成器,让它可以生成一些完全不同的HTML页面。一个页面可能包含表格,另一个页面可能包含图像库,还有一个页面包含联系表单,等等。 HTML页面生成问题可以使用建造者模式来解决。该模式中,有两个参与者:建造者 (builder)和指挥者(director)。建造者负责创建复杂对象的各个组成部分。在HTML例子中,这些组成部分是页面标题、文本标题、内容主体及页脚。指挥者使用一个建造者实例控制建造的过程。对于HTML示例,这是指调用建造者的函数设置页面标题、文本标题等。使用不同的建造者实例让我们可以创建不同的HTML页面,而无需变更指挥者的代码
说白了就是在金拱门点了餐后,工作人员按着订单拿着汉堡,薯条,可乐等到餐盘上,东西都弄好后,就可以递给我了(其中,其他人工作人员就有专门做薯条的,做汉堡的) 其中的我就相当于建造者模式的客户端,按着订单取货的工作人员是指挥员,其他工作人员是建造者
如果我们知道一个对象必须经过多个步骤来创建,并且要求同一个构造过程可以产生不同的表现,就可以使用建造者模式。这种需求存在于许多应用中,例如页面生成器、文档转换器以及用户界面(User Interface,UI)表单创建工具
工厂模式和建造者模式有两个区别:
主要的区别在于工厂模式以单个步骤创建对象,而建造者模式以多个步骤创建对象,并且几乎始终会使用一个指挥者。一些有针对性的建造者模式实现并未使用指挥者,如Java的StringBuilder,但这只是例外。
另一个区别是,在工厂模式下,会立即返回一个创建好的对象;而在建造者模式下,仅在需要时客户端代码才显式地请求指挥者返回最终的对象 就像直接买配置好的笔记本电脑和组装的台式机一样
示例代码如下(来自于《精通python设计模式》): 改例中,使用工厂模式,有两个pizza类可以选取,然后通过指挥者(Waiter)来一步一步创建,这里并没返回数据,当创建完时,才一起返回数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 from enum import Enumimport time PizzaProgress = Enum('PizzaProgress' , 'queued preparation baking ready' ) PizzaDough = Enum('PizzaDough' , 'thin thick' ) PizzaSauce = Enum('PizzaSauce' , 'tomato creme_fraiche' ) PizzaTopping = Enum('PizzaTopping' , 'mozzarella double_mozzarella bacon ham mushrooms red_onion oregano' ) STEP_DELAY = 3 class Pizza : def __init__ (self, name ): self.name = name self.dough = None self.sauce = None self.topping = [] def __str__ (self ): return self.name def prepare_dough (self, dough ): self.dough = dough print('preparing the {} dough of your {}...' .format (self.dough.name, self)) time.sleep(STEP_DELAY) print('done with the {} dough' .format (self.dough.name))class MargaritaBuilder : def __init__ (self ): self.pizza = Pizza('margarita' ) self.progress = PizzaProgress.queued self.baking_time = 5 def prepare_dough (self ): self.progress = PizzaProgress.preparation self.pizza.prepare_dough(PizzaDough.thin) def add_sauce (self ): print('adding the tomato sauce to your margarita...' ) self.pizza.sauce = PizzaSauce.tomato time.sleep(STEP_DELAY) print('done with the tomato sauce' ) def add_topping (self ): print('adding the topping (double mozzarella, oregano) to your margarita' ) self.pizza.topping.append([i for i in (PizzaTopping.double_mozzarella, PizzaTopping.oregano)]) time.sleep(STEP_DELAY) print('done with the topping (double mozzarrella, oregano)' ) def bake (self ): self.progress = PizzaProgress.baking print('baking your margarita for {} seconds' .format (self.baking_time)) time.sleep(self.baking_time) self.progress = PizzaProgress.ready print('your margarita is ready' )class CreamyBaconBuilder : def __init__ (self ): self.pizza = Pizza('creamy bacon' ) self.progress = PizzaProgress.queued self.baking_time = 7 def prepare_dough (self ): self.progress = PizzaProgress.preparation self.pizza.prepare_dough(PizzaDough.thick) def add_sauce (self ): print('adding the crème fraîche sauce to your creamy bacon' ) self.pizza.sauce = PizzaSauce.creme_fraiche time.sleep(STEP_DELAY) print('done with the crème fraîche sauce' ) def add_topping (self ): print('adding the topping (mozzarella, bacon, ham, mushrooms, red onion, oregano) to your creamy bacon' ) self.pizza.topping.append([t for t in (PizzaTopping.mozzarella, PizzaTopping.bacon, PizzaTopping.ham, PizzaTopping.mushrooms, PizzaTopping.red_onion, PizzaTopping.oregano)]) time.sleep(STEP_DELAY) print('done with the topping (mozzarella, bacon, ham, mushrooms, red onion, oregano)' ) def bake (self ): self.progress = PizzaProgress.baking print('baking your creamy bacon for {} seconds' .format (self.baking_time)) time.sleep(self.baking_time) self.progress = PizzaProgress.ready print('your creamy bacon is ready' )class Waiter : def __init__ (self ): self.builder = None def construct_pizza (self, builder ): self.builder = builder [step() for step in (builder.prepare_dough, builder.add_sauce, builder.add_topping, builder.bake)] @property def pizza (self ): return self.builder.pizzadef validate_style (builders ): try : pizza_style = input ('What pizza would you like, [m]argarita or [c]reamy bacon? ' ) builder = builders[pizza_style]() valid_input = True except KeyError as err: print('Sorry, only margarita (key m) and creamy bacon (key c) are available' ) return (False , None ) return (True , builder)def main (): builders = dict (m=MargaritaBuilder, c=CreamyBaconBuilder) valid_input = False while not valid_input: valid_input, builder = validate_style(builders) print() waiter = Waiter() waiter.construct_pizza(builder) pizza = waiter.pizza print() print('Enjoy your {}!' .format (pizza))if __name__ == '__main__' : main()
抽象工厂模式(*)
抽象工厂设计模式是抽象方法的一种泛化。概括来说,一个抽象工厂是(逻辑上的)一组工 厂方法,其中的每个工厂方法负责产生不同种类的对象
汽车制造业应用了抽象工厂的思想。冲压不同汽车模型的部件(车门、仪表盘、车篷、挡泥 板及反光镜等)所使用的机件是相同的。机件装配起来的模型随时可配置,且易于改变
因为抽象工厂模式是工厂方法模式的一种泛化,所以它能提供相同的好处:让对象的创建更容易追踪;将对象创建与使用解耦;提供优化内存占用和应用性能的潜力。这样会产生一个问题:我们怎么知道何时该使用工厂方法,何时又该使用抽象工厂?答案是,通常一开始时使用工厂方法,因为它更简单。如果后来发现应用需要许多工厂方法,那么将创建一系列对象的过程合并在一起更合理,从而最终引入抽象工厂。 抽象工厂有一个优点,在使用工厂方法时从用户视角通常是看不到的,那就是抽象工厂能够通过改变激活的工厂方法动态地(运行时)改变应用行为。
代码示例如下(来自于《精通python设计模式》): 这段代码就是使用了游戏进入前,可以选择不同的难度一样(这里为小孩和大人),选择完后game就有对应的类,小孩的类的方法和大人的类的方法执行着不同的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 class Frog : def __init__ (self, name ): self.name = name def __str__ (self ): return self.name def interact_with (self, obstacle ): print('{} the Frog encounters {} and {}!' .format (self, obstacle, obstacle.action()))class Bug : def __str__ (self ): return 'a bug' def action (self ): return 'eats it' class FrogWorld : def __init__ (self, name ): print(self) self.player_name = name def __str__ (self ): return '\n\n\t------ Frog World ———' def make_character (self ): return Frog(self.player_name) def make_obstacle (self ): return Bug()class Wizard : def __init__ (self, name ): self.name = name def __str__ (self ): return self.name def interact_with (self, obstacle ): print('{} the Wizard battles against {} and {}!' .format (self, obstacle, obstacle.action()))class Ork : def __str__ (self ): return 'an evil ork' def action (self ): return 'kills it' class WizardWorld : def __init__ (self, name ): print(self) self.player_name = name def __str__ (self ): return '\n\n\t------ Wizard World ———' def make_character (self ): return Wizard(self.player_name) def make_obstacle (self ): return Ork()class GameEnvironment : def __init__ (self, factory ): self.hero = factory.make_character() self.obstacle = factory.make_obstacle() def play (self ): self.hero.interact_with(self.obstacle)def validate_age (name ): try : age = input ('Welcome {}. How old are you? ' .format (name)) age = int (age) except ValueError as err: print("Age {} is invalid, please try \ again…" .format (age)) return (False , age) return (True , age)def main (): name = input ("Hello. What's your name? " ) valid_input = False while not valid_input: valid_input, age = validate_age(name) game = FrogWorld if age < 18 else WizardWorld environment = GameEnvironment(game(name)) environment.play()if __name__ == '__main__' : main()
单例模式
在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例
单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。 从具体实现角度来说,就是以下三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,三是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象。
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。[3] 如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。
示例代码:
1 2 3 4 5 6 7 8 class Singleton (object ): __instance=None def__init__(self): pass def__new__(cls,*args,**kwd): if Singleton.__instance is None : Singleton.__instance=object .__new__(cls,*args,**kwd) return Singleton.__instance
python中__new__是在__init__之前执行的,用于创建示例,使用__instance来判断这个实例是否被创建,从而限制只有单例
这样使用的单例模式只能对自己的类进行限制,也不灵活。可以使用装饰器实现的单例模式,以达到灵活使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def Singleton (cls ): _instance = {} def _singleton (*args, **kargs ): if cls not in _instance: _instance[cls] = cls(*args, **kargs) return _instance[cls] return _singleton @Singleton class A (object ): a = 1 def __init__ (self, x = 0 ): self.x = x a1 = A(2 ) a2 = A(3 ) print id (a1) print id (a2) print(a1.x) print(a2.x)
原型模式 原型模式的应用就像在github等版本管理器一样,有个人po上段代码后,别人可以copy他,并进行修改,这时是一个副本而不是上传者po的代码,上传者也可以new一个分支来修改。 其实就像生活中的有丝分裂一样,在复制一份后,两份的改变不互相影响
示例代码如下(来自于《精通python设计模式》): 代码中是在说一本书,发布第二版后的改变,如年代,字数,价格等
注意: 浅副本(copy.copy())和深副本(copy.deepcopy())之间的区别
浅副本构造一个新的复合对象后,(会尽可能地)将在原始对象中找到的对象的引用插入 新对象中。
深副本构造一个新的复合对象后,会递归地将在原始对象中找到的对象的副本插入新对 象中。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 import copyfrom collections import OrderedDictclass Book : def __init__ (self, name, authors, price, **rest ): '''rest的例子有:出版商,长度,标签,出版日期''' self.name = name self.authors = authors self.price = price self.__dict__.update(rest) def __str__ (self ): mylist = [] ordered = OrderedDict(sorted (self.__dict__.items())) for i in ordered.keys(): mylist.append('{}: {}' .format (i, ordered[i])) if i == 'price' : mylist.append('$' ) mylist.append('\n' ) return '' .join(mylist)class Prototype : def __init__ (self ): self.objects = dict () def register (self, identifier, obj ): self.objects[identifier] = obj def unregister (self, identifier ): del self.objects[identifier] def clone (self, identifier, **attr ): found = self.objects.get(identifier) if not found: raise ValueError('Incorrect object identifier: {}' .format (identifier)) obj = copy.deepcopy(found) obj.__dict__.update(attr) return objdef main (): b1 = Book('The C Programming Language' , ('Brian W. Kernighan' , 'Dennis M.Ritchie' ), price=118 , publisher='Prentice Hall' , length=228 , publication_date='1978-02-22' , tags=('C' , 'programming' , 'algorithms' , 'data structures' )) prototype = Prototype() cid = 'k&r-first' prototype.register(cid, b1) b2 = prototype.clone(cid, name='The C Programming Language(ANSI)' , price=48.99 , length=274 , publication_date='1988-04-01' , edition=2 ) for i in (b1, b2): print(i) print('ID b1 : {} != ID b2 : {}' .format (id (b1), id (b2)))if __name__ == '__main__' : main()
结构型模式 适配器模式(*) 适配器模式就像是苹果最近出的macbook和iphone8他们的接口使得线无法一起使用,这时,他们就造出了接口转换器,又能多赚点钱了,美滋滋。比如之前有个已经做好的项目了,现在想把另外一个写好的代码当做组件融入项目里,但由于之前没有规划好,这时候就要运用适配器模式,写个适配代码,让这段代码能在项目中运行。但这个时候要遵守2个原则:
适配器模式(Adapter pattern)是一种结构型设计模式,帮助我们实现两个不兼容接口之间的兼容。首先,解释一下不兼容接口的真正含义。如果我们希望把一个老组件用于一个新系统中,或者把一个新组件用于一个老系统中,不对代码进行任何修改两者就能够通信的情况很少见。但又并非总是能修改代码,或因为我们无法访问这些代码(例如,组件以外部库的方式提供),或因为修改代码本身就不切实际。在这些情况下,我们可以编写一个额外的代码层,该代码层包含让两个接口之间能够通信需要进行的所有修改。这个代码层就叫适配器
示例代码如下(来自于《精通python设计模式》): 第二部分是组件,第一部分的Adapter为适配器,适配器使用字典进行封装,虽然执行了execute方法,但在Synthesizer和Human中,对应执行的却是play和speak,但客户端不care这些,客户端仅知道如何调用execute()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 from external import Synthesizer, Humanclass Computer : def __init__ (self, name ): self.name = name def __str__ (self ): return 'the {} computer' .format (self.name) def execute (self ): return 'executes a program' class Adapter : def __init__ (self, obj, adapted_methods ): self.obj = obj self.__dict__.update(adapted_methods) def __str__ (self ): return str (self.obj)def main (): objects = [Computer('Asus' )] synth = Synthesizer('moog' ) objects.append(Adapter(synth, dict (execute=synth.play))) human = Human('Bob' ) objects.append(Adapter(human, dict (execute=human.speak))) for i in objects: print('{} {}' .format (str (i), i.execute()))if __name__ == "__main__" : main()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Synthesizer : def __init__ (self, name ): self.name = name def __str__ (self ): return 'the {} synthesizer' .format (self.name) def play (self ): return 'is playing an electronic song' class Human : def __init__ (self, name ): self.name = name def __str__ (self ): return '{} the human' .format (self.name) def speak (self ): return 'says hello'
修饰器模式(*) 修饰器模式和Python修饰器之间并不是一对一的等价关系
修饰器模式 修饰器就像单反/微单一样。一开始买来时,大家都会选择带上自带的狗头。如果要去拍人像的话,就会换上一个大光圈的定焦头。如果要取拍星星,打鸟,就会换上长焦距镜头。如果要拍微距的话,就可以换一个微距镜头。其中,机身为修饰器模式中要修饰的部分,而镜头就是修饰器。
在许多编程语言中,使用子类化(继承)来实现修饰器模式在Python中,我们可以(并且应该)使用内置的修饰器特性。一个Python修饰器就是对Python语法的一个特定改变,用于扩展一个类、方法或函数的行为,而无需使用继承。从实现的角度来说,Python修饰器是一个可调用对象(函数、方法、类),接受一个函数对象fin作为输入,并返回另一 个函 数对象 fout。这意味着可以将任何具有这些属性的可调用对象当作一个修饰器。 最常见的python秀时期应该就是内置的,property修饰器让一个方法表现为一个变量。但是修饰器模式和Python修饰器之间并不是一对一的等价关系。 Python修饰器能做的实际上比修饰器模式多得多,其中之一就是实现修饰器模式
这里的内容主要是我阅读完《python编程实战后整理的》 首先是原型函数,用来计算平均值的
1 2 3 def mean (first,second, *rest ): numbers = (first, second) + rest return sum (numbers)/len (numbers)
还有一个修饰函数
1 2 3 4 5 def float_args_and_return (functiong ): def wrapper (*args, **kwargs ): args = [float (arg) for arg in args] return float (function(*args, **kwargs)) return wrapper
通过修饰器模式使用该语句就可以完成
1 mean = float_args_and_return(mean)
在python中可以使用@语法糖来解决,由于在修饰函数中,使用这样的写法,那么修饰后的函数的__name__属性就和原函数不用了(变成了”wrapper”),而且即便原函数有docstring,修饰后的函数也不会有了。这时加上python标准库提供的@functools.wraps修饰器,我们就可以确保修改后的属性和名称与原函数相同 即代码如下: 可以发现str和int在修饰器后,都可以互相计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import functoolsdef float_args_and_return (function ): @functools.wraps(function ) def wrapper (*args, **kwargs ): args = [float (arg) for arg in args] return float (function(*args, **kwargs)) return wrapper@float_args_and_return def mean (first,second, *rest ): numbers = (first, second) + rest return sum (numbers)/len (numbers) print(mean(3 ,4 ,6 )) print(mean(3 ,"4" ,6 ))
修饰器工厂 先看代码: 该例子只是用来描述python的灵活性以及创建修饰器工厂的可行性,实际中这样用不太python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import functoolsdef statically_typed (*types, return_type=None ): def decorator (function ): @functools.wraps(function ) def wrapper (*args, **kwargs ): if len (args) > len (types): raise ValueError("too many arguments" ) elif len (args) < len (types): raise ValueError("too few arguments" ) for i, (arg, type_) in enumerate (zip (args, types)): if not isinstance (arg, type_): raise ValueError("argument {} must be of type {}" .format (i, type_.__name__)) result = function(*args, **kwargs) if (return_type is not None and not isinstance (result, return_type)): raise ValueError("return value must be of type {}" .format (return_type.__name__)) return result return wrapper return decorator@statically_typed(str , str , return_type=str ) def make_tagged (text, tag ): return "<{0}>{1}</{0}>" .format (tag, text)@statically_typed(str , int , str ) def repeat (what, count, separator ): return ((what + separator) * count)[:-len (separator)] print(make_tagged("Aaa" ,"b" )) print(repeat("3" , 4 , "6" ))
可以看出statically_typed()里面很乱很乱。。。。。 然而这个函数并不是修饰器,而是修饰器工厂,也就是一种能制作修饰器的函数。当想用一套模板来制造修饰器时。可以使用这种方法。 正如起名修饰器工厂,这里是遵循工程模式来创建修饰器函数的
类修饰器 类修饰器,也就是对类的修饰器。 下面的例子是书的类,Book()的实习都要重复使用getter与setter.可以通过类修饰器来减少重复使用 代码如下: 不过要注意以下几点
代码中,3个修饰器执行时,是从下往上执行
is_non_empty_str(),is_in_range()是用于验证的验证器
在修饰器ensure中传入的分别是实例需要的属性分别为名字,函数,和doc
decorator()中的getter是用来返回保存在私有atttribute的值也就是属性的名字,setter()中的setattr用来设置实例属性的值
而这两个则是通过decorator的setattr来绑定类的实例和实例的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import functoolsimport numbersdef is_non_empty_str (name, value ): if not isinstance (value, str ): raise ValueError("{} must be of type str" .format (name)) if not bool (value): raise ValueError("{} may not be empty" .format (name))def is_in_range (minimum=None , maximum=None ): def is_in_range (name, value ): if not isinstance (value, numbers.Number): raise ValueError("{} must be a number" .format (name)) if minimum is not None and value < minimum: raise ValueError("{} {} is too small" .format (name, value)) if maximum is not None and value > maximum: raise ValueError("{} {} is too big" .format (name, value)) return is_in_rangedef ensure (name, validate, doc=None ): def decorator (Class ): privateName = "_" + name def getter (self ): return getattr (self, privateName) def setter (self, value ): validate(name, value) setattr (self, privateName, value) setattr (Class, name, property (getter, setter, doc=doc)) return Class return decorator@ensure("title" , is_non_empty_str ) @ensure("price" , is_in_range(1 , 10000 ) ) @ensure("quantity" , is_in_range(0 , 1000000 ) ) class Book : def __init__ (self, title, price, quantity ): self.title = title self.price = price self.quantity = quantity @property def value (self ): return self.price * self.quantity A = Book("english" ,60 ,3 ) print(A.value) print(A.price) print(A._title)
优化后的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import functoolsimport numbersdef is_non_empty_str (name, value ): if not isinstance (value, str ): raise ValueError("{} must be of type str" .format (name)) if not bool (value): raise ValueError("{} may not be empty" .format (name))def is_in_range (minimum=None , maximum=None ): def is_in_range (name, value ): if not isinstance (value, numbers.Number): raise ValueError("{} must be a number" .format (name)) if minimum is not None and value < minimum: raise ValueError("{} {} is too small" .format (name, value)) if maximum is not None and value > maximum: raise ValueError("{} {} is too big" .format (name, value)) return is_in_rangeclass Ensure : def __init__ (self, validate, doc=None ): self.validate = validate self.doc= docdef do_ensure (Class ): def make_property (name, attribute ): privateName = "_" + name def getter (self ): return getattr (self, privateName) def setter (self, value ): attribute.validate(name, value) setattr (self, privateName, value) return property (getter, setter, doc=attribute.doc) for name, attribute in Class.__dict__.items(): if isinstance (attribute, Ensure): setattr (Class, name, make_property(name, attribute)) return Class@do_ensure class do_Book : title = Ensure(is_non_empty_str) price = Ensure(is_in_range(1 , 10000 )) quantity = Ensure(is_in_range(0 , 1000000 )) def __init__ (self, title, price, quantity ): self.title = title self.price = price self.quantity = quantity @property def value (self ): return self.price * self.quantity B = do_Book("english" ,60 ,3 ) print(B.value) print(B.price) print(B._title)
外观模式 在客户端代码想要使用一个复杂系统但又不关心系统复杂性之时,外观模式是为复杂系统提供一个简单接口的理想方式。一台计算机是一个外观,因为当我们使用它时需要做的事情仅是按一个按钮来启动它;其余的所有硬件复杂性都用户无感知地交由BIOS、引导加载程序以及其他系统软件来处理。
示例代码如下(来自于《精通python设计模式》):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 from enum import Enumfrom abc import ABCMeta, abstractmethod State = Enum('State' , 'new running sleeping restart zombie' )class User : pass class Process : pass class File : pass class Server (metaclass=ABCMeta ): @abstractmethod def __init__ (self ): pass def __str__ (self ): return self.name @abstractmethod def boot (self ): pass @abstractmethod def kill (self, restart=True ): pass class FileServer (Server ): def __init__ (self ): '''初始化文件服务进程要求的操作''' self.name = 'FileServer' self.state = State.new def boot (self ): print('booting the {}' .format (self)) '''启动文件服务进程要求的操作''' self.state = State.running def kill (self, restart=True ): print('Killing {}' .format (self)) '''杀死文件服务进程要求的操作''' self.state = State.restart if restart else State.zombie def create_file (self, user, name, permissions ): '''检查访问权限的有效性、用户权限,等等''' print("trying to create the file '{}' for user '{}' with permissions {}" .format (name, user, permissions))class ProcessServer (Server ): def __init__ (self ): '''初始化进程服务进程要求的操作''' self.name = 'ProcessServer' self.state = State.new def boot (self ): print('booting the {}' .format (self)) '''启动进程服务进程要求的操作''' self.state = State.running def kill (self, restart=True ): print('Killing {}' .format (self)) '''杀死进程服务进程要求的操作''' self.state = State.restart if restart else State.zombie def create_process (self, user, name ): '''检查用户权限、生成PID,等等''' print("trying to create the process '{}' for user '{}'" .format (name, user))class WindowServer : pass class NetworkServer : pass class OperatingSystem : '''外观''' def __init__ (self ): self.fs = FileServer() self.ps = ProcessServer() def start (self ): [i.boot() for i in (self.fs, self.ps)] def create_file (self, user, name, permissions ): return self.fs.create_file(user, name, permissions) def create_process (self, user, name ): return self.ps.create_process(user, name)def main (): os = OperatingSystem() os.start() os.create_file('foo' , 'hello' , '-rw-r-r' ) os.create_process('bar' , 'ls /tmp' )if __name__ == '__main__' : main()
享元模式(*) 由于对象创建的开销,面向对象的系统可能会面临性能问题。所以有了享元模式,享元模式可以让多个单元一起共享同一个需要的数据。类似于玩游戏时,每个人其实都差不多,只是表情或者帽子等有差别,那些没差别的部位可以来自同一个对象,减少内存开销
示例代码如下(来自于《精通python设计模式》): 代码中,通过__new__的方式和pool限制了只创建了三个对象,通过输出,可以看出虽然生产了30课树,但除了年龄,位置外,其余的属性都是这三个对象的其中一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import randomfrom enum import Enum TreeType = Enum('TreeType' , 'apple_tree cherry_tree peach_tree' )class Tree : pool = dict () def __new__ (cls, tree_type ): obj = cls.pool.get(tree_type, None ) if not obj: obj = object .__new__(cls) cls.pool[tree_type] = obj obj.tree_type = tree_type return obj def render (self, age, x, y ): print('render a tree of type {} and age {} at ({}, {})' .format (self.tree_type, age, x, y))def main (): rnd = random.Random() age_min, age_max = 1 , 30 min_point, max_point = 0 , 100 tree_counter = 0 for _ in range (10 ): t1 = Tree(TreeType.apple_tree) t1.render(rnd.randint(age_min, age_max), rnd.randint(min_point, max_point), rnd.randint(min_point, max_point)) tree_counter += 1 for _ in range (3 ): t2 = Tree(TreeType.cherry_tree) t2.render(rnd.randint(age_min, age_max), rnd.randint(min_point, max_point), rnd.randint(min_point, max_point)) tree_counter += 1 for _ in range (5 ): t3 = Tree(TreeType.peach_tree) t3.render(rnd.randint(age_min, age_max), rnd.randint(min_point, max_point), rnd.randint(min_point, max_point)) tree_counter += 1 print('trees rendered: {}' .format (tree_counter)) print('trees actually created: {}' .format (len (Tree.pool))) t4 = Tree(TreeType.cherry_tree) t5 = Tree(TreeType.cherry_tree) t6 = Tree(TreeType.apple_tree) print('{} == {}? {}' .format (id (t4), id (t5), id (t4) == id (t5))) print('{} == {}? {}' .format (id (t5), id (t6), id (t5) == id (t6)))if __name__ == '__main__' : main()
模型-视图-控制器模式(*) MVC模式要是有接触到ui的几乎都见过。该模式通过分层设计,把视图和模型分开,再通过控制器把他们联系起来,但视图和模型可以互相不用知道对方如何工作。 例如在Django中,view+url为C,model为m,template为V,MVC运行方式如下:
用户通过单击(键入、触摸等)某个按钮触发一个视图
视图把用户操作告知控制器
控制器处理用户输入,并与模型交互
模型执行所有必要的校验和状态改变,并通知控制器应该做什么
控制器按照模型给出的指令,指导视图适当地更新和显示输出
MVC模式有3个好处
视图与模型的分离允许美工一心搞UI部分,程序员一心搞开发,不会相互干扰。
由于视图与模型之间的松耦合,每个部分可以单独修改/扩展,不会相互影响。例如,添加一个新视图的成本很小,只要为其实现一个控制器就可以了。
因为职责明晰,维护每个部分也更简单
创建MVC时要确保:模型很智能,控制器很瘦,视图很傻瓜
智能模型
包含所有的校验/业务规则/逻辑
处理应用的状态
访问应用数据(数据库、云或其他)
不依赖UI
瘦控制器
在用户与视图交互时,更新模型
在模型改变时,更新视图
如果需要,在数据传递给模型/视图之前进行处理
不展示数据
不直接访问应用数据
不包含校验/业务规则/逻辑
傻瓜视图
展示数据
允许用户与其交互
仅做最小的数据处理,通常由一种模板语言提供处理能力(例如,使用简单的变量和循环控制)
不存储任何数据
不直接访问应用数据
不包含校验/业务规则/逻辑
示例代码如下(来自于《精通python设计模式》):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 quotes = ('A man is not complete until he is married. Then he is finished.' , 'As I said before, I never repeat myself.' , 'Behind a successful man is an exhausted woman.' , 'Black holes really suck...' , 'Facts are stubborn things.' )class QuoteModel : def get_quote (self, n ): try : value = quotes[n] except IndexError as err: value = 'Not found!' return valueclass QuoteTerminalView : def show (self, quote ): print('And the quote is: "{}"' .format (quote)) def error (self, msg ): print('Error: {}' .format (msg)) def select_quote (self ): return input ('Which quote number would you like to see?' )class QuoteTerminalController : def __init__ (self ): self.model = QuoteModel() self.view = QuoteTerminalView() def run (self ): valid_input = False while not valid_input: n = self.view.select_quote() try : n = int (n) except ValueError as err: self.view.error("Incorrect index '{}'" .format (n)) else : valid_input = True quote = self.model.get_quote(n) self.view.show(quote)def main (): controller = QuoteTerminalController() while True : controller.run()if __name__ == '__main__' : main()
代理模式(*) 代理,就是把这个步骤交给其他的去做,然后返回结果。代理模式有很多种:
远程代理:实际存在于不同地址空间(例如,某个网络服务器)的对象在本地的代理者。
虚拟代理:用于懒初始化,将一个大计算量对象的创建延迟到真正需要的时候进行。
保护/防护代理:控制对敏感对象的访问。
智能(引用)代理:在对象被访问时执行额外的动作。此类代理的例子包括引用计数和线程安全检查。
虚拟代理的应用:
示例代码如下(来自于《精通python设计模式》):
通过LazyProperty修饰器来达到代理。可以通过输出来理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class LazyProperty : def __init__ (self, method ): self.method = method self.method_name = method.__name__ print('function overriden: {}' .format (self.method)) print("function's name: {}" .format (self.method_name)) def __get__ (self, obj, cls ): if not obj: return None value = self.method(obj) print('value {}' .format (value)) setattr (obj, self.method_name, value) return valueclass Test : def __init__ (self ): self.x = 'foo' self.y = 'bar' self._resource = None @LazyProperty def resource (self ): print('initializing self._resource which is: {}' .format (self._resource)) self._resource = tuple (range (5 )) return self._resourcedef main (): t = Test() print(t.x) print(t.y) print(t.resource) print(t.resource)if __name__ == '__main__' : main()
输出部分:
可以从输出部分看到t = Test()时,代理修饰器已经运行了。
第一个print(t.resource) 开始加载,当来到
LazyProperty的value = self.method(obj)语句时,调用到了Text的resource(self),开始生成数值,取代原先的None并返回给代理。代理使用setattr把数据与method_name绑定,当再次调用print(t.resource)时,调用的是代理的method_name的数据,所以直接返回数据
总结的说,就是在第一次获取数据时,是获取到Test.resource的值(这时才刚加载),并把值存在代理的value里面(所以之后都不用加载Test.resource的值)。加载之后的每次调用,返回的都是代理的value(包括第一调用时返回的值)
1 2 3 4 5 6 7 8 function overriden: <function Test.resource at 0x7fad622c4d90 > function's name: resource foo bar initializing self._resource which is: None value (0, 1, 2, 3, 4) (0, 1, 2, 3, 4) (0, 1, 2, 3, 4)
防护代理的应用:
示例代码如下(来自于《精通python设计模式》):
注:密码不应该明文出现在代码里面,以下代码仅为示例所需
可以从代码看出Info类仅是个代理,调用的数据还是来自于SensitiveInfo类,不过info类多了个密码验证功能,如果不能验证成功,就不会访问到SensitiveInfo类的数据。从而利用代理达到防护数据被窃取的可能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class SensitiveInfo : def __init__ (self ): self.users = ['nick' , 'tom' , 'ben' , 'mike' ] def read (self ): print('There are {} users: {}' .format (len (self.users), ' ' .join(self.users))) def add (self, user ): self.users.append(user) print('Added user {}' .format (user))class Info : '''SensitiveInfo的保护代理''' def __init__ (self ): self.protected = SensitiveInfo() self.secret = '0xdeadbeef' def read (self ): self.protected.read() def add (self, user ): sec = input ('what is the secret? ' ) self.protected.add(user) if sec == self.secret else print("That's wrong!" )def main (): info = Info() while True : print('1. read list |==| 2. add user |==| 3. quit' ) key = input ('choose option: ' ) if key == '1' : info.read() elif key == '2' : name = input ('choose username: ' ) info.add(name) elif key == '3' : exit() else : print('unknown option: {}' .format (key))if __name__ == '__main__' : main()
## 行为型模式
### 责任链模式
责任链模式就像广播网络一样,一台机器发出一个广播,同个本地局域网的其他主机就会开始接收,如果这个数据是自己可以用的,就给自己执行,如果不是就丢给下一个主机。
就像python里面的if....elif...elif....else(我认为这就是责任链模式。。这是我搜索时没有答案可以验证我的这个认为)
>其原则如下所示。
(1) 存在一个对象链(链表、树或任何其他便捷的数据结构)。
(2) 我们一开始将请求发送给链中的第一个对象。
(3) 对象决定其是否要处理该请求。
(4) 对象将请求转发给下一个对象。
(5) 重复该过程,直到到达链尾。
示例代码如下(来自于《精通python设计模式》): 可以通过输出查看事件的显示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 class Event : def __init__ (self, name ): self.name = name def __str__ (self ): return self.nameclass Widget : def __init__ (self, parent=None ): self.parent = parent def handle (self, event ): handler = 'handle_{}' .format (event) if hasattr (self, handler): method = getattr (self, handler) method(event) elif self.parent: self.parent.handle(event) elif hasattr (self, 'handle_default' ): self.handle_default(event)class MainWindow (Widget ): def handle_close (self, event ): print('MainWindow: {}' .format (event)) def handle_default (self, event ): print('MainWindow Default: {}' .format (event))class SendDialog (Widget ): def handle_paint (self, event ): print('SendDialog: {}' .format (event))class MsgText (Widget ): def handle_down (self, event ): print('MsgText: {}' .format (event))def main (): mw = MainWindow() sd = SendDialog(mw) msg = MsgText(sd) for e in ('down' , 'paint' , 'unhandled' , 'close' ): evt = Event(e) print('\nSending event -{}- to MainWindow' .format (evt)) mw.handle(evt) print('Sending event -{}- to SendDialog' .format (evt)) sd.handle(evt) print('Sending event -{}- to MsgText' .format (evt)) msg.handle(evt)if __name__ == '__main__' : main()
命令模式 命令模式就是通过封装,使代码能够通过一些选项终端运行不一样的功能
示例代码如下(来自于《精通python设计模式》): 通过命令来打开文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 import os verbose = True class RenameFile : def __init__ (self, path_src, path_dest ): self.src, self.dest = path_src, path_dest def execute (self ): if verbose: print("[renaming '{}' to '{}']" .format (self.src, self.dest)) os.rename(self.src, self.dest) def undo (self ): if verbose: print("[renaming '{}' back to '{}']" .format (self.dest, self.src)) os.rename(self.dest, self.src)class CreateFile : def __init__ (self, path, txt='hello world\n' ): self.path, self.txt = path, txt def execute (self ): if verbose: print("[creating file '{}']" .format (self.path)) with open (self.path, mode='w' , encoding='utf-8' ) as out_file: out_file.write(self.txt) def undo (self ): delete_file(self.path)class ReadFile : def __init__ (self, path ): self.path = path def execute (self ): if verbose: print("[reading file '{}']" .format (self.path)) with open (self.path, mode='r' , encoding='utf-8' ) as in_file: print(in_file.read(), end='' )def delete_file (path ): if verbose: print("deleting file '{}'" .format (path)) os.remove(path)def main (): orig_name, new_name = 'file1' , 'file2' commands = [] for cmd in CreateFile(orig_name), ReadFile(orig_name), RenameFile(orig_name, new_name): commands.append(cmd) [c.execute() for c in commands] answer = input ('reverse the executed commands? [y/n] ' ) if answer not in 'yY' : print("the result is {}" .format (new_name)) exit() for c in reversed (commands): try : c.undo() except AttributeError as e: pass if __name__ == '__main__' : main()
解释器模式 封装自己写好的代码,操作者通过命令来调用代码来完成自己想要的操作就是解释器模式所提供的功能 很多应用都会提供命令模式,像A家的Ae,Pr,Ps等产品,让操作这个应用的人都能以自己的能力取更好的制作出自己的作品,python有很多命令操作工具包,通过封装能使操作的人按照命令来操作代码。 或者说,我们打开一个解释器在运行一段代码时,该解释器就是在利用解释器模式运行我们的代码 示例代码如下(来自于《精通python设计模式》):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 from pyparsing import Word, OneOrMore, Optional, Group, Suppress, alphanumsclass Gate : def __init__ (self ): self.is_open = False def __str__ (self ): return 'open' if self.is_open else 'closed' def open (self ): print('opening the gate' ) self.is_open = True def close (self ): print('closing the gate' ) self.is_open = False class Garage : def __init__ (self ): self.is_open = False def __str__ (self ): return 'open' if self.is_open else 'closed' def open (self ): print('opening the garage' ) self.is_open = True def close (self ): print('closing the garage' ) self.is_open = False class Aircondition : def __init__ (self ): self.is_on = False def __str__ (self ): return 'on' if self.is_on else 'off' def turn_on (self ): print('turning on the aircondition' ) self.is_on = True def turn_off (self ): print('turning off the aircondition' ) self.is_on = False class Heating : def __init__ (self ): self.is_on = False def __str__ (self ): return 'on' if self.is_on else 'off' def turn_on (self ): print('turning on the heating' ) self.is_on = True def turn_off (self ): print('turning off the heating' ) self.is_on = False class Boiler : def __init__ (self ): self.temperature = 83 def __str__ (self ): return 'boiler temperature: {}' .format (self.temperature) def increase_temperature (self, amount ): print("increasing the boiler's temperature by {} degrees" .format (amount)) self.temperature += amount def decrease_temperature (self, amount ): print("decreasing the boiler's temperature by {} degrees" .format (amount)) self.temperature -= amountclass Fridge : def __init__ (self ): self.temperature = 2 def __str__ (self ): return 'fridge temperature: {}' .format (self.temperature) def increase_temperature (self, amount ): print("increasing the fridge's temperature by {} degrees" .format (amount)) self.temperature += amount def decrease_temperature (self, amount ): print("decreasing the fridge's temperature by {} degrees" .format (amount)) self.temperature -= amountdef main (): word = Word(alphanums) command = Group(OneOrMore(word)) token = Suppress("->" ) device = Group(OneOrMore(word)) argument = Group(OneOrMore(word)) event = command + token + device + Optional(token + argument) gate = Gate() garage = Garage() airco = Aircondition() heating = Heating() boiler = Boiler() fridge = Fridge() tests = ('open -> gate' , 'close -> garage' , 'turn on -> aircondition' , 'turn off -> heating' , 'increase -> boiler temperature -> 5 degrees' , 'decrease -> fridge temperature -> 2 degrees' ) open_actions = {'gate' : gate.open , 'garage' : garage.open , 'aircondition' : airco.turn_on, 'heating' : heating.turn_on, 'boiler temperature' : boiler.increase_temperature, 'fridge temperature' : fridge.increase_temperature} close_actions = {'gate' : gate.close, 'garage' : garage.close, 'aircondition' : airco.turn_off, 'heating' : heating.turn_off, 'boiler temperature' : boiler.decrease_temperature, 'fridge temperature' : fridge.decrease_temperature} for t in tests: if len (event.parseString(t)) == 2 : cmd, dev = event.parseString(t) cmd_str, dev_str = ' ' .join(cmd), ' ' .join(dev) if 'open' in cmd_str or 'turn on' in cmd_str: open_actions[dev_str]() elif 'close' in cmd_str or 'turn off' in cmd_str: close_actions[dev_str]() elif len (event.parseString(t)) == 3 : cmd, dev, arg = event.parseString(t) cmd_str, dev_str, arg_str = ' ' .join(cmd), ' ' .join(dev), ' ' .join(arg) num_arg = 0 try : num_arg = int (arg_str.split()[0 ]) except ValueError as err: print("expected number but got: '{}'" .format (arg_str[0 ])) if 'increase' in cmd_str and num_arg > 0 : open_actions[dev_str](num_arg) elif 'decrease' in cmd_str and num_arg > 0 : close_actions[dev_str](num_arg)if __name__ == '__main__' : main()
观察者模式(*)
有时,我们希望在一个对象的状态改变时更新另外一组对象。在MVC模式中有这样一个非常常见的例子,假设在两个视图(例如,一个饼图和一个电子表格)中使用同一个模型的数据,无论何时更改了模型,都需要更新两个视图。这就是观察者设计模式要处理的问题。观察者模式描述单个对象(发布者,又称为主持者或可观察者)与一个或多个对象(订阅者,又称为观察者)之间的发布—订阅关系。在MVC例子中,发布者是模型,订阅者是视图。然而,MVC并非是仅有的发布—订阅例子。信息聚合订阅(比如, RSS或Atom)是另一种例子。许多读者通常会使用一个信息聚合阅读器订阅信息流,每当增加一条新信息时,他们就能自动地获取到更新。观察者模式背后的思想等同于MVC和关注点分离原则背后的思想,即降低发布者与订阅者之间的耦合度,从而易于在运行时添加/删除订阅者。此外,发布者不关心它的订阅者是谁。它只是将通知发送给所有订阅者。
观察者模式经常被用于社交网络,当用户关注的人更新文章时,社交网络使用事件驱动推送更新给用户 示例代码如下(来自于《精通python设计模式》): 在输出中我们看到,添加额外的观察者,就会出现更多(相关的)输出;一个观察者被删除后,就再也不会被通知到。删除一个不存在的观察者或者两次添加相同的观察者。这都是通过Publisher类了来实行的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 class Publisher : def __init__ (self ): self.observers = [] def add (self, observer ): if observer not in self.observers: self.observers.append(observer) else : print('Failed to add: {}' .format (observer)) def remove (self, observer ): try : self.observers.remove(observer) except ValueError: print('Failed to remove: {}' .format (observer)) def notify (self ): [o.notify(self) for o in self.observers]class DefaultFormatter (Publisher ): def __init__ (self, name ): Publisher.__init__(self) self.name = name self._data = 0 def __str__ (self ): return "{}: '{}' has data = {}" .format (type (self).__name__, self.name, self._data) @property def data (self ): return self._data @data.setter def data (self, new_value ): try : self._data = int (new_value) except ValueError as e: print('Error: {}' .format (e)) else : self.notify()class HexFormatter : def notify (self, publisher ): print("{}: '{}' has now hex data = {}" .format (type (self).__name__, publisher.name, hex (publisher.data)))class BinaryFormatter : def notify (self, publisher ): print("{}: '{}' has now bin data = {}" .format (type (self).__name__, publisher.name, bin (publisher.data)))def main (): df = DefaultFormatter('test1' ) print(df) print() hf = HexFormatter() df.add(hf) df.data = 3 print(df) print() bf = BinaryFormatter() df.add(bf) df.data = 21 print(df) print() df.remove(hf) df.data = 40 print(df) print() df.remove(hf) df.add(bf) df.data = 'hello' print(df) print() df.data = 15.8 print(df)if __name__ == '__main__' : main()
状态模式(*)
状态模式是一个或多个有限状态机(简称状态机)的实现,用于解决一个特定的软件工程问题。状态机是一个抽象机器,具有两个主要部分:状态和转换。状态是指一个系统的当前状况。一个状态机在任意时间点只会有一个激活状态。转换是指从当前状态到一个新状态的切换。在一个转换发生之前或之后通常会执行一个或多个动作。状态机可以使用状态图进行视觉上的展现。状态机用于解决许多计算机问题和非计算机问题,其中包括交通灯、停车计时器、硬件设计和编程语言解析等。我们也看到零食自动贩卖机是如何与状态机的工作方式相关联的。
玩游戏时,我们可以经常看到状态模式,比如玩lol时,敌方有座防御塔,如果是你一个人走进去,你会被防御塔打,如果小兵先进去,你再进去时,防御塔会打小兵,如果敌方防御塔在范围内没监测到我方单位在里面,他就进入不攻击模式。 所以状态模式最重要的有两个点,一个是多个状态,第二是多个状态可以互相切换
示例代码如下(来自于《精通python设计模式》):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 from state_machine import State, Event, acts_as_state_machine, after, before, InvalidStateTransition@acts_as_state_machine class Process : created = State(initial=True ) waiting = State() running = State() terminated = State() blocked = State() swapped_out_waiting = State() swapped_out_blocked = State() wait = Event(from_states=(created, running, blocked, swapped_out_waiting), to_state=waiting) run = Event(from_states=waiting, to_state=running) terminate = Event(from_states=running, to_state=terminated) block = Event(from_states=(running, swapped_out_blocked), to_state=blocked) swap_wait = Event(from_states=waiting, to_state=swapped_out_waiting) swap_block = Event(from_states=blocked, to_state=swapped_out_blocked) def __init__ (self, name ): self.name = name @after('wait' ) def wait_info (self ): print('{} entered waiting mode' .format (self.name)) @after('run' ) def run_info (self ): print('{} is running' .format (self.name)) @before('terminate' ) def terminate_info (self ): print('{} terminated' .format (self.name)) @after('block' ) def block_info (self ): print('{} is blocked' .format (self.name)) @after('swap_wait' ) def swap_wait_info (self ): print('{} is swapped out and waiting' .format (self.name)) @after('swap_block' ) def swap_block_info (self ): print('{} is swapped out and blocked' .format (self.name))def transition (process, event, event_name ): try : event() except InvalidStateTransition as err: print('Error: transition of {} from {} to {} failed' .format (process.name, process.current_state, event_name))def state_info (process ): print('state of {}: {}' .format (process.name, process.current_state))def main (): RUNNING = 'running' WAITING = 'waiting' BLOCKED = 'blocked' TERMINATED = 'terminated' p1, p2 = Process('process1' ), Process('process2' ) [state_info(p) for p in (p1, p2)] print() transition(p1, p1.wait, WAITING) transition(p2, p2.terminate, TERMINATED) [state_info(p) for p in (p1, p2)] print() transition(p1, p1.run, RUNNING) transition(p2, p2.wait, WAITING) [state_info(p) for p in (p1, p2)] print() transition(p2, p2.run, RUNNING) [state_info(p) for p in (p1, p2)] print() [transition(p, p.block, BLOCKED) for p in (p1, p2)] [state_info(p) for p in (p1, p2)] print() [transition(p, p.terminate, TERMINATED) for p in (p1, p2)] [state_info(p) for p in (p1, p2)]if __name__ == '__main__' : main()
策略模式 策略模式,它最主要的特点是,可以把一系列(可互换的)算法封装起来,并根据用户需求来选择其中一种。例如python里面的sorted函数,根据key的不同,能按不同的方式排序。 我感觉是很多策略模式的集成于者?看后觉得是很多设计模式的都需要使用他,就像工厂模式里面,可以理解为根据用户选择不同的读取方式,把数据注入不同的工厂,再读取出来。(这里就不上代码了)
模板模式 (*) 这个模式在我写爬虫时就使用过了,但是我并不知道这叫模板模式。当时都思考了好久好久改了好多方案,包括用来效率比较差一点的if…elif…else的方法(这是由于其中一个网站的编码不一样导致我想用这种方法)。所以。。设计模式是之前的人归纳自己能提高自己写代码能力的方法,我们早点理解设计模式,就能少遇一些坑,也能更容易写出代码。
模板模式,就是几个函数的代码里面,只有小部分代码不同时。可以把这些函数的相同代码归为一个函数,根据不同的调用形成不同的原先的代码,旨在减少代码沉宇 以下代码来自于我的爬虫代码(修改于12月5日): 其中url_parse就运用了模板模式通过传入的不同action之后就调用不同的数据清理函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 import reimport requestsimport jsonimport timefrom bs4 import BeautifulSoupfrom datetime import datetimeimport asyncioimport aiohttpdef pinpaigongyu_58city (soup,city,page=1 ): """ 数据来自58品牌公寓 url由于是移动端提取的,所以拼接为电脑端(我网页只运行为电脑端) address+info_title后期拼接后,高德能找到对应的位置,准确率提高 """ print('开始爬取58同城' +city+'的信息,第' +str (page)+'页' ) house_info_list = soup.find_all("li" ,{"class" :"item" }) pattern_58city = re.compile (r"%s(.+)" %city) for house_info in house_info_list: house_url = house_info.find("a" ).get('href' ) house_url = 'http://' +city+'.58.com' +re.findall(pattern_58city, house_url)[0 ] info_title = house_info.find("dt" ,{"class" :"info-title" }).text.split("|" )[1 ].split("-" )[0 ].split("(" )[0 ].strip() address = house_info.find("em" ).text.strip()+info_title price = house_info.find("span" ,{"class" :"info-line2-price" }).text.strip() price = re.findall(re.compile (r'\d+' ),price)[0 ] room_info = house_info.find("dd" ,{"class" :"room_icon" }).text.split("\n" )[1 :-2 ] room_info = "," .join(room_info) sql_data(house_url,address,info_title,price,room_info) print('爬取了58同城' +city+'的信息,第' +str (page)+'页' )def anjuke (soup,city,page=1 ): """ 数据来自安居客,速度比较慢 """ print('开始爬取安居客' +city+'的信息,第' +str (page)+'页' ) house_info_list = soup.find_all("div" ,{"class" :"zu-itemmod" }) for house_info in house_info_list: house_url = house_info.get('link' ) address_info_title = house_info.find("address" ,{"class" :"details-item" }).text.split("[" ) address = address_info_title[1 ].strip().rstrip(']' ) info_title = address_info_title[0 ].strip() price = house_info.find("strong" ).text room_info = house_info.find("p" ,{"class" :"details-item tag" }).text.split("|" ) room_info = "," .join(room_info) print(house_url,address,info_title,price,room_info) sql_data(house_url,address,info_title,price,room_info) print('爬取了安居客' +city+'的信息,第' +str (page)+'页' )def fang (soup,city,page=1 ): print('开始爬取房天下' +city+'的信息,第' +str (page)+'页' ) house_info_list = soup.find_all("dl" ,{"class" :"list hiddenMap rel" }) for house_info in house_info_list: house_url = 'http://zu.' +city+'.fang.com' +house_info.find("a" ,{"target" :"_blank" }).get('href' ) address = house_info.find("p" ,{"class" :"gray6 mt20" }).text info_title = address.split("-" )[-1 ].strip() price = house_info.find("span" ,{"class" :"price" }).text try : room_info = (house_info.find("span" ,{"class" :"note colorGreen" }).text +'-' +house_info.find("span" ,{"class" :"note colorBlue" }).text +'-' +house_info.find("span" ,{"class" :"note colorRed" }).text).strip() except : room_info = '没有房间具体信息' sql_data(house_url,address,info_title,price,room_info) print('爬取了房天下' +city+'的信息,第' +str (page)+'页' )def sql_data (house_url,address,info_title,price,room_info ): ''' 写入数据 ''' print(house_url+'|' +address+'|' +info_title+'|' +price+'|' +room_info) print('--------' )async def url_parse (url,city,page,action ): ''' 解析网页,之后转入对应的函数进行数据清理 使用协程启动,对这两个库还没那么了解,之后再改动得好点 ''' headers={'User-Agent' :'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36' } async with aiohttp.ClientSession() as session: async with session.get(url,headers=headers) as resp: print(url, resp.status) if resp.status == 200 : try : soup = BeautifulSoup(await resp.text(),"html.parser" ) except : soup = BeautifulSoup(await resp.text(encoding='gbk' ),"html.parser" ) action(soup,city,page) elif url not in url_temp_list: url_temp_list.append(url) url_parse(url,city,page) else : return ("该url无法爬取:%s" % url)if __name__ == '__main__' : tasks = [] url_temp_list = [] page_all = 2 city_list = ['sz' ,'gz' ] url_list = [ ['http://m.58.com/city/pinpaigongyu/pnpage/?segment=true' ,pinpaigongyu_58city], ['https://city.zu.anjuke.com/fangyuan/ppage' ,anjuke], ['http://zu.city.fang.com/house/i3page' ,fang] ] for page in range (page_all): page = page+1 for city in city_list: for url in url_list: url_temp = url[0 ].replace('city' ,city) url_temp = url_temp.replace('page' ,str (page)) tasks.append(url_parse(url_temp,city,page,url[1 ])) start_time = time.time() loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) loop.close() end_time = time.time() print(end_time-start_time)
总结 设计模式,是对编程的一个规范,各个设计模式之间会有许多相连,或者有很多共同的地方。就像我们在写代码时,可能一个代码就运用了很多设计模式,有时觉得能不能分清他们并不是最重要的,最重要的是如何使用他们,理解好他们,来提高我们的生产力。