每一个设计模式系统的命名、解释和评价了面向对象系统中一个重要的和重复出现的设计。这里面向对象划重点,意思是所有的设计模式都是针对面向对象的提出的。
设计模式的4个基本要素:

  • 模式名称
  • 问题
  • 解决方案
  • 效果

之前已经接触过的设计模式有单例模式。

接口

接口,就是一个类,声明了若干方法,要求继承该接口的子类必须实现这些方法。
接口的作用:限制方法的名称及调用方法,隐藏了类的内部实现。

第一种写法:
实际只有指导的用处,并不能对子类进行约束:

class Interface(object):
    def method(self, arg):
        raise NotImplementedError

class RealizeInterface(Interface):
    def method(self, arg):
        print("Realize Interface %s" % arg)

上面代码中的Interface就是接口类,之后的RealizeInterface就是一个继承了该接口的子类。在子类里必须实现接口类里定义的方法。
问题:这里只有指导作用,没有约束的作用。只有在子类调用接口的方法的时候才会报错,而且本来也会报一个没有该方法的错。

第二种写法:
按这面的写法定义,就能起到约束的效果:

from abc import ABCMeta, abstractclassmethod

class Interface(metaclass=ABCMeta):  # 这是一个接口类,并且无法实例化
    @abstractclassmethod  # 这个方法必须被子类实现
    def method(self, arg):
        pass

class RealizeInterface(Interface):
    # def method(self, arg):  # 比如我就是不实现接口的方法
    #     print("Realize Interface %s" % arg)

    def hello(self, arg):
        print("Hello %s" % arg)

RealizeInterface()  # 实例化的时候就会报错

下面是报错信息:

TypeError: Can't instantiate abstract class RealizeInterface with abstract methods method

没有method这个抽象方法,就不能实例化RealizeInterface这个类。

设计模式六大原则

  • 开闭原则
  • 里氏(Liskov)替换原则
  • 依赖倒置原则
  • 接口隔离原则
  • 迪米特法则
  • 单一职责原则

开闭原则

开放封闭原则。代码要对扩展开放,对修改关闭。
所以,尽量要在不修改源码的情况的进行扩展。

里氏替换原则

所有引用基类的地方,必须透明的使用其子类的对象。
就是,我在写高层代码实现的时候,不用管子类。只需要去查看基类的方法就好了。所有父类有的方法,子类都可以出现。python里应该不用特别关注了,因为子类都是继承父类的,已经这么做好了。

依赖倒置原则

高层模块不应该依赖底层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程。
高层模块,比如是一个实例,然后去实现某个具体的方法、功能。
底层模块,比如就是子类
抽象,比如就是上面的接口类,它是抽象的
也就是说,功能的实现、子类都应该照着接口类来写。所有的代码都是依赖抽象也就是接口类的。虽然高层模块是通过调用底层模块,也就是子类来实现的。但是两者不要有依赖关系。两者都是照着接口类的要求去写就好了。
针对实现编程,就是去看一下子类的内容,然后实现它。这是不对的,不应该依赖子类的方法,不同的子类可能用不同的方法来实现功能。
针对接口编程,把高层函数里需要调用的方法都写在接口里。子类的代码要照着接口去实现。高层代码就只需要去查看接口类。

接口隔离原则

使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些它不需要的接口。
先说反例:现在有一个动物接口类,类里要实现在陆地上奔跑、在水中游泳、在天空飞翔这些方法。现在定义一个子类,比如老虎。接口要求我们子类必须实现接口类的全部的方法。问题来了,老虎只有奔跑的方法,没有其他方法,但是要求所有方法都要实现。
根据接口隔离,就需要我们把上面的接口分开,比如陆栖动物、水栖动物、飞行生物这么3个接口类。老虎类只需要继承陆气动物这个接口类就好了。如果要一只青蛙,或者鸭子,那么可以多继承,继承两个甚至三个。
为什么要这么做,结合里氏替换原则。如果我拿到一个对象,我不用关心这是老虎还是鸭子,我直接看接口类,有跑、有游、有飞。所以我认为3个方法都可以用。现在这里形象的用动物来举例子,实际中你看到的就是一段或者一大段代码,难以判断它哪些方法可以有,哪些方法应该没有,参考的就是接口类里有什么方法。
多继承,根据这个原则,就要用到多继承了。但是这里只是继承接口类,这是应该要这么做的,但是如果是其他的类,还是要避免多继承的。这是一个组合复用原则

迪米特法则

一个软件实体(如类、模块、函数)应当尽可能少的与其他实体发生相互作用。
就是2个字:解耦

单一职责原则

不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。

组合复用原则

这是第7个了。
组合复用原则也叫合成/聚合复用原则(CARP),就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。
这个原则的简短表述就是:要尽量使用组合,尽量不要使用继承。
组合就是将已有的对象纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能。

设计模式

先看一下,总共有哪些设计模式。挑了其中的一部分讲了一下。

设计模式分类

分为3个大类,每个里面再有具体的模式

  • 创建型模式:这类模式的目的是用来创建对象的
    • 工厂方法模式
    • 抽象工厂模式
    • 创建者模式
    • 原型模式
    • 单例模式
  • 结构型模式
    • 适配器模式
    • 桥模式
    • 组合模式
    • 装饰模式
    • 外观模式
    • 享元模式
    • 代理模式
  • 行为型模式
    • 解释器模式
    • 责任链模式
    • 命令模式
    • 迭代器模式
    • 中介者模式
    • 备忘录模式
    • 观察者模式
    • 状态模式
    • 策略模式
    • 访问者模式
    • 模板方法模式

简单工厂模式

在讲2种工厂模式之前,先学习一下简单工厂模式。
内容:不直接向客户端暴露对象创建的实现细节,而是通过一个工厂类来负责创建产品类的实例。
角色:

  • 工厂角色(Creator)
  • 抽象产品角色(Product)
  • 具体产品角色(Concrete Product)

优点:

  • 隐藏了对象创建的实现细节
  • 客户端不需要修改代码

缺点:

  • 违反了单一职责原则,将创建几种逻辑到一个工厂类里
  • 当添加新产品时,需要修改工厂类代码,违反了开闭原则

平时我们是自己通过调用类来创建一个对象的。在简单工厂模式里,它把创建对象的过程也放到了工厂类里。用户调用工厂类的创建方法,提供参数,拿到返回的创建好的对象:

from abc import abstractclassmethod, ABCMeta

# 先来定义接口类
class Dialog(metaclass=ABCMeta):
    @abstractclassmethod
    def say_hi(self):
        pass

# 用英语实现一个Dialog接口
class English(Dialog):
    def say_hi(self):
        print('Hi')

# 用中文实现一个Dialog接口
class Chinese(Dialog):
    def say_hi(self):
        print("你好")

# 通过工厂类里的创建方法来创建实例
class DialogFactory(object):
    def create_dialog(self, language):  # 提供不同的参数,返回不同的实例
        if language == 'English':  # 这里会有很多的elif,也是这个模式的一个缺点
            return English()
        elif language == 'Chinese':
            return Chinese()
        else:
            raise NameError(language)

# 这里是高层模块的实现方法
f = DialogFactory()  # 先实例化工厂类
d1 = f.create_dialog('English')  # 通过调用工厂类的方法,获得一个产品实例
d2 = f.create_dialog('Chinese')
d1.say_hi()
d2.say_hi()

在上面代码的基础上,现在要再加一个功能,比如要用火星文。底层还是把这个算作是中文的一种,实现的时候给中文实现的类里通过加一个参数来控制。

# 新的中文类的实现,扩展了一个功能,用参数控制
class Chinese(Dialog):
    def __init__(self, leetspeak=False):
        self.leetspeak = leetspeak

    def say_hi(self):
        if self.leetspeak:
            print("妳恏")
        else:
            print("你好")

# 上面添加了新产品,这里需要修改工厂类代码才能用
class DialogFactory(object):
    def create_dialog(self, language):
        if language == 'English':
            return English()
        elif language == 'Chinese':
            return Chinese()
        elif language == 'Mars':
            return Chinese(True)
        else:
            raise NameError(language)

# 高层的实现完全没有变化,原来的照样能用。新加的产品也像之前的产品一样使用
# 并且隐藏了底层的实现,看不出其实两个产品是同一个类是实现的,只是实例化的时候,通过参数来控制
f = DialogFactory()
d1 = f.create_dialog('English')
d2 = f.create_dialog('Chinese')
d1.say_hi()
d2.say_hi()
d3 = f.create_dialog('Mars')
d3.say_hi()

但是上面最后实现的时候,完全和其他方法一样,虽然在接口,其实是和中文实现是同一个类,只是参数不同,但是这些都隐藏了,最后实现的时候完全不用关心这些细节。
之前的已经实现的部分,不需要做任何修改,原来能用,扩展后依然能用。新扩展的内容的对用方法也和之前已经实现的内容完全一样(虽然底层的实现是不同的)。有点的第2条,客户端不用修改代码。
缺点的第一条,可能是这样的。上面的例子比较简单,只实现了一个方法。实际使用时,你的工厂类可能要实现很多方法,各种方法的逻辑都可能不同。违反单一职责原则。
缺点的第二条,在例子里扩展功能的时候已经很明显了,改了工厂类的代码。

工厂方法模式

在上面简单工厂模式的基础上来理解工厂方法模式。
内容:定义一个用处创建对象的接口(工厂接口),让子类决定实例化哪一个产品类。
角色:

  • 抽象工厂角色(Creator)
  • 具体工厂角色(Concrete Creator)
  • 抽象产品角色(Product)
  • 具体产品角色(Concrete Product)

工厂方法模式想不简单工厂模式,将每个具体产品都对应了一个具体工厂。
就是在简单工厂模式的基础上,把工厂类再抽象出来。现在不再把所有的产品都放到一个工厂里,而是先抽象一个工厂接口类,然后为每个产品定义一个具体的工厂类。

# 先抽象一个工厂类,就是工厂的接口类
class DialogFactory(object):
    def create_dialog(self):
        raise NotImplementedError

# 然后,对于每一种产品都单独建一个工厂
class EnglishDialogFactory(DialogFactory):
    def create_dialog(self):
        return English()

class ChineseDialogFactory(DialogFactory):
    def create_dialog(self):
        return Chinese()

class MarsDialogFactory(DialogFactory):
    def create_dialog(self):
        return Chinese(True)

f1 = EnglishDialogFactory()
f2 = ChineseDialogFactory()
d1 = f1.create_dialog()
d2 = f2.create_dialog()
d1.say_hi()
d2.say_hi()
f3 = MarsDialogFactory()
d3 = f3.create_dialog()
d3.say_hi()

试用场景:

  • 需要生产多种、大量复杂对象的时候
  • 需要降低耦合度的时候
  • 当系统中的产品种类需要经常扩展的时候

优点:

  • 每个具体产品都对应一个具体工厂类,不需要修改工厂类代码
  • 工厂类可以不知道它所创建的对象的类,python里可能没这个问题
  • 隐藏了对象常见的实现细节

缺点:每增加一个具体产品类,就必须增加一个相应的具体工厂类

抽象工厂模式

内容:定义一个工厂类接口,让工厂子类来创建一系列相关或相互依赖的对象。
角色:

  • 抽象工厂角色(Creator)
  • 具体工厂角色(Concrete Creator)
  • 抽象产品角色(Product)
  • 具体产品角色(Concrete Product)
  • 客户端(Client)

例子:首先有一系列的产品,有水果:苹果、香蕉,可以做成派或者果汁。那就需要先有一种水果,然后选择做成什么食物。
一种水果、一种加工方法,称作一个产品系列
苹果和香蕉,称作一个产品簇

# 抽象产品,这个有多个抽象产品
class Fruit(metaclass=ABCMeta):
    @abstractclassmethod
    def get(self):
        pass

class Food(metaclass=ABCMeta):
    @abstractclassmethod
    def make(self):
        pass

# 具体产品
class Apple(Fruit):
    def get(self):
        return "获得一个苹果"

class Banana(Fruit):
    def get(self):
        return "获得一个香蕉"

class Pie(Food):
    def make(self):
        return "做了一个派"

class Juice(Food):
    def make(self):
        return "榨了一杯果汁"

# 抽象工厂,工厂接口类。
# 接口要求实现2个步骤,获取水果,做成食物
class Factory(metaclass=ABCMeta):
    @abstractclassmethod
    def get_fruit(self):
        pass

    @abstractclassmethod
    def make_food(self):
        pass

# 具体工厂,子类里要创建一系列对象并返回
# 实现接口的2个方法,更多工厂就不写了
class ApplePieFactory(Factory):
    def get_fruit(self):
        return Apple()

    def make_food(self):
        return Pie()

# 客户端的实现
class Food(object):
    def __init__(self, fruit, food):
        self.fruit = fruit
        self.food = food

    def make_food(self):
        fruit = self.fruit.get()
        food = self.food.make()
        return "%s,%s" % (fruit, food)

# 把工厂类返回的产品实例传递给客户端的类
# 客户的类里对产品做各种实现
def get_product(factory):
    fruit = factory.get_fruit()
    food = factory.make_food()
    return Food(fruit, food)

# 这里是高层代码的实现
# 上面定义了一大堆底层,到这里调用起来就确实是很方便的
f1 = get_product(ApplePieFactory())
print(f1.make_food())

相比工厂方法模式,抽象工厂模式中的每个具体的工厂都生产一套产品。
工厂方法模式的工厂类只调用一个产品。抽象工厂模式的例子中,工厂类要调用多个产品。
适用场景:

  • 系统要独立于产品的创建于组合时
  • 强调一系列相关的产品对象的设计以便进行联合使用
  • 提供一个产品类库,想隐藏产品的具体实现时

优点:

  • 将客户端与类的具体实现相分离
  • 每个工厂创建了一个完成的产品系列,是的易于交换产品系统
  • 有利于产品的一致性(即产品之间的约束关系)

缺点:难以支撑性种类的抽象产品
比如上面的例子中是2个过程,如果要再加产品组合进来,必须可以选择堂食、外带。要按照这个设计模式的思路进行扩展的话,抽象工厂也就是接口类就需要加一个产品进去。基本上所有的底层都要改了。

建造者模式

这个不算工厂模式,但是和工厂模式非常相似。
内容:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
角色:

  • 抽象建造者(Builder)
  • 具体建造者(Concrete Builder)
  • 指挥者(Director)
  • 产品(Product)

建造者模式与抽象工厂模式相似,也用来创建复杂对象。主要区别是建造者模式着重一步步构造一个复杂对象,而抽象工厂模式着重于多个系列的产品对象。
多类产品,每类产品有好几种,这是抽象工厂模式
多类产品,没类产品只有一种,这是建造者模式
一类产品,没类产品有好几种,这是工厂方法模式

例子:来捏个脸,最终要生成的就是一张脸。然后要捏出一张脸,需要多个产品类,比如眼睛、耳朵、鼻子、嘴巴这里就用那么多。

# 产品
class Face(object):
    def __init__(self, eye=None, ear=None, nose=None, mouth=None):
        self.eye = eye
        self.ear = ear
        self.nose = nose
        self.mouth = mouth

    def __str__(self):
        return "%s, %s, %s, %s" % (self.eye, self.ear, self.nose, self.mouth)

# 建造者,相当于之前的工厂
class FaceBuilder(metaclass=ABCMeta):
    @abstractclassmethod
    def build_eye(self):
        pass

    @abstractclassmethod
    def build_ear(self):
        pass

    @abstractclassmethod
    def build_nose(self):
        pass

    @abstractclassmethod
    def build_mouth(self):
        pass

    # 这个接口方法是的多出来的,目的就是把上面的多个产品组合成最终的一个大产品
    @abstractclassmethod
    def get_face(self):
        pass

# 在这里组装
class GirlFaceBuilder(FaceBuilder):
    def __init__(self):
        self.face = Face()
    def build_eye(self):
        self.face.eye = "大眼睛"
    def build_ear(self):
        self.face.ear = "精灵耳朵"
    def build_nose(self):
        self.face.nose = "挺鼻梁"
    def build_mouth(self):
        self.face.mouth = "樱桃小嘴"
    def get_face(self):
        return self.face

# 指挥者,保证所有的产品都被调用了,保证各个产品的调用顺序
class FaceDirector(object):
    def build_face(self, builder):
        builder.build_eye()
        builder.build_ear()
        builder.build_nose()
        builder.build_mouth()
        return builder.get_face()

# 高端模块的调用
d1 = FaceDirector()  # 创建一个指挥者
b1 = GirlFaceBuilder()  # 创建一个建造者
f1 = d1.build_face(b1)  # 调用指挥者的方法,把建造者作为参数传入
print(f1)

适用场景:

  • 当创建复杂对象的算法(Director)应该独立于改对象的组成部分以及它们的配装方式(Builder)时
  • 当构造过程允许被构造的对象有不同的表示时(不同Builder)

优点:

  • 隐藏了一个产品的内部结构和装配过程
  • 将构造代码与表示代码分开
  • 可以对构造过程进行更精细的控制

Builder隐藏了内部结构(工厂模式也有)。多了个Director,多隐藏了装配过程。

创建型模式小结

上面那么多的模式,掌握工厂方法模式就好了,其他的看不懂就算了。
其他模式比使用工厂方法模式更灵活,但是它们更加复杂。通常,设计以使用工厂方法模式开始,并且当设计者发现需要更大的灵活性时,设计便会向其他创建型模式演化。

依赖于继承的创建型模式:工厂方法模式
依赖于组合的创建型模式:抽象工厂模式、创建者模式

单例模式

内容:保证一个类只有一个实例,并提供一个访问它的全局访问点。
角色:单例(Singleton)
适用场景:当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
优点:

  • 对唯一实例的受控访问
  • 单例模式相当于全局变量,但防止了命名空间被污染

与单例模式功能相似的概念:全局变量、静态变量(方法)。既然3个概念的作用差不多,单例模式要比另外2个要好,所以需要用到上面的概念的时候,可以考虑用单例模式。
python中的单例模式

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

这里的做法是把单例模式的类做成一个基类,只要继承了这个类的子类就是单例模式的。之前用过单例,是直接在需要单例的类里定义一个new方法。两种方法都是可以的。

适配器模式

内容:将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
角色:

  • 目标接口(Target)
  • 待适配的类(Adaptee)
  • 适配器(Adapter)

两种实现方式:

  • 类适配器:使用多继承
  • 对象适配器:使用组合

这里的例子还是以上面简单工厂里的例子来扩展,现在有扩展了一个功能,但是没有继承我们的接口,或者是继承了别的接口,总之就是没有按照创建型模式规定的来:

# 再写一个实现,但是没有按照接口写,或者没有按照我们的接口写
class British(object):
    def say_hello(self):
        print('Hello')

假如这是一段很长很复杂的代码,本身没问题,就是没有写成我们要的样子。总之就是要用,但是不能改源码。
类适配器:

# 工厂类
class DialogFactory(object):
    def create_dialog(self, language):
        if language == 'English':
            return English()
        elif language == 'British':
            return BritishDialog()  # 类适配器的这一层已经和别的一样了
        elif language == 'Chinese':
            return Chinese()
        elif language == 'Mars':
            return Chinese(True)
        else:
            raise NameError

# 类适配器
class BritishDialog(Dialog, British):
    def say_hi(self):
        return self.say_hello()

使用类适配器的话,如果有好几个需要适配的类,我们就是对应写几个适配器。有一个类就要写一个适配器。
对象适配器:

# 工厂类
class DialogFactory(object):
    def create_dialog(self, language):
        if language == 'English':
            return English()
        elif language == 'British':
            return BritishDialog(British())  # 对象适配器的这一层和别的都不一样
        elif language == 'Chinese':
            return Chinese()
        elif language == 'Mars':
            return Chinese(True)
        else:
            raise NameError

# 对象适配器
class BritishDialog(Dialog):
    def __init__(self, dialog):
        self.dialog = dialog

    def say_hi(self):
        return self.dialog.say_hello()

f = DialogFactory()
d4 = f.create_dialog('British')
d4.say_hi()

这种方法,无论写了几个非标准的sya_hello方法,都适配成了say_hi方法了。但是用对象适配器的话,实例化产品的方法就和别人不一样了,具体看工厂类里的返回实例的那行。
缺点就是实例化的方法和别的不一样,这里有工厂方法隐藏了,也就没什么问题。如果没有采用工厂模式,那么高层代码在写的时候就会和别的不一样。

适用场景:

  • 想适用一个已经存在的类,而它的接口不符合你的要求
  • 想适用一些已经存在的子类,但不可能对每一个都进行子类化以匹配他们的接口。对象适配器可以适配它的父类接口。这里就是对象适配器的例子,适配了所有的方法

组合模式

内容:将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
角色:

  • 抽象组件(Component)
  • 叶子组件(Leaf)
  • 复合组件(Composite)
  • 客户端(Clinet),课上和下面的例子里并没有看到客户端

每一个组件可以和其他一个或多个组件进行组合,组合后成为一个新的组件。组合的组件也是一个组件,可以和别的组件继续组合下去。无论是你叶子组件还是复合组件。
下面的例子,先定义了一个抽象组件,然后实现了叶子组件(即基础图形)。然后通过基础图形,可以组合出各种复合图形。复合图形还可以再复合:

from abc import abstractclassmethod, ABCMeta

# 抽象组件,图形的接口类
class Line(metaclass=ABCMeta):
    @abstractclassmethod
    def draw(self):  # 画出图形的接口
        pass

    @abstractclassmethod
    def add(self, line):  # 进行组合的接口
        pass

    def get_children(self):
        pass

# 叶子组件,基础图形
class ShortLine(Line):

    def __init__(self):
        self.line = '*** ***'

    def draw(self):
        print(self.line)

    def add(self, line):  # 基础图形,不能添加
        raise TypeError

    def get_children(self):  # 不能添加,自然也不会有孩子
        raise TypeError

class LongLine(Line):  # 还是一个基础图形

    def __init__(self):
        self.line = '*******'

    def draw(self):
        print(self.line)

    def add(self, line):
        raise TypeError

    def get_children(self):
        raise TypeError

# 复合组件,各种基础图形或者复合图形组合后的图形
class Pic(Line):

    def __init__(self):
        self.children = []  # 列表里存了复合了哪些图形

    def draw(self):
        for i in self.children:
            i.draw()

    def add(self, line):  # 可以组合各种图形(基础图形或组合图形)
        self.children.append(line)

    def get_children(self):
        return self.children

# 先把基础图形画出来看看
short_line = ShortLine()
lang_line = LongLine()
print("基础图形")
short_line.draw()
lang_line.draw()

# 画复合图形
qian = Pic()
qian.add(lang_line)
qian.add(lang_line)
qian.add(lang_line)
print("天乾")
qian.draw()

kan = Pic()
kan.add(short_line)
kan.add(lang_line)
kan.add(short_line)
print("水坎")
kan.draw()

# 用组合后的图形继续组合
song = Pic()
song.add(qian)
song.add(kan)
print("天水讼,把2个图形组合起来")
song.draw()

应用狭窄,只能用到树形结构上。目的简单,用简单的对象组合生成复杂的对象,并且组合后行为上还能保持一致,。
适用场景:

  • 表示对象的“部分-整体”层次结构(特别是结构是递归的)
  • 希望用户忽略组合对象与单个对象的不同,用户统一的使用组合结构中的所有对象

优点:

  • 定义了包含基本对象和组合对象的类层次结构
  • 简化客户端代码,即客户端可以一致的使用组合对象和单个对象
  • 更容易增加新类型的组件

缺点:很难限制组合中的组件

代理模式

内容:为其他对象提供一种代理以控制对这个对象的访问。
角色:

  • 抽象实体(Subject)
  • 实体(RealSubject)
  • 代理(Proxy)

适用场景:

  • 远程代理:为远程的对象提供代理。有个对象实际是在另外一个系统里的,但是要让高层模块看起来这个对象就像在自己的系统里一样。
  • 虚代理:根据需要创建很大的对象。根据需要创建对象,比如在手机上看一个很大的文章,你还没有浏览到的内容里的gif动图,就没有必要加载。这时就为这个对象创建一个虚代理,只有当页面浏览到这里的时候才去加载。加载的时候才创建对象,不加载的时候就先不加载。
  • 保护代理:控制对原始对象的访问,用于对象有不同访问权限时。不同的情况,对于一个对象需要有不同的访问权限。不直接把对象暴露给客户端。写几个的代理,提供给客户端访问。客户端只能提供提供的代理访问,权限就是代理提供的权限。

优点:

  • 远程代理:可以隐藏对象位于远程地址空间的事实
  • 虚代理:可以进行优化,例如根据要求创建对象
  • 保护代理:允许在访问一个对象时有一些附加的内务处理

下面是代理模式的例子:

from abc import ABCMeta, abstractmethod

class Subject(metaclass=ABCMeta):
    @abstractmethod
    def get_content(self):
        pass

class RealSubject(Subject):
    def __init__(self, filename):
        print("读取文件内容:%s" % filename)
        f = open(filename)
        self.content = f.read()
        f.close()

    def get_content(self):
        return self.content

# 这个不是远程代理,远程代理没举例
# 下面是个没任何作用的代理
# 只是给上面的实体类封了一层壳,功能和收么的类完全一样
# 不过好歹是实现了代理,不直接把对象暴露出来
class ProxyA(Subject):
    def __init__(self, filename):
        self.subj = RealSubject(filename)

    # 调用代理的一个方法,实际就是调用self.subj的对应的方法
    # 这里写死了,应该可以做个反射,全部指过去
    def get_content(self):
        return self.subj.get_content()

# 虚代理
# 这个代理最初只保存一下文件名,但是不实例化
# 只有调用了get_content方法时,才会检查是否有实例,如果没有才创建对象
class ProxyB(Subject):
    def __init__(self, filename):
        self.filename = filename
        self.subj = None

    def get_content(self):
        if not self.subj:
            self.subj = RealSubject(self.filename)
        return self.subj.get_content()

# 保护代理
# 这个类实现的不是很好
class ProxyC(Subject):
    def __init__(self, filename):
        self.subj = RealSubject(filename)

    def get_content(self):  # 假设这个就是权限受限时提供的文件内容
        return "??????"

通过代理,可以给原来的类添加或者修改功能,但是又不用修改原来的代码。添加和修改功能,例子里貌似没体现,不过只要去代理里面改代码就能实现了。代理里拿到了实体的对象后,可以直接在代理里根据需要修改对象。

责任链模式

内容:是多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

角色:

  • 抽象处理者(Handler)
  • 具体处理者(ConcreteHandler)
  • 客户端(Client)

举例:

  • 请假:同样是请假,如果短假,主管直接批就好了。如果假期太长,主管处理不了,就需要找经理批。如果再长一点,经理也批不了,就要找总经理批了。
    主管批(3天内)-->经理批(7天内)-->总经理批
    在这个情景里,主管、经理、总经理都可以处理这个请求,他们构成一个责任链。先把请求提交到责任链里级别低的人,如果请求在这在范围内,给予处理。如果请求不在职责范围内,则传给责任链里后面的那个类。
  • Javascript的事件浮升机制:这个也是责任链模式。内存元素和外层元素都绑定了onclick事件,点击后是调用内层元素的onclick事件。如果内存元素没有onclick事件,则往外一层的元素里去找。

请假的例子

from abc import ABCMeta, abstractmethod

class Handler(metaclass=ABCMeta):
    @abstractmethod
    def handle_leave(self, day):
        pass

class Supervisor(Handler):
    def __init__(self):
        self.successor = DepartmentManagerHandler()
    def handle_leave(self, day):
        if day < 3:
            print("主管批准%d天假期" % day)
        else:
            print("主管无权准假")
            self.successor.handle_leave(day)

class DepartmentManagerHandler(Handler):
    def __init__(self):
        self.successor = GeneralManagerHandler()
    def handle_leave(self, day):
        if day < 7:
            print("部门经理批准%s天假期" % day)
        else:
            print("部门经理无权准假")
            self.successor.handle_leave(day)

class GeneralManagerHandler(Handler):
    def handle_leave(self, day):
        if day < 15:
            print("总经理批准%d天假期" % day)
        else:
            print("驳回")

day = 10
h = Supervisor()
h.handle_leave(day)

Javascript的事件浮升机制

from abc import ABCMeta, abstractmethod

class Handler(metaclass=ABCMeta):
    @abstractmethod
    def add_event(self, func):
        """增加事件的方法"""
        pass

    @abstractmethod
    def handle(self):
        """处理事件的方法"""
        pass

class BodyHandler(Handler):
    def __init__(self):
        self.func = None

    def add_event(self, func):
        self.func = func

    def handle(self):
        """最顶端的handle方法单独写"""
        if self.func:
            return self.func()
        else:
            print("没有事件处理")

class ElementHandler(Handler):
    def __init__(self, successor):
        self.func = None
        self.successor = successor

    def add_event(self, func):
        self.func = func

    def handle(self):
        """其他handle方法都是通用的,关键是下面的客户端要写好对应关系"""
        if self.func:
            return self.func()
        else:
            return self.successor.handle()

# 客户端
body = {'type': 'body', 'name': 'body', 'father': None, 'children': []}
div = {'type': 'div', 'name': 'div', 'children': [], 'father': body}
a = {'type': 'a', 'name': 'a', 'children': [], 'father': div}
body['children'].append(div)
div['children'].append(a)
body['event_handler'] = BodyHandler()
div['event_handler'] = ElementHandler(div['father']['event_handler'])
a['event_handler'] = ElementHandler(a['father']['event_handler'])

def attach_event(element, func):
    element['event_handler'].add_event(func)

# 高端模块调用
# 这里相当于写了事件处理函数
def func_body():
    print("这是给body的函数")

def func_div():
    print("这是给div的函数")

def func_a():
    print("这是给a的函数")

# 这里相当于把事件处理函数绑定到标签上
attach_event(body, func_body)
attach_event(div, func_div)
# attach_event(a, func_a)

a['event_handler'].handle()

适用场景:

  • 有多个对象可以处理一个请求,具体哪个对象处理由运行时决定
  • 在不明确接收者的情况下,向多个对象中的一个提交一个请求

优点:降低耦合度。一个对象无需知道是其他哪一个对象处理其请求

缺点:请求不保证被接收。链的末端没有处理或链配置错误

迭代器模式

内容:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示

实现方法:__iter____next__

观察者模式

内容:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。观察者模式又称“发布-订阅”模式

角色:

  • 抽象主题(Subject)
  • 具体主题(ConcreteSubject)——发布者
  • 抽象观察者(Observer)
  • 具体观察者(ConcreteObserver)——订阅者

适用场景:

  • 当一个抽象模型有两方面,其中一个方面依赖于另一个方面。将这两者封装在独立对象中以使它们可以各自独立地改变和复用。
  • 当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。
  • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不希望这些对象是紧密耦合的。

优点:

  • 目标和观察者之间的抽象耦合最小
  • 支持广播通信

缺点:多个观察者之间互不知道对方存在,因此一个观察者对主题的修改可能造成错误的更新。

这个模式主要的用途就是发布-订阅。

策略模式

内容:定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。本模式使得算法可独立于使用它的客户而变化。
不同的算法,在策略模式里也可以理解为不同的策略。

角色:

  • 抽象策略(Strategy)
  • 具体策略(ConcreteStrategy)
  • 上下文(Context)

下面的例子,先定义了一个抽象策略,就是排序。然后写了2个排序算法,作为2个具体策略。然后看下上文类,在创建对象的时候,可以传入一个策略。之后可以通过体格的set方法修改策略。类里还提供了一个do方法,使用策略进行排序:

from abc import ABCMeta, abstractmethod
import random

class Sort(metaclass=ABCMeta):
    @abstractmethod
    def sort(self, data):
        pass

class QuickSort(Sort):

    @staticmethod
    def quick_sort(li):
        """这个是之前讲排序的时候的快排函数"""

        def _quick_sort(li, left, right):
            if left < right:
                mid = partition(li, left, right)
                # print(li)
                _quick_sort(li, left, mid - 1)
                _quick_sort(li, mid + 1, right)
            return li

        def partition(li, left, right):
            tmp = li[left]
            while left < right:
                while left < right and li[right] >= tmp:
                    right -= 1
                li[left] = li[right]
                while left < right and li[left] <= tmp:
                    left += 1
                li[right] = li[left]
            # 到这里left和right是一样的了,所以用left少个字母
            li[left] = tmp
            return left

        left = 0
        right = len(li) - 1
        return _quick_sort(li, left, right)

    def sort(self, data):
        print("使用快速排序算法进行排序")
        return self.quick_sort(data)

class PythonSort(Sort):
    def sort(self, data):
        data.sort()
        print("使用Python内置方法进行排序")
        return data

class Context(object):
    def __init__(self, data, strategy=None):
        self.data = data
        self.data_copy = None
        self.strategy = strategy  # 实例化的时候可以把策略传进来

    def set_strategy(self, strategy):
        """提供了一个方法,可以修改策略"""
        self.strategy = strategy

    def do_strategy(self):
        if self.strategy:
            self.data_copy = self.data.copy()
            print("排序前:", self.data_copy)
            self.strategy.sort(self.data_copy)
        else:
            raise TypeError

# 高端模块的调用
li = list(range(10))
random.shuffle(li)  # 洗牌

context = Context(li, PythonSort())
context.do_strategy()
print("排序后:", context.data_copy)
context.set_strategy(QuickSort())  # 设置换个策略
context.do_strategy()  # 再在做一个排序
print("排序后:", context.data_copy)

上面的例子实现了方便的在多种策略之间进行切换。

适用场景:

  • 许多相关的类仅仅是行为有差别
  • 需要使用一个算法的不同变体
  • 算法使用了客户端无需知道的数据
  • 一个类中的多种行为以多个条件语句的形式存在,可以将这些行为封装入不同的策略类中

优点:

  • 定义了一系列可重用的算法和行为
  • 消除了一些条件语句
  • 可以提供相同行为的不同实现

缺点:

  • 客户必须了解不同的策略
  • 策略与上下文之间的通信开销
  • 增加了对象的数目

模板方法模式

内容:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

角色:

  • 抽象类(AbstractClas):定义抽象的原子操作(钩子操作),实现一个模板方法作为算法的骨架。
  • 具体类(ConcreteClass):实现原子操作

适用场景:

  • 一次性实现一个算法的不变的部分
  • 各个子类中的公共行为应该被提取出来,并集中到一个公共父类中,以避免代码重复
  • 控制子类扩展

举例,比如处理文件、数据库、Socket,都是3步:打开、处理、关闭。在抽象类里就定义好这3个接口,另外再写一个实现的类process,所有的子类用的都是process的实现过程。流程在父类的process里定义,子类只负责实现各自不同的接口:

rom abc import abstractclassmethod, ABCMeta

class IOHandler(metaclass=ABCMeta):
    @abstractclassmethod
    def open(self, name):
        pass
    @abstractclassmethod
    def deal(self, change):
        pass
    @abstractclassmethod
    def close(self):
        pass

    def process(self, name, change):
        self.open(name)
        self.deal(change)
        self.close()

class FileHandler(IOHandler):
    def open(self, name):
        self.file = open(name, 'a')

    def deal(self, change):
        self.file.writelines([change])

    def close(self):
        self.file.close()

# 高端模块的实现
f = FileHandler()
f.process('test.txt', 'TEST')