重温设计模式--9、桥接模式


在这里插入图片描述

桥接模式的概念

  • 桥接模式(Bridge Pattern)是一种结构型设计模式,它的主要目的是将抽象部分与它的实现部分分离,使它们都可以独立地变化。这种模式就像是在抽象和实现之间搭建了一座“桥”,让它们可以通过这座桥来进行协作,而不是将它们硬编码在一起。
  • 例如,假设有一个图形绘制系统,需要绘制不同颜色和形状的图形。如果不使用桥接模式,可能会创建很多不同的类来表示各种颜色和形状的组合,如“RedCircle(红色圆形)”、“BlueRectangle(蓝色矩形)”等。这样会导致类的数量急剧增加,而且如果要添加一种新的颜色或者形状,就需要创建更多的类。而使用桥接模式,可以将颜色和形状这两个维度分开处理,通过“桥”来组合它们。
  1. 桥接模式的结构

    • 抽象部分(Abstraction):定义了抽象类的接口,它包含一个对实现部分对象的引用。这个抽象类通常会提供一些高层次的操作,这些操作会调用实现部分的方法来完成具体的工作。
    • 细化抽象部分(RefinedAbstraction):是抽象部分的子类,它实现了抽象类中的抽象方法,并且可以根据具体的需求扩展功能。这些子类可以通过调用实现部分的方法来完成自己的功能。
    • 实现部分(Implementor):定义了实现部分的接口,这个接口不一定要和抽象部分的接口完全一致。它提供了基本的操作,这些操作可以被抽象部分及其子类调用。
    • 具体实现部分(ConcreteImplementor):是实现部分接口的具体实现类,它们实现了实现部分接口中定义的方法,提供了具体的功能。
    • 在这里插入图片描述
  2. C++代码示例

    • 假设我们要实现一个简单的图形绘制系统,有两种形状(圆形和矩形)和两种颜色(红色和蓝色)。
    • 实现部分(Implementor)接口
      • 首先定义一个颜色接口,用于表示颜色相关的操作。
      • class Color {
        public:
            virtual void applyColor() = 0;
        };
        
    • 具体实现部分(ConcreteImplementor)类
      • 然后实现红色和蓝色的具体颜色类。
      • class RedColor : public Color {
        public:
            void applyColor() override {
                std::cout << "Applying red color." << std::endl;
            }
        };
        class BlueColor : public Color {
        public:
            void applyColor() override {
                std::cout << "Applying blue color." << std::endl;
            }
        };
        
    • 抽象部分(Abstraction)类
      • 定义一个形状抽象类,它包含一个颜色对象的引用,并且有一个绘制方法。
      • class Shape {
        protected:
            Color* color;
        public:
            Shape(Color* c) : color(c) {}
            virtual void draw() = 0;
        };
        
    • 细化抽象部分(RefinedAbstraction)类
      • 实现圆形和矩形的具体形状类,它们在绘制方法中会调用颜色对象的方法来设置颜色并进行绘制。
      • class Circle : public Shape {
        public:
            Circle(Color* c) : Shape(c) {}
            void draw() override {
                std::cout << "Drawing a circle. ";
                color->applyColor();
            }
        };
        class Rectangle : public Shape {
        public:
            Rectangle(Color* c) : Shape(c) {}
            void draw() override {
                std::cout << "Drawing a rectangle. ";
                color->applyColor();
            }
        };
        
    • 使用示例
      • 在主函数中,可以这样使用桥接模式来绘制不同颜色的图形。
      • int main() {
            RedColor red;
            BlueColor blue;
            Shape* circle1 = new Circle(&red);
            Shape* circle2 = new Circle(&blue);
            Shape* rectangle1 = new Rectangle(&red);
            Shape* rectangle2 = new Rectangle(&blue);
            circle1->draw();
            circle2->draw();
            rectangle1->draw();
            rectangle2->draw();
            delete circle1;
            delete circle2;
            delete rectangle1;
            delete rectangle2;
            return 0;
        }
        
    • 在这个代码中,形状类(Shape)和颜色类(Color)是通过桥接模式分离的。形状类的对象可以和不同的颜色类对象组合,从而实现了抽象和实现的分离,使得添加新的形状或者颜色都更加容易。例如,如果要添加一种新的颜色“GreenColor”,只需要创建一个新的类继承自Color接口并实现applyColor方法即可,而不需要修改形状相关的类。同样,如果要添加一种新的形状“Triangle”,只需要创建一个新的类继承自Shape抽象类并实现draw方法,也不需要修改颜色相关的类。

总结

一般问题:一个类需要在两个以上维度扩展,采用继承方式会导致子类数量过多

核心方案:将抽象部分与实现部分分离,使其都可以独立变化,采用组合方式

设计意图:桥接模式不是将两个不相干的类链接,而是将一个需要多维度变化的类拆分成抽象部分和实现部分,并且在抽象层对两者做组合关联,是用组合的方式来解决继承的问题。举个例子,如果一个类在两个维度分别有m和n种变化,采用继承的方式就需要扩展出m*n个子类,且一个维度每增加一种变化就多出另一个维度变化总数的子类;如果将两个维度拆分再组合,加起来也只有m+n个子类,且每个维度独立扩展,一个维度增加一种变化只需要增加1个子类。

合成/聚合复用原则

  1. 定义

    • 合成聚合复用原则(Composite/Aggregate Reuse Principle,CARP)也叫组合/聚合复用原则。它是面向对象设计原则之一,指的是尽量使用对象组合(has - a关系)或聚合(contains - a关系)而不是继承来达到软件复用的目的。
    • 组合是一种强“拥有”关系,体现部分和整体的生命周期是一致的,例如鸟和翅膀,翅膀作为鸟的一部分,当鸟不存在时,翅膀也不存在了。聚合是一种弱“拥有”关系,部分可以独立于整体存在,例如雁群和大雁,大雁可以离开雁群独立生存。
  2. 与继承复用对比

    • 继承复用的缺点
      • 强耦合性:在继承关系中,子类与父类紧密耦合。例如,有一个基类“Vehicle(交通工具)”,它有两个子类“Car(汽车)”和“Motorcycle(摩托车)”。如果在Vehicle类中添加一个新的方法“repair(维修)”,那么Car和Motorcycle类都会继承这个方法。但如果Car类需要对“repair”方法有不同的实现,比如汽车维修可能涉及更多复杂的检查项目,就需要重写这个方法。这可能会导致代码在继承体系中频繁修改,而且一旦父类发生变化,子类可能会受到意外的影响。
      • 缺乏灵活性:继承关系在编译时就已经确定,不能在运行时改变。比如一个基于继承的图形绘制系统,有基类“Shape(形状)”和子类“Circle(圆)”“Rectangle(矩形)”等。如果想要在运行时动态地改变一个形状对象从圆形变成矩形,使用继承就很难实现这种灵活的转换。
    • 组合/聚合复用的优点
      • 松耦合:组合和聚合关系使得各个类之间相对独立。以汽车为例,可以有一个“Engine(引擎)”类和一个“Car”类,它们通过组合关系关联。“Car”类拥有一个“Engine”类的对象作为其成员。如果“Engine”类的内部实现发生变化,比如改进了引擎的燃油喷射系统,只要“Engine”类对外提供的接口不变,“Car”类的代码基本不需要修改。
      • 灵活性高:在运行时可以方便地改变组合或聚合的对象。例如,在一个游戏开发中,角色的武器是通过组合的方式添加到角色身上的。可以在游戏运行过程中,根据游戏情节的发展,为角色更换不同的武器,只需要重新组合武器对象和角色对象即可。
  3. 应用场景示例

    • 游戏开发中的道具系统:假设正在开发一款角色扮演游戏。有一个“Player(玩家)”类,玩家可以拥有不同的“Item(道具)”。“Player”类和“Item”类之间是聚合关系,因为玩家可以捡起或丢弃道具。可以通过在“Player”类中定义一个集合(如列表)来存储玩家拥有的道具。
  4. 遵循原则的好处

    • 提高代码的可维护性:由于类之间的耦合度降低,当对某个类进行修改时,对其他类的影响范围较小。这样在维护代码时,更容易定位和解决问题。
    • 增强代码的可扩展性:方便添加新的功能。例如在上述游戏道具系统中,如果要添加一种新的道具类型,只需要创建新的道具类并实现其功能,然后在玩家类中稍作修改(如果需要),就可以将新道具集成到游戏中,而不会对原有的游戏系统造成大规模的破坏。
    • 提升代码的复用性:可以更好地复用已有的类。比如在不同的游戏场景或者不同的游戏角色中,可以复用“Item”类的各种道具,通过组合或聚合的方式将它们添加到不同的角色或场景中。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值