C++桥接模式如何分离抽象 实现独立变化的两个维度设计

桥接模式通过组合解耦抽象与实现。1.核心是将“做什么”和“怎么做”分离,避免类爆炸;2.结构包含抽象、精化抽象、实现者、具体实现者四个角色;3.适用于多维度变化场景如跨平台ui或图形绘制;4.c++++中需注意实现者生命周期管理;5.区别于策略模式(行为切换)和适配器模式(接口转换),侧重结构解耦。

C++桥接模式如何分离抽象 实现独立变化的两个维度设计

C++的桥接模式,说白了,就是把一个大问题拆成两个可以独立变化的小问题,让“做什么”(抽象)和“怎么做”(实现)这两条线能各走各的路,互不干扰。这就像一座桥,连接了两岸,但两岸上的风景怎么变,桥本身的功能和结构是相对独立的,它只负责连接。核心在于通过组合而非继承,将抽象层和实现层解耦,从而允许它们各自独立地扩展和演进。

C++桥接模式如何分离抽象 实现独立变化的两个维度设计

解决方案

我们在软件设计中,有时候会遇到这样的场景:你有一个概念,它有很多种变体,同时它又可以在多种不同的环境下运行或以多种方式实现。如果用传统的继承方式来处理,很快就会陷入一个“类爆炸”的泥潭。比如,你有一堆图形(圆形、方形、三角形),它们又要在不同的绘图API上绘制(OpenGL、DirectX、SVG)。如果直接用继承,你可能需要CircleOpenGLCircleDirectXSquareOpenGLSquareDirectX……每增加一个图形或一个绘图API,类的数量就会呈乘法级增长,维护起来简直是噩梦。

C++桥接模式如何分离抽象 实现独立变化的两个维度设计

桥接模式提供了一个优雅的解决方案。它将抽象(比如Shape)和实现(比如DrawingAPI)分离开来。抽象层定义了高层接口,它内部持有一个指向实现层接口的指针或引用。当抽象层需要执行某个操作时,它就把这个操作委托给它所持有的实现对象。这样一来,抽象的具体实现(比如Circle)就不再关心它具体是在哪个绘图API上绘制的,它只知道通过一个DrawingAPI接口去调用绘制方法。而DrawingAPI的具体实现(比如OpenGLAPI)也只负责它自己的绘图逻辑,它不关心是哪个形状在调用它。

立即学习“C++免费学习笔记(深入)”;

这种分离带来的好处是显而易见的:

C++桥接模式如何分离抽象 实现独立变化的两个维度设计独立变化:你可以增加新的形状,而无需修改任何绘图API的实现;同样,你可以增加新的绘图API,而无需修改任何形状的实现。减少耦合:抽象和实现之间的耦合度大大降低,它们通过一个接口松散地联系在一起。运行时切换:你甚至可以在运行时切换抽象所使用的具体实现,比如在不同的绘图模式之间切换。

C++中桥接模式的实际应用场景有哪些?

说实话,C++里桥接模式的应用场景还挺多的,不只是图形绘制这么简单。我个人觉得,当你发现一个类或者一组类的继承体系,开始因为两个或更多个正交(也就是互相独立)的变化维度而变得臃肿不堪时,就应该考虑它了。

一个非常经典的例子就是跨平台的用户界面(UI)工具包。想象一下,你有一个Button抽象类,它在Windows上可能对应一个Win32Button,在macOS上对应一个CocoaButton,在Linux上可能对应一个GTKButtonButton的“行为”(点击、禁用等)是一个维度,而它在不同操作系统上的“具体绘制和事件处理”(实现)是另一个维度。如果直接继承,你就会有WindowsButtonMacButtonLinuxButton,然后你再来个WindowsCheckboxMacCheckbox……这会很糟糕。

用桥接模式,你可以定义一个UIElement(抽象)和PlatformImplementor(实现)接口。ButtonCheckbox等继承自UIElement,它们内部持有PlatformImplementor的引用。而Win32ImplementorCocoaImplementorGTKImplementor则实现PlatformImplementor接口。这样,你新增一个UI控件(比如Slider),只需要继承UIElement,而不需要关心平台细节;新增一个平台支持,只需要实现PlatformImplementor接口,而不需要修改所有UI控件的逻辑。

另一个常见但可能不那么显眼的场景是,当你需要隐藏一个类的具体实现细节,只暴露一个稳定的接口给客户端时,也就是所谓的PIMPL(Pointer to IMPLementation)惯用法。PIMPL本质上就是桥接模式的一种特例。你的公共类(抽象)只包含一个指向私有实现类的指针,所有实际的业务逻辑都在私有实现类中。这不仅可以减少头文件依赖,加快编译速度,还能在不破坏ABI兼容性的前提下修改内部实现。

如何在C++中构建桥接模式的各个组成部分?

在C++中实现桥接模式,主要涉及四个核心角色:抽象(Abstraction)、精化抽象(Refined Abstraction)、实现者(Implementor)和具体实现者(Concrete Implementor)。理解它们各自的职责以及如何用C++的特性来表达,是关键。

抽象(Abstraction):这是客户端代码直接打交道的接口。它通常是一个抽象基类,定义了高层操作。它内部会持有一个指向Implementor接口的指针或引用。

// 抽象基类class DrawingAPI { // Implementor 接口public:    virtual void drawCircle(double x, double y, double radius) = 0;    virtual ~DrawingAPI() = default;};// 抽象class Shape {protected:    DrawingAPI* drawingAPI_; // 持有实现者的引用public:    Shape(DrawingAPI* api) : drawingAPI_(api) {}    virtual void draw() = 0; // 高层操作    virtual ~Shape() = default;};

这里Shape是抽象,它依赖于DrawingAPI这个实现者接口。

精化抽象(Refined Abstraction):这是Abstraction的具体子类,它实现了Abstraction定义的高层操作。这些操作通常会通过委托调用Implementor的方法。

// 精化抽象:圆形class Circle : public Shape {private:    double x_, y_, radius_;public:    Circle(double x, double y, double r, DrawingAPI* api)        : Shape(api), x_(x), y_(y), radius_(r) {}    void draw() override {        // 将具体绘制操作委托给 drawingAPI_        drawingAPI_->drawCircle(x_, y_, radius_);    }};

Circle就是Shape的精化抽象,它通过其内部的drawingAPI_来完成绘制。

实现者(Implementor):这是一个接口(在C++中通常是纯虚基类),定义了抽象层所需的所有基本操作。它不关心这些操作具体是如何被实现的,只提供一个规范。

// DrawingAPI 就是 Implementor 接口// class DrawingAPI { ... }; (已在上方定义)

具体实现者(Concrete Implementor):这是Implementor接口的具体实现。每个具体实现者都提供了一套不同的方式来实现Implementor定义的操作。

// 具体实现者:OpenGL 绘图APIclass OpenGLDrawingAPI : public DrawingAPI {public:    void drawCircle(double x, double y, double radius) override {        std::cout << "Drawing Circle with OpenGL at (" << x << "," << y << ") radius " << radius << std::endl;        // 实际的OpenGL调用...    }};// 具体实现者:DirectX 绘图APIclass DirectXDrawingAPI : public DrawingAPI {public:    void drawCircle(double x, double y, double radius) override {        std::cout << "Drawing Circle with DirectX at (" << x << "," << y << ") radius " << radius << std::endl;        // 实际的DirectX调用...    }};

在实际使用时,客户端代码会创建具体的Implementor对象,然后将其传递给Abstraction的构造函数。

// 客户端代码// #include // #include  // For std::unique_ptr// ... (类定义) ...int main() {    // 创建具体的实现者    std::unique_ptr openglAPI = std::make_unique();    std::unique_ptr directxAPI = std::make_unique();    // 创建抽象,并传入不同的实现者    std::unique_ptr circleOpenGL = std::make_unique(1.0, 2.0, 3.0, openglAPI.get());    std::unique_ptr circleDirectX = std::make_unique(5.0, 6.0, 7.0, directxAPI.get());    circleOpenGL->draw();  // 输出: Drawing Circle with OpenGL...    circleDirectX->draw(); // 输出: Drawing Circle with DirectX...    // 运行时切换实现(如果抽象允许)    // 比如,你可以在一个工厂方法中根据配置返回不同的 DrawingAPI 实例    // 或者 Shape 内部提供一个 setDrawingAPI 方法    // 但通常来说,Bridge模式的连接是在对象创建时建立的。    return 0;}

需要注意的是,在C++中处理Implementor的生命周期是个关键点。上面例子中我用了原始指针,但实际项目中,为了避免内存泄漏和管理复杂性,通常会使用智能指针(如std::unique_ptrstd::shared_ptr)来管理DrawingAPI对象的生命周期。如果一个DrawingAPI实例会被多个Shape对象共享,那么std::shared_ptr会是更好的选择。

桥接模式与策略模式、适配器模式有何异同?

这几个设计模式确实在结构上有些相似之处,都涉及到了“委托”或者“封装”,但它们解决的问题和侧重点是不同的。理解它们的异同,能帮助我们更准确地选择合适的模式。

与策略模式(Strategy Pattern)的异同

相同点:两者都使用了对象组合(Composition)而非继承,并且都通过委托来执行行为。它们都将算法或行为封装在独立的类中。不同点目的:策略模式的目的是封装一组可互换的算法,让客户端可以在运行时选择不同的算法。它关注的是“行为”的变化。桥接模式的目的是将抽象和实现分离,让它们可以独立地变化和扩展。它关注的是“结构”和“维度”的分离。维度:策略模式通常处理一个维度上的变化(不同的算法)。桥接模式处理的是两个或多个正交维度上的变化(抽象的种类和实现的种类)。层次:策略模式通常在同一抽象层次上提供不同的行为实现。桥接模式则是在不同层次(抽象层和实现层)之间建立桥梁。举例:策略:计算税费有多种方法(普通税、增值税、消费税),你可以把这些方法封装成不同的策略,然后根据需要切换。桥接:绘制圆形,可以在OpenGL上画,也可以在DirectX上画。圆形是抽象,OpenGL/DirectX是实现。

与适配器模式(Adapter Pattern)的异同

相同点:两者都涉及到一个类“使用”另一个类,并且都可能涉及到接口的转换。不同点目的:适配器模式的目的是让一个不兼容的接口变得兼容,通常是为了让两个本来无法协同工作的接口能够一起工作。它解决的是“接口不匹配”的问题。桥接模式的目的是分离抽象和实现,允许它们独立演化。它解决的是“多维度变化导致类爆炸”的问题。方向:适配器模式通常是单向的,将一个现有接口转换为目标接口。桥接模式则是双向的,它在抽象和实现之间建立一个稳定的连接点,两者都可以独立扩展。设计时机:适配器模式通常是在系统已经存在,需要集成现有不兼容组件时使用(事后弥补)。桥接模式更多是在系统设计之初,预见到多维度变化时使用(事前规划)。举例:适配器:你有一个老旧的LegacyLogger接口,但你的新系统只认识NewLogger接口,你需要一个LegacyLoggerAdapter来把LegacyLogger包装成NewLogger。桥接:同上文的图形绘制或跨平台UI。

总的来说,桥接模式是当你预见到系统将会有两个或更多个独立变化的维度时,用来解耦和避免类爆炸的利器。它让你的设计更加灵活,更易于维护和扩展。当然,引入桥接模式会增加一些类的数量和间接性,所以并非所有简单场景都适用,它更适合那些复杂度较高、变化频繁的系统。

以上就是C++桥接模式如何分离抽象 实现独立变化的两个维度设计的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1466844.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月18日 16:14:42
下一篇 2025年12月18日 16:14:57

相关推荐

  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 旋转长方形后,如何计算其相对于画布左上角的轴距?

    绘制长方形并旋转,计算旋转后轴距 在拥有 1920×1080 画布中,放置一个宽高为 200×20 的长方形,其坐标位于 (100, 100)。当以任意角度旋转长方形时,如何计算它相对于画布左上角的 x、y 轴距? 以下代码提供了一个计算旋转后长方形轴距的解决方案: const x = 200;co…

    2025年12月24日
    000
  • 旋转长方形后,如何计算它与画布左上角的xy轴距?

    旋转后长方形在画布上的xy轴距计算 在画布中添加一个长方形,并将其旋转任意角度,如何计算旋转后的长方形与画布左上角之间的xy轴距? 问题分解: 要计算旋转后长方形的xy轴距,需要考虑旋转对长方形宽高和位置的影响。首先,旋转会改变长方形的长和宽,其次,旋转会改变长方形的中心点位置。 求解方法: 计算旋…

    2025年12月24日
    000
  • 旋转长方形后如何计算其在画布上的轴距?

    旋转长方形后计算轴距 假设长方形的宽、高分别为 200 和 20,初始坐标为 (100, 100),我们将它旋转一个任意角度。根据旋转矩阵公式,旋转后的新坐标 (x’, y’) 可以通过以下公式计算: x’ = x * cos(θ) – y * sin(θ)y’ = x * …

    2025年12月24日
    000
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • 如何计算旋转后长方形在画布上的轴距?

    旋转后长方形与画布轴距计算 在给定的画布中,有一个长方形,在随机旋转一定角度后,如何计算其在画布上的轴距,即距离左上角的距离? 以下提供一种计算长方形相对于画布左上角的新轴距的方法: const x = 200; // 初始 x 坐标const y = 90; // 初始 y 坐标const w =…

    2025年12月24日
    200
  • CSS元素设置em和transition后,为何载入页面无放大效果?

    css元素设置em和transition后,为何载入无放大效果 很多开发者在设置了em和transition后,却发现元素载入页面时无放大效果。本文将解答这一问题。 原问题:在视频演示中,将元素设置如下,载入页面会有放大效果。然而,在个人尝试中,并未出现该效果。这是由于macos和windows系统…

    2025年12月24日
    200
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 如何用HTML/JS实现Windows 10设置界面鼠标移动探照灯效果?

    Win10设置界面中的鼠标移动探照灯效果实现指南 想要在前端开发中实现类似于Windows 10设置界面的鼠标移动探照灯效果,有两种解决方案:CSS 和 HTML/JS 组合。 CSS 实现 不幸的是,仅使用CSS无法完全实现该效果。 立即学习“前端免费学习笔记(深入)”; HTML/JS 实现 要…

    2025年12月24日
    000
  • 如何计算旋转后的长方形在画布上的 XY 轴距?

    旋转长方形后计算其画布xy轴距 在创建的画布上添加了一个长方形,并提供其宽、高和初始坐标。为了视觉化旋转效果,还提供了一些旋转特定角度后的图片。 问题是如何计算任意角度旋转后,这个长方形的xy轴距。这涉及到使用三角学来计算旋转后的坐标。 以下是一个 javascript 代码示例,用于计算旋转后长方…

    2025年12月24日
    000
  • 为什么我的 Safari 自定义样式表在百度页面上失效了?

    为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…

    2025年12月24日
    000
  • 如何用前端实现 Windows 10 设置界面的鼠标移动探照灯效果?

    如何在前端实现 Windows 10 设置界面中的鼠标移动探照灯效果 想要在前端开发中实现 Windows 10 设置界面中类似的鼠标移动探照灯效果,可以通过以下途径: CSS 解决方案 DEMO 1: Windows 10 网格悬停效果:https://codepen.io/tr4553r7/pe…

    2025年12月24日
    000

发表回复

登录后才能评论
关注微信