Facade 门面模式

设计模式 2020-04-10 3275 字 1114 浏览 点赞

起步

门面模式属于结构型,旨在定义一组高层接口让子系统更易用。

照《Learning Python Design Patterns, 2nd Edition.pdf》一书的说法就是:It delegates the client’s requests to the appropriate subsystem objects using composition

这个模式太简单常见了,我都不知道怎么说起……

如果没有门面模式

现在来假设一个场景,项目中需要一个功能,使得项目可以根据配置文件连接不同的数据库。按“将大象放进冰箱”的逻辑,解析配置文件分这几步骤:1. 确定配置文件存在;2. 加载配置文件;3. 拼接 uri。

如果要求配置文件的格式为:

DB:
    TYPE: mysql
    DRIVER: pymysql
    USER: zhong
    PASSWORD: 123456
    HOST: 192.168.111.136
    PORT: 3306
    DBNAME: tmp

基于单一职责原则,代码可做如下设计:

class PathUtils(object):
    @staticmethod
    def is_exists(path):
        return os.path.exists(path)

class ConfigLoader(object):
    def __init__(self, path):
        self._path = path
    
    def load(self) -> dict:
        # ... 加载配置文件的逻辑
        return {}

class Logger(object):
    @staticmethod
    def info(string):
        logging.info(f"{time.time()}_reason: {string}")

class DBConfigUtils(object):
    def __init__(self, db_info: dict):
        self._db_info = db_info

    def make_uri(self):
        # mysql+pymysql://user:password@ip:port/db_name
        return (
            f'{self._db_info["TYPE"]}+{self._db_info["DIRVER"]}://'
            f'{self._db_info["USER"]}:{self._db_info["PASSWORD"]}@'
            f'{self._db_info["HOST"]}:{self._db_info["PORT"]}/'
            f'{self._db_info["DBNAME"]}'
        )

当数据层需要连接数据库时,需要数据库 uri。那么客户端代码如下:

class Dao(object):
    def __init__(self, path):
        self.set_up(path)
    
    def set_up(self, path):
        # 确定配置文件是否存在
        if not PathUtils.is_exists(path):
            raise ValueError
        # 加载配置文件
        config = ConfigLoader(path)
        global_config = config.load()
        # 日志输出
        Logger.info(f"成功加载配置")
        # 组装 db uri
        db_util_config = DBConfigUtils(global_config["DB"])
        uri = db_util_config.make_uri()
        
        # 创建引擎
        engine = create_engine(uri)
        ...

可以看到,在 Dao.set_up 方法中,由于需要获取 uri 而不得不调用多个对象方法,以组合的方式拿到了 db uri,这才创建了 engine。但对于数据库来说,set\_up 通常不关心配置文件在哪儿,需要调用什么解析器来解析配置文件等等;它只是需要 db uri 而已。

门面模式

在上述示例中,配置功能的开发者给出了很细粒度的接口,允许程序员自由控制程序流程。但绝大多数情况下,这种细粒度接口带来了使用上的麻烦,很容易客户端使用不当导致拿不到正确的 uri。

门面模式要求接口开发者在设计细粒度接口之后,可再行设计冗余的门面接口,来提供更易用的接口。用代码说话就是:

class PathUtils(object): ...

class ConfigLoader(object): ...

class Logger(object): ...

class DBConfigUtils(object): ...

def db_uri(path):
    # 确定配置文件是否存在
    if not PathUtils.is_exists(path):
        raise ValueError
    # 加载配置文件
    config = ConfigLoader(path)
    global_config = config.load()
    # 日志输出
    Logger.info(f"成功加载配置")
    # 组装 db uri
    db_util_config = DBConfigUtils(global_config["DB"])
    uri = db_util_config.make_uri()
    return uri

客户端使用:

class Dao(object):
    ...
    
    def set_up(self, path):
        uri = db_uri(path)
        ...

总结

门面模式乍一看很像封装,但跟封装不同。门面模式要求客户端既可访问粒度细的底层接口,又可访问粒度大的顶层接口,供客户端随意切换选择。从而保障了代码的通用性和易用性

通常门面模式不负责添加新功能,而是组合现有接口实现一种默认行为。这种设计方式在许多库中都能看到。

参考



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论