设计模式(不完全)

设计模式(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.当你想要创建一个可扩展的关联在创建类与支持创建对象的类之间。

一个例子更能很好的理解以上的内容:

  1. 我们有一个基类Person ,包涵获取名字,性别的方法 。有两个子类male 和female,可以打招呼。还有一个工厂类。
  2.  工厂类有一个方法名getPerson有两个输入参数,名字和性别。
  3.  用户使用工厂类,通过调用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')

 

posted @ 2020-09-03 16:48  Achilles_Heel  阅读(196)  评论(0编辑  收藏  举报