c/c+设计模式--桥接模式

桥接模式是软件设计模式的一种,它用于将抽象部分与其实现部分分离,使它们可以独立变化。这种模式通过创建桥接接口,将抽象和实现部分分离,从而使它们可以独立地变化,而不会相互影响。

在桥接模式中,有两个重要的概念:抽象和实现。抽象定义了客户端的接口,而实现则提供了具体的实现。通过桥接模式,抽象和实现可以独立地进行扩展,而不会相互影响。这种分离允许系统更容易地适应变化,并且提供了更大的灵活性。

桥接模式通常用于以下情况:

  • 当需要避免由于抽象和实现之间的紧耦合而导致的继承层次结构爆炸时。
  • 当抽象和实现部分需要独立扩展而不会相互影响时。
  • 当一个类存在多个独立变化的维度时,可以使用桥接模式将它们分离,以便各自独立变化。

在实际应用中,桥接模式可以被用来处理数据库驱动程序、操作系统封装、图形用户界面等领域,以便将抽象部分和实现部分分离,使它们可以独立变化。

总的来说,桥接模式是一种有助于解耦抽象和实现的设计模式,它提供了更大的灵活性和可维护性,特别适用于需要处理多个独立变化维度的情况。

namespace _nmsp1
{
    
    //图像显示类
    class Image
    {
    public:
        //根据pData(缓冲区)中的内容以及iDataLen所指示的缓冲区的长度,将这些数据显示出来
        void draw(const char* pfilename)
        {
            int iLen = 0;
            char* pData = parsefile(pfilename, iLen);
            if (iLen > 0)
            {
                cout << "显示pData所指向的缓冲区中的图像数据。" << endl;
                //....
                delete pData; //模拟代码中因为pData的内存是new出来的,所以这里需要释放该内存
            }
        }
        virtual ~Image() {} //做父类时析构函数应该为虚函数
    private:
        //根据文件名分析文件内容,每个子类因为图像文件格式不同,会有不同的读取和处理代码
        virtual char* parsefile(const char* pfilename, int& iLen) = 0;
    };

    //处理png格式图像文件的显示
    class Image_png :public Image
    {
    private:
        //读取png文件内容并进行解析,最终整理成统一的二进制数据格式返回
        virtual char* parsefile(const char* pfilename, int& iLen)
        {
            //以下是模拟代码,模拟从图像文件中读取到了数据,最终转换成了100个字节的数据格式(事先约定好的格式规范)并返回
            cout << "开始分析png文件中的数据并将分析结果放到pData中。";
            iLen = 100;
            char* presult = new char[iLen];
            //...
            return presult;
        }
    };
    //处理jpg格式图像文件的显示
    class Image_jpg :public Image
    {
    private:
        virtual char* parsefile(const char* pfilename, int& iLen)
        {
            cout << "开始分析jpg文件中的数据并将分析结果放到pData中。";
            //......
        }
    };
    //处理jpg格式图像文件的显示
    class Image_bmp :public Image
    {
    private:
        virtual char* parsefile(const char* pfilename, int& iLen)
        {
            cout << "开始分析bmp文件中的数据并将分析结果放到pData中。";
            //......
        }
    };
}
int main()
{

      _nmsp1::Image* pImg = new _nmsp1::Image_png();
    pImg->draw("c:\\somedir\\filename.jpg");

    //释放资源
    delete pImg;
}

 

 

namespace _nmsp2
{
    //操作系统类型相关类
    class ImageOS
    {
    public:
        virtual void draw(char* pData, int iLen) = 0;
        virtual ~ImageOS() {} //做父类时析构函数应该为虚函数
    };
    //Windows操作系统
    class ImageOS_Windows :public ImageOS
    {
    public:
        virtual void draw(char* pData, int iLen)
        {
            cout << "在Windows操作系统下显示pData所指向的缓冲区中的图像数据。" << endl;
            //...具体处理代码略
        }
    };
    //Linux操作系统
    class ImageOS_Linux :public ImageOS
    {
    public:
        virtual void draw(char* pData, int iLen)
        {
            cout << "在Linux操作系统下显示pData所指向的缓冲区中的图像数据。" << endl;
            //...具体处理代码略
        }
    };
    //Mac操作系统
    class ImageOS_Mac :public ImageOS
    {
    public:
        virtual void draw(char* pData, int iLen)
        {
            cout << "在Mac操作系统下显示pData所指向的缓冲区中的图像数据。" << endl;
            //...具体处理代码略
        }
    };
    //----------------------------
    //图像文件格式相关类
    class ImageFormat
    {
    public:
        ImageFormat(ImageOS* pimgos) :m_pImgOS(pimgos) {} //构造函数
        virtual void parsefile(const char* pfilename) = 0; //根据文件名分析文件内容,每个子类因为图像文件格式不同,会有不同的读取和处理代码
        virtual ~ImageFormat() {} //做父类时析构函数应该为虚函数
    protected:
        ImageOS* m_pImgOS; //委托
    };
    //png格式的图像文件
    class Image_png :public ImageFormat
    {
    public:
        Image_png(ImageOS* pimgos) :ImageFormat(pimgos) {} //构造函数
        virtual void parsefile(const char* pfilename)
        {
            cout << "开始分析png文件中的数据并将分析结果放到pData中。";
            int iLen = 100;
            char* presult = new char[iLen];
            m_pImgOS->draw(presult, iLen);

            //释放资源
            delete presult;
        }
    };
    //jpg格式的图像文件
    class Image_jpg :public ImageFormat
    {
    public:
        Image_jpg(ImageOS* pimgos) :ImageFormat(pimgos) {} //构造函数
        virtual void parsefile(const char* pfilename)
        {
            cout << "开始分析jpg文件中的数据并将分析结果放到pData中。";
            //......            
        }
    };
    //bmp格式的图像文件
    class Image_bmp :public ImageFormat
    {
    public:
        Image_bmp(ImageOS* pimgos) :ImageFormat(pimgos) {} //构造函数
        virtual void parsefile(const char* pfilename)
        {
            cout << "开始分析bmp文件中的数据并将分析结果放到pData中。";
            //......            
        }
    };
}

int main()
{
 _nmsp2::ImageOS* pimgos_windows = new _nmsp2::ImageOS_Windows(); //针对Windows操作系统
    _nmsp2::ImageFormat* pimg_png = new _nmsp2::Image_png(pimgos_windows); //运行时把图像文件格式png和操作系统windows动态组合到一起。
    pimg_png->parsefile("c:\\somedir\\filename.jpg");

    //释放资源
    delete pimg_png;
    delete pimgos_windows;
    
    return 0;


}

桥接模式uml:

 桥接(Bridge)模式:桥梁模式(桥模式),结构型模式。
    //解决的问题:根据单一职责原则,在一个类中,不要做太多事情,如果事情很多,尽量拆分到多个类中去,然后在
      //一个类中包含指向另外一个类对象的指针,当需要执行另外一个类中的动作时,用指针直接去调用另外一个类的成员函数。
   (1)一个传统的继承范例导致子类数量爆炸式增长
    //图像文件格式:png,jpg,bmp等

 (2)将类与类之间的继承关系改为委托关系
    //类与类之间:继承、组合(委托);
    //文件格式:png、jpg、bmp。 操作系统类型:Windows、Linux、Mac。
    //说明:a)parsefile()其实现代码与具体操作系统类型无关。  b):draw():不同的操作系统,用于显示pData的实现代码不同。

  (3)引入桥接(Bridge)模式
    //不同维度的独立变化,才是能够顺利使用桥接模式的前提。
    //定义:将抽象部分与它的实现部分分离,使他们都可以独立地变化和扩展。
    //a)抽象部分:一般指业务功能,比如ImageFormat类。
    //b)实现部分:一般指具体平台实现,比如ImageOS类。
    //角色
    //(a)Abstraction(抽象部分相关接口):ImageFormat类
    //(b)RefinedAbstraction(扩充抽象部分接口):Image_png、Image_jpg、Image_bmp类。
    //(c)Implementor(实现部分相关接口):ImageOS类
    //(d)ConcreteImplementor(实现部分具体类):ImageOS_Windows、ImageOS_Linux、ImageOS_Mac类。
    //单一职责原则,开闭原则,组合复用原则,依赖倒置原则。
 

 

 

当在C++中使用桥接模式时,可以考虑一个简单的例子:图形界面库。假设我们有一个图形界面库,它需要支持不同的操作系统和不同的应用程序界面。我们可以使用桥接模式来处理这种情况。

首先,我们定义一个抽象类 Window 作为抽象部分,它包含了一些基本的窗口操作方法:

class Window {
public:
    virtual void draw() = 0;
    virtual void open() = 0;
    virtual void close() = 0;
    virtual ~Window() {}
};

然后,我们定义一个实现类 WindowImpl 作为实现部分,它包含了窗口的具体实现:

class WindowImpl {
public:
    virtual void drawWindow() = 0;
    virtual void openWindow() = 0;
    virtual void closeWindow() = 0;
    virtual ~WindowImpl() {}
};

接下来,我们创建一个桥接类 WindowWithBorder,它将抽象部分和实现部分连接起来:

class WindowWithBorder : public Window {
public:
    WindowWithBorder(WindowImpl* impl) : m_impl(impl) {}
    void draw() override {
        m_impl->drawWindow();
    }
    void open() override {
        m_impl->openWindow();
    }
    void close() override {
        m_impl->closeWindow();
    }
private:
    WindowImpl* m_impl;
};

最后,我们创建具体的实现类,比如 LinuxWindowImplWindowsWindowImpl,它们分别实现了 WindowImpl 接口:

class LinuxWindowImpl : public WindowImpl {
public:
    void drawWindow() override {
        // 在Linux上绘制窗口
    }
    void openWindow() override {
        // 在Linux上打开窗口
    }
    void closeWindow() override {
        // 在Linux上关闭窗口
    }
};

class WindowsWindowImpl : public WindowImpl {
public:
    void drawWindow() override {
        // 在Windows上绘制窗口
    }
    void openWindow() override {
        // 在Windows上打开窗口
    }
    void closeWindow() override {
        // 在Windows上关闭窗口
    }
};

通过这样的设计,我们可以在不同的操作系统上轻松地实现不同风格的窗口,而不用修改抽象部分的代码。这样就实现了抽象部分和实现部分的解耦,并且使得它们可以独立变化。这就是桥接模式在C++中的一个简单示例。

当使用桥接模式时,首先需要定义抽象部分和实现部分。在C++中,可以使用抽象基类和纯虚函数来定义抽象部分,以及实现类来定义实现部分。然后通过桥接类将抽象部分和实现部分连接起来。

下面是一个更完整的示例:

// 定义抽象部分
class Window {
public:
    virtual void draw() = 0;
    virtual void open() = 0;
    virtual void close() = 0;
    virtual ~Window() {}
};

// 定义实现部分
class WindowImpl {
public:
    virtual void drawWindow() = 0;
    virtual void openWindow() = 0;
    virtual void closeWindow() = 0;
    virtual ~WindowImpl() {}
};

// 桥接类连接抽象部分和实现部分
class WindowWithBorder : public Window {
public:
    WindowWithBorder(WindowImpl* impl) : m_impl(impl) {}
    void draw() override {
        m_impl->drawWindow();
    }
    void open() override {
        m_impl->openWindow();
    }
    void close() override {
        m_impl->closeWindow();
    }
private:
    WindowImpl* m_impl;
};

// 具体的实现类
class LinuxWindowImpl : public WindowImpl {
public:
    void drawWindow() override {
        // 在Linux上绘制窗口
        std::cout << "Drawing window in Linux style" << std::endl;
    }
    void openWindow() override {
        // 在Linux上打开窗口
        std::cout << "Opening window in Linux style" << std::endl;
    }
    void closeWindow() override {
        // 在Linux上关闭窗口
        std::cout << "Closing window in Linux style" << std::endl;
    }
};

class WindowsWindowImpl : public WindowImpl {
public:
    void drawWindow() override {
        // 在Windows上绘制窗口
        std::cout << "Drawing window in Windows style" << std::endl;
    }
    void openWindow() override {
        // 在Windows上打开窗口
        std::cout << "Opening window in Windows style" << std::endl;
    }
    void closeWindow() override {
        // 在Windows上关闭窗口
        std::cout << "Closing window in Windows style" << std::endl;
    }
};

int main() {
    // 创建使用Linux风格实现的窗口
    WindowImpl* linuxImpl = new LinuxWindowImpl();
    Window* linuxWindow = new WindowWithBorder(linuxImpl);
    linuxWindow->draw();
    linuxWindow->open();
    linuxWindow->close();

    // 创建使用Windows风格实现的窗口
    WindowImpl* windowsImpl = new WindowsWindowImpl();
    Window* windowsWindow = new WindowWithBorder(windowsImpl);
    windowsWindow->draw();
    windowsWindow->open();
    windowsWindow->close();

    delete linuxWindow;
    delete linuxImpl;
    delete windowsWindow;
    delete windowsImpl;

    return 0;
}

在这个示例中,我们首先定义了抽象部分 Window 和实现部分 WindowImpl。然后通过桥接类 WindowWithBorder 将抽象部分和实现部分连接起来。最后创建了具体的实现类 LinuxWindowImplWindowsWindowImpl

main 函数中,我们实例化了不同风格的窗口,并调用其方法进行操作。这样就实现了抽象部分和实现部分的解耦,并且可以独立地变化。

posted @ 2024-05-29 14:42  白伟碧一些小心得  阅读(101)  评论(0编辑  收藏  举报