起步
门面模式属于结构型,旨在定义一组高层接口让子系统更易用。
照《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)
...
总结
门面模式乍一看很像封装,但跟封装不同。门面模式要求客户端既可访问粒度细的底层接口,又可访问粒度大的顶层接口,供客户端随意切换选择。从而保障了代码的通用性和易用性。
通常门面模式不负责添加新功能,而是组合现有接口实现一种默认行为。这种设计方式在许多库中都能看到。
参考
- 感谢 极客时间王争老师的《设计模式之美:门面模式》
- 感谢《Learning Python Design Patterns, 2nd Edition.pdf》
还不快抢沙发