欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程语言 > asp.net >内容正文

asp.net

趣谈设计模式 | 桥接模式(Bridge):将抽象与实现分离

发布时间:2024/4/11 asp.net 95 豆豆
生活随笔 收集整理的这篇文章主要介绍了 趣谈设计模式 | 桥接模式(Bridge):将抽象与实现分离 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

文章目录

  • 案例:跨平台应用开发
  • 桥接模式
  • 总结
  • 完整代码与文档


案例:跨平台应用开发

A公司最近准备开发一组应用合集,目前包括了视频播放器、音乐播放器、文本播放器三种应用,但是在发售前,他们遇到了困难。

由于目前计算机系统繁多,且彼此之间大多不相互兼容,所以还需要针对不同平台,为应用进行处理。

在初步的设计中,A公司将平台抽象为接口,然后针对不同平台来实现应用,设计图如下

这一设计乍一看没有什么问题,但是复用性却极差。

例如当A公司新开发了一个PDF阅读器时,就需要分别在这三个平台下都实现一个PDF阅读器,可由于这些PDF阅读器之间相差不大,代码的复用程度低。

而如果A公司此时想要兼容更多的平台,例如新加入了Android平台,就需要新增一个Android类,并且在其下实现所有之前的应用,这样的设计不仅代码复用性差,灵活性也不足。

于是A公司想到了第二种方案,按照具体应用的实现来进行分类,设计图如下

但是由于这一设计的本质还是依赖于继承,所以它无论是平台,还是具体的应用发生了拓展,它都仍然存在着上面的问题。

引用GOF的观点

对象的继承关系是在编译时就定义好了的,所以无法在运行时改变从父类继承的实现。子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化都必然会导致子类发生变化。当你需要复用子类时,如果继承下来的实现不适合解决性的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。

那如何解决这个问题呢?我们可以考虑用聚合代替继承

在上面的设计中,最大的问题就是平台以及应用之间存在着强耦合的关系,无论我们新增的是平台还是应用,都会对彼此造成影响。

既然如此,我们为何不将他们分离呢?

此时,平台和应用之间相互独立,当我们要新增平台,只需要去实现平台接口,应用也同理。

那么接下来如何处理平台与应用之间的关系呢?首先我们要知道,虽然平台中包含了各种应用,但是应用本身并不是平台的一部分,那么它们其实是has-a的关系,所以我们可以使用聚合(即A包含B,但B不是A的一部分)来实现


通过聚合,既保证了平台与应用两者的独立变化,同时又保证了两者之间的包含关系。

聚合像桥梁一样将两者连接到一块,所以这种设计模式又叫做桥接模式


桥接模式

桥接模式将抽象部分与它的实现部分分离,使他们都可以独立地变化

什么叫将抽象和实现分离呢?
这里的抽象指的并不是抽象类或者接口,而是被抽象出来的一套“类库”,它只包含骨架代码,真正的业务逻辑需要委托给定义中的“实现”来完成。我们这里所说的实现也绝非接口的实现类,而是一套独立的“类库”。“抽象”和“实现”独立开发,通过对象之间的组合关系,组装在一起。

桥接模式由以下部分组成

  • Abstraction(抽象):使用实现提供的接口来定义基本功能接口
  • Implementor(实现):提供了用于抽象的接口,它是一个抽象类或者接口。
  • RefinedAbstraction(提炼后的抽象):作为抽象的子类,增加新的功能,也就是增加新的接口(方法)
  • ConcreteImplementor(具体的实现):作为实现的子类,通过实现具体方法来实现接口

类图如下

根据桥接模式来改造我们的设计,如下图

首先分别实现“抽象”:平台,以及“实现”:软件

#pragma onceclass Implementor { public:virtual ~Implementor() = default;virtual void run() = 0; }; #pragma once #include"Implementor.hpp"class Abstraction { public:virtual ~Abstraction() = default;void setImplementor(Implementor* implementor){_implementor = implementor;}virtual void run(){if(_implementor != nullptr){_implementor->run();}}protected:Implementor* _implementor = nullptr; };

接着实现我们具体的实现,以及提炼后的抽象

#pragma once #include<iostream> #include"Implementor.hpp"class Music : public Implementor { public:void run() override{std::cout << "启动音乐播放器" << std::endl;} };class Vedio : public Implementor { public:void run() override{std::cout << "启动视频播放器" << std::endl;} };class Text : public Implementor { public:void run() override{std::cout << "启动文本阅读器" << std::endl;} }; #pragma once #include<iostream> #include"Abstraction.hpp"class Linux : public Abstraction { public:void run() override{std::cout << "Linux系统:";_implementor->run();} };class Windows : public Abstraction { public:void run() override{std::cout << "Windows系统:";_implementor->run();} };class Mac : public Abstraction { public:void run() override{std::cout << "Mac系统:";_implementor->run();} };

测试代码

#include"RefinedAbstraction.hpp" #include"ConcreteImplementor.hpp"using namespace std;int main() {Abstraction* linux = new Linux;Abstraction* windows = new Windows;Abstraction* mac = new Mac;linux->setImplementor(new Vedio);windows->setImplementor(new Vedio);linux->run();windows->run();linux->setImplementor(new Music);linux->run();mac->setImplementor(new Text);mac->run();return 0; }

运行结果如下

在这种设计下,当我们需要拓展平台或者应用的时候,就只需要去实现对应的抽象类即可


总结

要点

  • 将抽象与实现分离,起到了解耦合的作用
  • 抽象和实现可以独立拓展,不会影响到对方
  • 实现细节对客户透明
  • 增加了设计的复杂度,由于聚合关系建立在抽象层,要求开发者针对抽象进行设计与编程。

应用场景

  • 适合使用在需要跨越多平台、型号的设计
  • 需要用不同的方法改变接口与实现时
  • 一个类存在两个独立变化的维度时

完整代码与文档

如果有需要完整代码或者markdown文档的同学可以点击下面的github链接
github

总结

以上是生活随笔为你收集整理的趣谈设计模式 | 桥接模式(Bridge):将抽象与实现分离的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。