模式定义:
此模式 属于 结构型模式, 通过 类间的 组合方式 实现 不同维度的 2种数据 相互交互; 2种维度的类 彼此 分开 拓展子类, 不会耦合在一起;
注意点:
网上 搜索 的 多数 教程 都 讲到 抽象 和 实现 这2个专业术语; 而此2个术语 很容易 让人引起误会,把 此 抽象 和 面向对象 里的 抽象出 父类 的 抽象概念 混淆; 其实 此模式中 不用 管 抽象 和 实现这2个 概念,如果过于在意 反而 无法理解此模式;
只需 理解为 2个不同的 维护 相互配合工作即可; 如 此教程中 的 形状 和 颜色 示例, 就 直接理解成 形状维度 和 颜色维度; 形状维度 内部类属性 可以调用 颜色对象,实现 颜色渲染功能;(如果 非要按照那2个概念来说,形状 类 为 抽象角色, 颜色 类 为 实现角色)
代码或现实中的例子:
- 5个男低音和 4个女高音 进行 男女 随机匹配 进行 组合唱歌; 考虑到 彼此水平的不同,和 配合差异,可能会 唱出 4*5=20 首不同的歌;
- 不同类型的坦克 可以有 不同颜色的 伪装;
- 从数据库查询完数据后,存数据到缓存中, 用户可以 指定 数据库的类型(mysql,sqlite,oracle),和 缓存的类型(redis,file,mongo,memcache) ; 实现见下方 示例代码
该模式关键的角色(不要 刻意在乎 抽象 和具体 这2个概念):
- 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
- 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
该模式的主要优缺点如下:
优点:
- 开闭原则;你可以 新增2个维度的 任何一部分, 且它们之间不会相互影响。
- 拓展性增强,增加任何一个维度 数据,都不会 影响其他维度
- 符合合成复用原则
- 其实现细节对客户透明
缺点:
- 每个 维度 对外接口(类方法等) 的问题, 如果 要客户端 无感知区别的话,则每个维度子类 对外接口 都要相同,否则 无法做到 客户透明(用户还要查看对应子类是否有对应方法); 这也 可能导致 为了刻意一致而一致,从而 影响整体代码结构;
何时使用此模式:
当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。
桥接模式通常适用于以下场景。
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
桥接模式的一个常见使用场景就是替换继承。我们知道,继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能,但这也是继承的一大缺点。
因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。因此,在设计模式中,有一个原则为优先使用组合/聚合,而不是继承。
很多时候,我们分不清该使用继承还是组合/聚合或其他方式等,其实可以从现实语义进行思考。因为软件最终还是提供给现实生活中的人使用的,是服务于人类社会的,软件是具备现实场景的。当我们从纯代码角度无法看清问题时,现实角度可能会提供更加开阔的思路。
示例代码部分
# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren
All rights reserved
create time '2020/11/10 16:54'
Usage:
业务需求:
从数据库查询完数据后,存数据到缓存中, 用户可以 指定 数据库的类型,和 缓存的类型
组合模式实现方式:
通过分析需求 得知 2个不同维度的 数据 需要 实现 笛卡尔积 数量级 的组合方式;并且 如果考虑后续 可能增加的 维度数据,组合数量会更多;
通过 数据层 连接不同数据库 和 用不同缓存方式 缓存 实现 桥接模式;
一个维度: 硬盘数据存储技术(不同的数据库): mysql,sqlite,oracle
另一个维度: 缓存存储方式:redis,file,mongo,memcache
如上 2个维度 需要 D(3)*D(4)=12种组合方式;
实现细节: 从数据库查询完数据后,存数据到缓存中, 也就是 在 数据库 相关类中 设置 缓存属性,值为 缓存实例对象,这个 缓存属性 就是 桥接模式 中的桥梁,
数据库类存缓存的操作,通过这个 缓存属性(桥梁) 到对应的 缓存 对象中 执行缓存操作; 也就是 数据库类 将缓存的 操作 委托给 缓存对象实现;
继承实现方式:
DB类
MysqlRedis(DB),MysqlFile(DB),MysqlMongo(DB),OracleRedis(DB),OracleFile(DB),OracleMongo(DB),... 共12子类
2种实现方式比较:
继承实现: 类数量实现 D(3)*D(4)+1(Db父类)=13个类
桥接模式: 类数量实现 1(Db父类)+3(Db子类)+1(Cache父类)+4(Cache子类)=9个类
不必多言,继承实现 方式 完全体现了 码农搬砖 4个字的核心思想,重复的做着大量 耗时,价值低廉,易错 的事情;且 在 任何维度 新增数据时 都要 增加大量新的子类,
而 桥接模式 则 只需 增加一个 子类即可
"""
class Db:
db_type = ''
def __init__(self, cache_obj):
"""
Db库初始化
:param cache_obj:
"""
self.cache = cache_obj
def query(self):
"""
从 数据库查询数据
:return:
"""
self.set_cache()
def set_cache(self):
"""数据存入缓存"""
print(f'{self.db_type}数据库 开始调用缓存类型:{self.cache.__class__.__name__} 的对象 把数据存入缓存')
self.cache.set_cache()
class Mysql(Db):
db_type = 'mysql'
def query(self):
"""mysql的查询数据功能"""
print(self.query.__doc__)
super(Mysql, self).query()
class Sqlite(Db):
db_type = 'sqlite'
def query(self):
"""Sqlite的查询数据功能"""
print(self.query.__doc__)
super(Sqlite, self).query()
class Cache:
"""缓存父类"""
def set_cache(self):
"""设置缓存"""
pass
class Redis(Cache):
def set_cache(self):
"""redis设置缓存"""
print(self.set_cache.__doc__)
class Memcache(Cache):
def set_cache(self):
"""memcache设置缓存"""
print(self.set_cache.__doc__)
if __name__ == '__main__':
"""
如上 示例代码 只实现了 部分 类型的类,多了 也没啥意义,理解思想即可
"""
print('mysql+redis组合\n')
cache_obj = Redis()
db_obj = Mysql(cache_obj)
db_obj.query()
print('*' * 10, '\n')
print('mysql+Memcache组合\n')
cache_obj = Memcache()
db_obj = Mysql(cache_obj)
db_obj.query()
print('*' * 10, '\n')
print('sqlite+Memcache组合\n')
cache_obj = Memcache()
db_obj = Sqlite(cache_obj)
db_obj.query()
print('*' * 10, '\n')
结果演示:
总结:
此模式 归根到底 便是 通过 不同类间 进行组合方式,一个类 指使另一个类去做对应的工作;
此模式 在 遇到 笛卡尔积 类型的 问题时 比较有效;
相关链接:
https://refactoringguru.cn/design-patterns/bridge
https://refactoringguru.cn/design-patterns/bridge/python/example
http://c.biancheng.net/view/1364.html
https://blog.csdn.net/weixin_41431904/article/details/80783839