首页 > 技术文章 > 设计模式01

Dominic-Ji 2018-04-25 14:56 原文

单例模式

1请手写一个单例

#encoding=utf8
import threading
import time
#这里使用方法__new__来实现单例模式
class Singleton(object):#抽象单例
    def __new__(cls, *args, **kw):
        if not hasattr(cls, '_instance'):
            orig = super(Singleton, cls)
            cls._instance = orig.__new__(cls, *args, **kw)
        return cls._instance
#总线
class Bus(Singleton):
    lock = threading.RLock()
    def sendData(self,data):
        self.lock.acquire()
        time.sleep(3)
        print "Sending Signal Data...",data
        self.lock.release()
#线程对象,为更加说明单例的含义,这里将Bus对象实例化写在了run里
class VisitEntity(threading.Thread):
    my_bus=""
    name=""
    def getName(self):
        return self.name
    def setName(self, name):
        self.name=name
    def run(self):
        self.my_bus=Bus()
        self.my_bus.sendData(self.name)

if  __name__=="__main__":
    for i in range(3):
        print "Entity %d begin to run..."%i
        my_entity=VisitEntity()
        my_entity.setName("Entity_"+str(i))
        my_entity.start()

2单例模式的优点和应用?

单例模式的优点:
1、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间;
2、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用;
3、单例可长驻内存,减少系统开销。
单例模式的应用举例:
1、生成全局惟一的序列号;
2、访问全局复用的惟一资源,如磁盘、总线等;
3、单个对象占用的资源过多,如数据库等;
4、系统全局统一管理,如Windows下的Task Manager;
5、网站计数器。
6、数据库配置,数据库连接池
7、应用程序的日志应用

3单例模式的缺点

1、单例模式的扩展是比较困难的;
2、赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到);
3、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试;
4、单例模式在某种情况下会导致“资源瓶颈”。

 

工厂类相关模式

1工厂模式、简单工厂模式、抽象工厂模式

工厂模式的定义如下:定义一个用于创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。其通用类图如下。其产品类定义产品的公共属性和接口,工厂类定义产品实例化的“方式”。

import random


class PetShop(object):
    def __init__(self,animal_factory=None):
        #  pet 宠物  factory 工厂
        self.pet_factory = animal_factory

    def show_pet(self):
        pet = self.pet_factory.get_pet()
        print("this is a lovely", pet)
        print("it says",pet.speak())
        print("it eats",self.pet_factory.get_food())


class Dog:
    def speak(self):
        return "Dog"

    def __str__(self):
        return "this is Dog"


class Cat:
    def speak(self):
        return "Cat"

    def __str__(self):
        return "this is Cat"


class CatFactory:
    def get_pet(self):
        return Cat()

    def get_food(self):
        return "cat food"

class DogFactory:
    def get_pet(self):
        return Dog()

    def get_food(self):
        return "dog food"


def get_factory():
    return random.choice([DogFactory,CatFactory])

if __name__ == '__main__':
    shop = PetShop() # pet_factory 默认为None,后面延迟加载

    shop.pet_factory = get_factory()()  #  延迟加载,随机选择一个工厂然后实例出来一个对象给商店
    shop.show_pet()

2工厂模式的优点和应用

工厂模式、抽象工厂模式的优点:
1、工厂模式巨有非常好的封装性,代码结构清晰;在抽象工厂模式中,其结构还可以随着需要进行更深或者更浅的抽象层级调整,非常灵活;
2、屏蔽产品类,使产品的被使用业务场景和产品的功能细节可以分而开发进行,是比较典型的解耦框架。
工厂模式、抽象工厂模式的使用场景:
1、当系统实例要求比较灵活和可扩展时,可以考虑工厂模式或者抽象工厂模式实现。比如,
在通信系统中,高层通信协议会很多样化,同时,上层协议依赖于下层协议,那么就可以对应建立对应层级的抽象工厂,根据不同的“产品需求”去生产定制的实例。

3工厂模式的不足

1、工厂模式相对于直接生成实例过程要复杂一些,所以,在小项目中,可以不使用工厂模式;
2、抽象工厂模式中,产品类的扩展比较麻烦。毕竟,每一个工厂对应每一类产品,产品扩展,就意味着相应的抽象工厂也要扩展。

建造者模式

1建造者模式

建造者模式的定义如下:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式的作用,就是将“构建”和“表示”分离,以达到解耦的作用。在上面订单的构建过程中,如果将order直接通过参数定义好(其构建与表示没有分离),同时在多处进行订单生成,此时需要修改订单内容,则需要一处处去修改,业务风险也就提高了不少。
在建造者模式中,还可以加一个Director类,用以安排已有模块的构造步骤。对于在建造者中有比较严格的顺序要求时,该类会有比较大的用处。

class orderDirector():
    order_builder=""
    def __init__(self,order_builder):
        self.order_builder=order_builder
    def createOrder(self,burger,snack,beverage):
        self.order_builder.addBurger(burger)
        self.order_builder.addSnack(snack)
        self.order_builder.addBeverage(beverage)
        return self.order_builder.build()

2建造者模式的优点和使用场景

优点:
1、封装性好,用户可以不知道对象的内部构造和细节,就可以直接建造对象;
2、系统扩展容易;
3、建造者模式易于使用,非常灵活。在构造性的场景中很容易实现“流水线”;
4、便于控制细节。
使用场景:
1、目标对象由组件构成的场景中,很适合建造者模式。例如,在一款赛车游戏中,车辆生成时,需要根据级别、环境等,选择轮胎、悬挂、骨架等部件,构造一辆“赛车”;
2、在具体的场景中,对象内部接口需要根据不同的参数而调用顺序有所不同时,可以使用建造者模式。例如:一个植物养殖器系统,对于某些不同的植物,浇水、施加肥料的顺序要求可能会不同,因而可以在Director中维护一个类似于队列的结构,在实例化时作为参数代入到具体建造者中。

3建造者模式的缺点

1、“加工工艺”对用户不透明。(封装的两面性)

 

原型模式

1原型模式

原型模式定义如下:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
需要注意一点的是,进行clone操作后,新对象的构造函数没有被二次执行,新对象的内容是从内存里直接拷贝的。

from copy import copy, deepcopy
class simpleLayer:
    background=[0,0,0,0]
    content="blank"
    def getContent(self):
        return self.content
    def getBackgroud(self):
        return self.background
    def paint(self,painting):
        self.content=painting
    def setParent(self,p):
        self.background[3]=p
    def fillBackground(self,back):
        self.background=back
    def clone(self):
        return copy(self)
    def deep_clone(self):
        return deepcopy(self)
if  __name__=="__main__":
    dog_layer=simpleLayer()
    dog_layer.paint("Dog")
    dog_layer.fillBackground([0,0,255,0])
    print "Background:",dog_layer.getBackgroud()
    print "Painting:",dog_layer.getContent()
    another_dog_layer=dog_layer.clone()
    print "Background:", another_dog_layer.getBackgroud()
    print "Painting:", another_dog_layer.getContent()

2原型模式的优点和使用场景

优点:
1、性能极佳,直接拷贝比在内存里直接新建实例节省不少的资源;
2、简化对象创建,同时避免了构造函数的约束,不受构造函数的限制直接复制对象,是优点,也有隐患,这一点还是需要多留意一些。
使用场景:
1、对象在修改过后,需要复制多份的场景。如本例和其它一些涉及到复制、粘贴的场景;
2、需要优化资源的情况。如,需要在内存中创建非常多的实例,可以通过原型模式来减少资源消耗。此时,原型模式与工厂模式配合起来,不管在逻辑上还是结构上,都会达到不错的效果;
3、某些重复性的复杂工作不需要多次进行。如对于一个设备的访问权限,多个对象不用各申请一遍权限,由一个设备申请后,通过原型模式将权限交给可信赖的对象,既可以提升效率,又可以节约资源。

3原型模式的缺点

1、深拷贝和浅拷贝的使用需要事先考虑周到;
2、某些编程语言中,拷贝会影响到静态变量和静态函数的使用。

 

适配器模式

1适配器模式

适配器模式定义如下:将一个类的接口变换成客户端期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。适配器模式和装饰模式有一定的相似性,都起包装的作用,但二者本质上又是不同的,装饰模式的结果,是给一个对象增加了一些额外的职责,而适配器模式,则是将另一个对象进行了“伪装”。

适配器可以认为是对现在业务的补偿式应用,所以,尽量不要在设计阶段使用适配器模式,在两个系统需要兼容时可以考虑使用适配器模式。

import os


class Dog:
    def __init__(self):
        self.name ="Dog"

    def bark(self): # bark :叫声
        return "woof!"  # woof 低吠声


class Cat:
    def __init__(self):
        self.name = "Cat"

    def meow(self):  # meow 猫叫声
        return "meow"   # meow 猫叫声


class Human:
    def __init__(self):
        self.name = "Human"

    def speak(self):
        return "hello, python"


class Car:
    def __init__(self):
        self.name = "Car"

    def make_noise(self, noise_level):
        return "noise level is {}".format(noise_level)


class Adapter: # adapter 适配器
    def __init__(self,obj,adapted_methods):   # adpted 适应
        self.obj = obj
        self.__dict__.update(adapted_methods)   # self.__dict__是打印对象所有的属性,结果是一个字典 {"kye":value}
        # key对应对象的属性,value对应属性的属性值。这里就相当于把不同类的方法都绑定到Adapter这个类实例化出来的
        # 对象的make_noise 属性上面去,该属性的值对应其他类里面的方法。

    def __getattr__(self, attr):  # 当调用类不存的属性或者方法时,就会触发该魔法方法
        return getattr(self.obj, attr)  # getattr(object,attr [,default])


def main():
    objects = []
    dog = Dog()
    objects.append(Adapter(dog,dict(make_noise=dog.bark)))
    cat = Cat()
    objects.append(Adapter(cat,dict(make_noise=cat.meow)))
    human = Human()
    objects.append(Adapter(human,dict(make_noise=human.speak)))
    car = Car()
    car_noise = lambda : car.make_noise(3)
    objects.append(Adapter(car,dict(make_noise=car_noise)))
    for obj in objects:
        print("A",obj.name,"goes",obj.make_noise())  # 这里 obj.make_noise 就相当于 dog.bark 这些方法,后面加括号代表执行
        print(obj.obj) # 原来的对象被存储到obj属性里面.

if __name__ == '__main__':
    # 适配器模式在不改变原有类的基础上,统一了所有的方法,还能够保存原有对象的引用obj属性
    main()

2适配器模式的优点和使用场景

优点:
1、适配器模式可以让两个接口不同,甚至关系不大的两个类一起运行;
2、提高了类的复用度,经过“伪装”的类,可以充当新的角色;
3、适配器可以灵活“拆卸”。
应用场景:
1、不修改现有接口,同时也要使该接口适用或兼容新场景业务中,适合使用适配器模式。例如,在一个嵌入式系统中,原本要将数据从Flash读入,现在需要将数据从磁盘读入,这种情况可以使用适配器模式,将从磁盘读入数据的接口进行“伪装”,以从Flash中读数据的接口形式,从磁盘读入数据。

3适配器模式的缺点

1、适配器模式与原配接口相比,毕竟增加了一层调用关系,所以,在设计系统时,不要使用适配器模式。

桥接模式

1桥接模式

桥梁模式又叫桥接模式,定义如下:将抽象与实现解耦(注意此处的抽象和实现,并非抽象类和实现类的那种关系,而是一种角色的关系,这里需要好好区分一下),可以使其独立变化。在形如上例中,Pen只负责画,但没有形状,它终究是不知道要画什么的,所以我们把它叫做抽象化角色;而Shape是具体的形状,我们把它叫做实现化角色。抽象化角色和实现化角色是解耦的,这也就意味着,所谓的桥,就是抽象化角色的抽象类和实现化角色的抽象类之间的引用关系。

class DrawingAPI1:
    def draw_circle(self, x, y, radius):
        print("API1.circle at {} : {}  ,radius:{}".format(x, y, radius))


class DrawingAPI2:
    def draw_circle(self,x,y,radius):
        print("API2.cirle at {} : {}  ,radius:{}".format(x, y, radius))


class CircleShape:
    def __init__(self,x,y,radius,drawing_api):
        self._x = x
        self._y = y
        self._radius = radius
        self._drawing_api = drawing_api

    def draw(self):
        self._drawing_api.draw_circle(self._x,self._y,self._radius)

    def scale(self,pct):   # scale 规模
        self._radius *= pct   # pct 百分比


def main():
    shapes = (
        CircleShape(1,2,3,DrawingAPI1()),
        CircleShape(5,7,11,DrawingAPI2()),
    )  # 提供2个
    for shape in shapes:
        shape.scale(2.5)
        shape.draw()

2桥梁模式的优点和应用场景

优点:
1、抽象角色与实现角色相分离,二者可以独立设计,不受约束;
2、扩展性强:抽象角色和实现角色可以非常灵活地扩展。
应用场景:
1、不适用继承或者原继承关系中抽象类可能频繁变动的情况,可以将原类进行拆分,拆成实现化角色和抽象化角色。例如本例中,若将形状、粗细、绘画样式等属于汇集在一个类中,一旦抽象类中有所变动,将造成巨大的风险;
2、重用性比较大的场景。比如开关控制逻辑的程序,开关就是抽象化角色,开关的形式有很多种,操作的实现化角色也有很多种,采用桥梁模式,(如当前例子)开关即可进行复用,整体会将设计的粒度减小。

3桥梁模式的缺点

1、增加对系统理解的难度。

装饰器模式

1对装饰器的理解 ,并写出一个计时器记录方法执行性能的装饰器?

装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外
功能,装饰器的返回值也是一个函数对象。

import time

def timeit(func):
	def wrapper():
		start = time.clock()
		func() end =time.clock()
		print 'used:', end - start
	return wrapper

@timeit
def foo():
	print 'in foo()'foo()

2装饰器模式的优点和应用场景

优点:
1、装饰器模式是继承方式的一个替代方案,可以轻量级的扩展被装饰对象的功能;
2、Python的装饰器模式是实现AOP的一种方式,便于相同操作位于不同调用位置的统一管理。
应用场景:
1、需要扩展、增强或者减弱一个类的功能,如本例。

3装饰器模式的缺点

1、多层装饰器的调试和维护有比较大的困难。

组合模式

1组合模式

组合模式也叫作部分-整体模式,其定义如下:将对象组合成树形结构以表示“部分”和“整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

2组合模式的优点和使用场景

优点:
1、节点增加和减少是非常自由和方便的,这也是树形结构的一大特点;
2、所有节点,不管是分支节点还是叶子结点,不管是调用一个结点,还是调用一个结点群,都是非常方便的。
使用场景:
1、维护部分与整体的逻辑关系,或者动态调用整体或部分的功能接口,可以考虑使用组合模式。例如,非常多的操作系统(如Linux)都把文件系统设计成树形结构,再比如说分布式应用中借助Zookeeper,也可以组织和调用分布式集群中的结点功能。

3组合模式的缺点

1、由于叶子结点和分支结点直接使用了实现类,而不方便使用抽象类,这大大限制了接口的影响范围;若结点接口发生变更,对系统造成的风险会比较大。

 

门面模式

1门面模式

门面模式也叫外观模式,定义如下:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。门面模式注重“统一的对象”,也就是提供一个访问子系统的接口。门面模式与之前说过的模板模式有类似的地方,都是对一些需要重复方法的封装。但从本质上来说,是不同的。模板模式是对类本身的方法的封装,其被封装的方法也可以单独使用;而门面模式,是对子系统的封装,其被封装的接口理论上是不会被单独提出来用的。

2门面模式的优点和使用场景

优点:
1、减少了系统之间的相互依赖,提高了系统的灵活;
2、提高了整体系统的安全性:封装起的系统对外的接口才可以用,隐藏了很多内部接口细节,若方法不允许使用,则在门面中可以进行灵活控制。
使用场景:
1、为一个复杂的子系统提供一个外界访问的接口。这类例子是生活还是蛮常见的,例如电视遥控器的抽象模型,电信运营商的用户交互设备等;
2、需要简化操作界面时。例如常见的扁平化系统操作界面等,在生活中和工业中都很常见。

3门面模式的缺点

1、门面模式的缺点在于,不符合开闭原则,一旦系统成形后需要修改,几乎只能重写门面代码,这比继承或者覆写等方式,或者其它一些符合开闭原则的模式风险都会大一些。

 

享元模式

1享元模式

享元模式定义如下:使用共享对象支持大量细粒度对象。大量细粒度的对象的支持共享,可能会涉及这些对象的两类信息:内部状态信息和外部状态信息。内部状态信息就是可共享出来的信息,它们存储在享元对象内部,不会随着特定环境的改变而改变;外部状态信息就不可共享的信息了。享元模式中只包含内部状态信息,而不应该包含外部状态信息。这点在设计业务架构时,应该有所考虑。

2享元模式的优点和使用场景

优点:
1、减少重复对象,大大节约了系统资源。
使用场景:
1、系统中存在大量的相似对象时,可以选择享元模式提高资源利用率。咖啡订购平台比较小,若假设一个电商平台,每个买家和卖家建立起买卖关系后,买家对象和卖家对象都是占用资源的。如果一个卖家同时与多个买家建立起买卖关系呢?此时享元模式的优势就体现出来了;
2、需要缓冲池的场景中,可以使用享元模式。如进程池,线程池等技术,就可以使用享元模式(事实上,很多的池技术中已经使得了享元模式)。

3享元模式的缺点

1、享元模式虽然节约了系统资源,但同时也提高了系统的复杂性,尤其当遇到外部状态和内部状态混在一起时,需要先将其进行分离,才可以使用享元模式。否则,会引起逻辑混乱或业务风险;
2、享元模式中需要额外注意线程安全问题。

 

推荐阅读