python 实现 外观模式

本文的目录地址

本文的代码地址

本质上,外观(Facade)是在已有的复杂系统之上实现的一个抽象层。

下图演示了外观的角色。从图中展示的类可知,仅Computer类需要暴露给客户端代码。客户端仅执行Computer的start()方法。所有其他复杂部件都由外观类Computer来维护。

现实生活的例子

在现实中,外观模式相当常见。当你致电一个银行或公司,通常是先被连线到客服部门,客服职员在你和业务部门及帮你解决具体问题的职员之间充当一个外观的角色。

也可以将汽车或摩托车的启动钥匙视为一个外观。外观是激活一个系统的便捷方式,系统的内部则非常复杂。

软件的例子

django-oscar-datacash模式是Django的一个第三方组件,用于集成DataCash支付网关。该组件有一个Gateway类,提供对多种DataCash API的细粒度访问。在那之上,它也包含一个Facade类,提供粗粒度API(提供给那些不需要处理细节的人),并针对审计目的提供保存事务的能力。

Caliendo是一个用于模拟Python API的接口,它包含一个facade模块。该模块使用外观模式来完成许多不同但有用的事情(比如缓存方法),并基于传给顶层Facade方法的输入对象决定返回什么方法。

应用案例

使用外观模式的最常见理由是为一个复杂系统提供单个简单的入口点。引入外观之后,客户端代码通过简单地调用一个方法/函数就能使用一个系统。同事,内部系统并不会丢失任何功能,外观只是封装了内部系统。

不把系统的内部功能暴露给客户端代码有一个额外的好处:我们可以改变系统内部,但客户端代码不用关心这个改变,也不会受这个改变的影响。客户端代码不需要进行任何改变。

如果你的系统包含多层,外观模式也能派上用场。你可以为每一层引入一个外观入口点,并让所有层级通过它们的外观相互通信。这提高了层级之间的松耦合性,尽可能保持层级独立。

实现

假设我们想使用多服务进程方式实现一个操作系统,多服务进程的操作系统有一个极小的内核,称为微内核(microkernel),它在特权模式下运行。系统的所有其他服务都遵从一种服务架构(驱动程序服务器、进程服务器、文件服务器等)。每个服务进程属于一个不同的内存地址空间,以用户模式在微内核之上运行。这种方式的优势是操作系统更能容错、更加可靠、更加安全。例如,由于所有驱动程序都以用户模式在一个驱动服务进程之上运行,所以某个驱动程序中的一个bug并不能让整个系统崩溃,也无法影响到其他服务进程。其劣势则是性能开销和系统编程的复杂性,因为服务进程和微内核之间,还有独立的服务进程之间,使用消息传递方式进行通信。消息传递比宏内核(如Linux)所使用的共享内存模型更加复杂。

我们从Server接口(这里的接口并非指语法上的interface,而是指一个不能直接实例化的类)开始实现,使用一个Enum类型变量来描述一个服务进程的不同状态,使用adc模块(abc-abstract base classes)来禁止对Server接口直接进行初始化,并强制子类实现关键的boot()和kill()方法。这里假设每个服务进程的启动、关闭及重启都相应地需要不同的动作。如果你以前没用过adc模块,请记住以下几个重要事项。

  • 我们需要使用metaclass关键字来继承ABCMeta。
  • 使用@abstractmethod修饰器来声明Server的所有子类都应(强制性地)实现哪些方法。

尝试移除一个子类的boot()或kill()方法,看看会发生什么(会报错,基类有抽象方法子类必须实现)。移除@abstractmethod修饰符之后再试试(不会报错)。

State = Enum('State', 'new running sleeeping restart zombie')

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

一个模块化的操作系统可以有很多有意思的服务进程,包括文件服务进程、进程服务进程、身份验证服务进程、网络服务进程和图形/窗口服务进程等。下面这个例子包含两个存根服务进程(FileServer和ProcessServer)。除了Server接口要求实现的方法之外,每个服务进程还可以具有自己特有的方法。例如,FileServer有一个create_file()方法用于创建文件,ProcessServer有一个create_process()方法用于创建进程。

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))

OperatingSystem类是一个外观。__init__()中创建所有需要的服务进程实例。start()方法是系统的入口点,供客户端代码使用。如果需要,可以添加更多的包装方法作为服务的访问点,比如包装方法create_file()和create_process()。从客户端的角度来看,所有服务都是由OperatingSystem类提供的。客户端并不应该被不必要的细节所干扰,比如,服务进程的存在和每个服务进程的责任。

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)

在下面的完整代码清单中(文件facade.py),可以看到许多模拟的类和服务进程,它们的存在是为了让读者了解系统运转要求哪些抽象(User、Process、File等)和服务进程(WindowsServer和NetworkServer等)。执行这个例子会显现两个存根服务进程的启动信息。


外观类OperatingSystem起到了很好的作用。客户端代码可以创建文件和进程,而无需知道操作系统的内部细节,比如,多个服务进程的存在。准确点说是客户端可以调用方法来创建文件和进程,但是目前它们是模拟的。


猜你喜欢

转载自blog.csdn.net/hbu_pig/article/details/80656076