设计模式(不完全)
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
一、设计模式分类
1. 创建型模式分类
社会化的分工越来越细,自然在软件设计方面也是如此,因此对象的创建和对象的使用分开也就成为了必然趋势。因为对象的创建会消耗掉系统的很多资源,所以单独对对象的创建进行研究,从而能够高效地创建对象就是创建型模式要探讨的问题。这里有6个具体的创建型模式可供研究,它们分别是:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
- 创建者模式
- 原型模式
- 单例模式
2. 结构型模式分类
在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。对象结构的设计很容易体现出设计人员水平的高低,这里有7个具体的结构型模式可供研究,它们分别是:
- 外观模式
- 适配器模式
- 代理模式
- 装饰模式
- 桥模式
- 组合模式
- 享元模式
3. 行为型模式分类
在对象的结构和对象的创建问题都解决了之后,就剩下对象的行为问题了,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高,这里有11个具体的行为型模式可供研究,它们分别是:
- 模板方法模式(Template Method);
- 观察者模式(Observer);
- 状态模式(State);
- 策略模式(Strategy);
- 职责链模式(Chain of Responsibility);
- 命令模式(Command);
- 访问者模式(Visitor);
- 调停者模式(Mediator);
- 备忘录模式(Memento);
- 迭代器模式(Iterator);
- 解释器模式(Interpreter)
二、设计模式的原则
1、开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:是对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
5、迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
6、合成复用原则(Composite Reuse Principle)
三、常用模式实现
1. 单例模式
单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象
class Singleton(object): 2 def __init__(self): 3 pass 4 5 def __new__(cls, *args, **kwargs): 6 if not hasattr(Singleton, "_instance"): # 反射 7 Singleton._instance = object.__new__(cls) 8 return Singleton._instance 9 10 obj1 = Singleton() 11 obj2 = Singleton() 12 print(obj1, obj2) #<__main__.Singleton object at 0x004415F0> <__main__.Singleton object at 0x004415F0>
通过重写__new__方法,可以实现单例模式,我们可以看到创建的两个实例obj1和obj2是同一个对象。
2. 工厂模式
工厂模式是一个在软件开发中用来创建对象的设计模式。
工厂模式包涵一个超类。这个超类提供一个抽象化的接口来创建一个特定类型的对象,而不是决定哪个对象可以被创建。
为了实现此方法,需要创建一个工厂类创建并返回。
当程序运行输入一个“类型”的时候,需要创建于此相应的对象。这就用到了工厂模式。在如此情形中,实现代码基于工厂模式,可以达到可扩展,可维护的代码。当增加一个新的类型,不在需要修改已存在的类,只增加能够产生新类型的子类。
简短的说,当以下情形可以使用工厂模式:
1.不知道用户想要创建什么样的对象
2.当你想要创建一个可扩展的关联在创建类与支持创建对象的类之间。
一个例子更能很好的理解以上的内容:
- 我们有一个基类Person ,包涵获取名字,性别的方法 。有两个子类male 和female,可以打招呼。还有一个工厂类。
- 工厂类有一个方法名getPerson有两个输入参数,名字和性别。
- 用户使用工厂类,通过调用getPerson方法。
在程序运行期间,用户传递性别给工厂,工厂创建一个与性别有关的对象。因此工厂类在运行期,决定了哪个对象应该被创建
class Person: def __init__(self): self.name = None self.gender = None def getName(self): return self.name def getGender(self): return self.gender class Male(Person): def __init__(self, name): print "Hello Mr." + name class Female(Person): def __init__(self, name): print "Hello Miss." + name class Factory: def getPerson(self, name, gender): if gender == ‘M': return Male(name) if gender == 'F': return Female(name) if __name__ == '__main__': factory = Factory() person = factory.getPerson("Chetan", "M")
3. 建造者模式
建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节.
它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
何时使用:一些基本部件不会变,而其组合经常变化的时候。
如何解决:将变与不变分离开。
关键代码:建造者:创建和提供实例,指挥者:管理建造出来的实例的依赖关系。
应用实例: 去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。
优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
实例:KFC套餐
建造者模式可以用于描述KFC如何创建套餐:套餐是一个复杂对象,它一般包含主食(如汉堡、鸡肉卷等)和饮料(如果汁、可乐等)等组成部分,不同的套餐有不同的组成部分,而KFC的服务员可以根据顾客的要求,一步一步装配这些组成部分,构造一份完整的套餐,然后返回给顾客。
#具体产品对象 class Menu: Menu_A=[] Menu_B=[] def set_MenuA(self,item): self.Menu_A.append(item) def set_MenuB(self,item): self.Menu_B.append(item) def get_MenuA(self): return self.Menu_A def get_MenuB(self): return self.Menu_B # Builder(抽象建造者) # 创建一个Product对象的各个部件指定的抽象接口。 class Product: product = Menu() def build_hanbao(self): pass def build_jiroujuan(self): pass def build_kele(self): pass def build_shutiao(self): pass # ConcreteBuilder(具体建造者) # 实现抽象接口,构建和装配各个部件。 #套餐A class product_A(Product): type="A" def build_hanbao(self): self.hanbao="汉堡" self.product.set_MenuA(self.hanbao) def build_kele(self): self.kele="可乐" self.product.set_MenuA(self.kele) def getType(self): return type # 套餐B class product_B(Product): type = "B" def build_shutiao(self): self.shutiao="薯条" self.product.set_MenuB(self.shutiao) def build_jiroujuan(self): self.jiroujuan="鸡肉卷" self.product.set_MenuB(self.jiroujuan) def build_kele(self): self.kele="可乐" self.product.set_MenuB(self.kele) def getType(self): return type #Director(指挥者) class Make: def __init__(self): self.builder = None def build_product(self, builder): self.builder = builder print(builder.type) if builder.type == "A": [step() for step in (builder.build_hanbao, builder.build_kele)] if builder.type == "B": [step() for step in (builder.build_shutiao, builder.build_jiroujuan, builder.build_kele)] #不同类型选择 def validate_style(builders): global valid_input try: print('套餐A:汉堡、可乐'+'\n' '套装B:薯条、鸡肉卷、可乐') product_style = input('请输入您的选择:' ) builder = builders[product_style]() valid_input = True except KeyError as err: print('Sorry, 没有这个套餐,请重新选择。') return (False, None) return (True, builder,product_style) #主函数 def main(): builders = dict(A=product_A, B=product_B) valid_input = False while not valid_input: valid_input, builder,product_style = validate_style(builders) Waiter = Make() Waiter.build_product(builder) if product_style == "A":print(builder.product.get_MenuA()) else:print(builder.product.get_MenuB()) if __name__ =="__main__": main()
4. 原型模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式本质就是克隆对象,所以在对象初始化操作比较复杂的情况下,很实用,能大大降低耗时,提高性能,因为“不用重新初始化对象,而是动态地获得对象运行时的状态”。
浅拷贝(Shallow Copy):指对象的字段被拷贝,而字段引用的对象不会被拷贝,拷贝的对象和源对象只是名称相同,但是他们共用一个实体。
深拷贝(deep copy):对对象实例中字段引用的对象也进行拷贝。
import copy from collections import OrderedDict class 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 obj def 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() """ >>> python3 prototype.py authors: ('Brian W. Kernighan', 'Dennis M. Ritchie') length: 228 name: The C Programming Language price: 118$ publication_date: 1978-02-22 publisher: Prentice Hall tags: ('C', 'programming', 'algorithms', 'data structures') authors: ('Brian W. Kernighan', 'Dennis M. Ritchie') edition: 2 length: 274 name: The C Programming Language (ANSI) price: 48.99$ publication_date: 1988-04-01 publisher: Prentice Hall tags: ('C', 'programming', 'algorithms', 'data structures') ID b1 : 140004970829304 != ID b2 : 140004970829472 """
5. 适配器模式
适配器模式(Adapter pattern)(也被称为Wrapper模式)是一种结构型设计模式,帮助我们实现两个不兼容接口之间的兼容。首先,解释一下不兼容接口的真正含义。如果我们希望把一个老组件用于一个新系统中,或者把一个新组件用于一个老系统中,不对代码进行任何修改两者就能够通信的情况很少见。但又并非总是能修改代码,或因为我们无法访问这些代码(例如,组件以外部库的方式提供),或因为修改代码本身就不切实际。在这些情况下,我们可以编写一个额外的代码层,该代码层包含让两个接口之间能够通信需要进行的所有修改。这个代码层就叫做适配器。
电子商务系统是这方面众所周知的例子。假设我们使用的一个电子商务系统中包含一个calculate_total(order)函数。这个函数计算一个订单的总金额,但货币单位为丹麦克朗(Danish Kroner,DKK)。顾客让我们支持更多的流行货币,比如美元(United States Dollar,USD)和欧元(Euro,EUR),这是很合理的要求。如果我们拥有系统的源代码,那么可以扩展系统,方法是添加一些新函数,将金额从DKK转换成USD,EUR。但是如果应用仅以外部库的方式提供,我们无法访问其源代码,那又该怎么办呢?在这种情况下,我们仍然可以使用这个外部库(例如,调用它的方法),但无法修改/扩展它。解决方案是编写一个包装器(又名适配器)将数据从给定的DKK格式转换成期望的USD或EUR格式。
适配器模式并不仅仅对数据转换有用。通常来说,如果你想使用一个接口,期望它是function_a(),但仅有function_b()可用,那么可以使用一个适配器把function_b()转换(适配)成function_a()。不仅对于函数可以这样做,对于函数参数也可以如此。其中一个例子是,有一个函数要求参数x、y、z,但你手头只有一带参数x、y的函数。
6.修饰器模式
该模式虽名为修饰器,但这并不意味着它应该只用于让产品看起来更漂亮。修饰器模式通常用于扩展一个对象的功能。这类扩展的实际例子有,给枪加一个消音器、使用不同的照相机镜头。
装饰模式:动态地给一个对象增加一些额外的职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。以对客户透明的方式动态地给一个对象附加上更多的责任 可以在不需要创建更多子类的情况下,让对象的功能得以扩展
import time def timmer(func): def deco(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time=time.time() print('the func run time is %s'%(end_time-start_time)) return deco @timmer def test1(): time.sleep(3) print('in the test1') @timmer def test2(name): time.sleep(1) print('test2',name) test1() test2('kobe')