• C++ 3个常用API包装器模式:代理模式、适配器模式、外观模式


    API包装器模式

    通常,需要编写基于另一组类的包装器接口,用一个新的、更简洁的API,来隐藏所有底层遗留代码;或者,已经编写了C++ API,但后来需要给特定客户提供纯C接口,但又不想改变原来的代码封装;或者,你的API用的了一个第三方依赖库,你想让客户直接使用此库,但不想将此库直接暴露给客户。

    包装器API的潜在副作用是影响性能,因为会增加一级额外的函数调用,以及存储保证层次状态带来的开销。但好处也是明显的,可以创建质量更高、更适配用户需求的API接口。

    C++中,有3个常用的API包装器模式:代理模式,适配器模式,外观模式。它们都属于结构型模式,按包装器层和原始接口的差异递增。

    代理模式

    在GoF中,代理模式的意图定义为:为其他对象提供一种代理以控制对这个对象的访问。

    代理模式提供一对一的转发接口,代理类和原始类应该具有相同接口。实现方式,可以是代理类存储原始类对象(即被代理类,也叫真实对象)的副本,或者指向原始类对象的指针(是不是很像Pimpl惯用法?),然后代理类的方法将重定向到原始类对象中的同名方法。

    缺点:1)将原始类接口暴露给用户;2)在改变原始类接口时,需要维护代理接口的完整性。

    代理模式 vs Impl惯用法

    代理类形式上有点像Pimpl惯用法(见C++ Pimpl惯用法(桥接模式特例)),区别在哪?

    • Pimpl惯用法属于桥接模式特例,主要目的是通过接口类实现对用户屏蔽实现细节,重在隐藏实现细节。私有实现类(Impl class)和接口类的API接口没必要保持一致,而且私有实现类往往与接口类是同一个实现者。

    • 代理模式主要目的是为用户提供控制对原始类对象的访问,要求接口必须保持与原始类接口一致,重在控制,即不能改变功能。被代理类经常是第三方库,或者已经设计好的部分。

    代理模式的简单实现

    代理类

    class Proxy
    {
    public:
        Proxy() : original_(new Original())
        {}
        ~Proxy()
        {
            delete original_;
        }
    
    private:
        Proxy(const Proxy&);
        const Proxy &operator=(const Proxy&);
    
        Original *original_;
    };
    

    将代理类设计为禁止copy,因为原始类不仅仅是一个类,背后往往设计到其他资源,仅仅拷贝对象并没有实际意义;当然,如果有copy对象需求,可以自行实现copy函数。

    另一种方案,是在此方案基础上增加代理和原始API共享的virtual接口,目的在于通过C++语法来保持2个API同步。这样做前提是你能修改原始API。

    // 通过公有接口IOriginal, 从语法层面确保代理类和被代理类接口一致
    
    class IOriginal // 原始类接口
    {
    public:
        virtual bool DoSomething(int value) = 0;
    };
    
    class Original : public IOriginal // 原始类
    {
    public:
        bool DoSomething(int value);
    };
    
    class Proxy : public IOriginal // 代理类
    {
    public:
        Proxy() : original_(new Original())
        {}
        ~Proxy()
        {
            delete original_;
        }
    
        bool DoSomething(int value)
        {
            return original_->DoSomething(value);
        }
    
    private:
        Proxy(const Proxy &);
        const Proxy &operator=(const Proxy&);
        
        Original *original_;
    };
    

    代理模式应用场景

    1)实现原始对象的惰性实例化。直到特定方法被调用时,即对象真正被需要时,Original对象才真正实例化。

    2)实现对Original对象的访问控制。如要在Proxy和Original对象之间插入权限层,确保当用户获得适当的授权后,只能调用Original对象上的特定方法。

    3)支持调试或“演习”模式。支持在Proxy方法中插入调试语句,记录所有对Original对象的调用,或者使用一个标志以“演习”(dry run)模式调用Proxy,以禁止调用特定的Original方法;例如,禁止将对象状态写入磁盘。

    4)保证Original类线程安全。通过给非线程安全地方法添加互斥锁实现线程安全。虽然不是确保线程安全的最佳实践,但如果不能修改Original,却也是一个权宜之计。比如为glibc库函数加锁。

    5)支持资源共享。当多个Proxy对象共享相同的Original基础类。例如,可用于实现引用计数或写时复制(copy-on-write)语义。这种用法实际上是享元模式(Flyweight)。

    6)应对Original类将来被修改的情况。如果预期依赖库可能会改变,可以为其API创建一个代理包装器模拟当前的行为。当库改变时,通过代理对象预留老的接口,改变代理类的底层实现,就可以使用新的库方法。准确来说,这是适配器对象,而非代理对象。


    适配器模式

    适配器模式意图:将一个类的接口转换成客户希望的另外一个接口。

    也就是说,适配器提供接口转换功能。真实对象接口与客户希望的接口并不兼容,可能由于参数顺序或类型不同,使用习惯不同,命名约定不同等等原因,导致无法直接使用。此时,可以用适配器模式对真实对象接口进行转换。

    适配器模式 vs 代理模式

    两种相同点在于,可用来对真实对象进行API包装,被包装的对象经常是第三方依赖库,无法改变;另外,两者都不会改变真实对象的基本功能。

    不同点:

    • 代理模式 要求不改变真实对象的接口,代理模式重在提供控制对象的访问。

    • 适配器模式 通常需要改变真实对象的接口(名字,参数个数、类型、顺序,返回值类型),适配器模式重在适配接口。

    适配器模式简单实现

    例如,现有真实对象类Rectangle 提供接口setDimension,是以圆心、半径、矩形宽和高方的式定义矩形,而客户需要通过矩形左下角、右上角坐标来定义矩形。可以通过适配器类RectangleAdapter,来对接口进行转换。

    class RectangleAdapter
    {
    public:
        RectangleAdapter() :
        rect_(new Rectangle())
        {}
    
        ~RectangleAdapter()
        {
            delete rect_;
        }
    
        void Set(float x1, float y1, float x2, float y2)
        {
            float w = x2 - x1;
            float h = y2 - y1;
            float cx = w / 2.0f + x1;
            float cy = h / 2.0f + y1;
            rect_->setDimension(cx, cy, w, h);
        }
    
    private:
        // 禁止copy
        RectangleAdapter(const RectangleAdapter&);
        const RectangleAdapter& operator=(const RectangleAdapter&);
        
        Rectangle *rect_;
    };
    

    适配器可以通过“组合”或“继承”来实现。前者称为对象适配器,后者称为类适配器。上面示例,显然是对象适配器。

    适配器模式优点

    1)强制API始终保持一致性。使用适配器模式能整合接口风格不同的类,为它们提供一致的接口供客户使用。

    2)包装API的依赖库。可以不暴露依赖库及其接口给用户。

    3)转换数据类型。例如,将极坐标转换为直角坐标。

    4)为API暴露一个不同的调用约定。例如,为纯C API提供面向对象版本。另一个常用的场景,就是将系统调用封装成RAII管理方式,同时伴随着C++接口包装C接口。


    外观模式

    外观模式对意图:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

    一个子系统可能包含多个对象,它们的API可能各不相同,如果让用户针对每个对象都定制一个调用,可能会有多种风格调用,而且相互之间很难转换。这将势必导致用户不得不了解个对象接口细节。而外观模式,为这个子系统内的所有API提供一个统一的接口,简化其使用,用户只需要对接外观模式即可。

    一个典型的外观模式,就是Linux的系统调用open/close/read/write/fcntl等,用户只需要针对文件描述符调用这些函数即可,而不必关系操作的具体是什么设备。

    外观模式 vs 适配器模式

    外观模式与适配器模式都是改变被包装类的接口,它们有什么区别?

    外观模式简化了类的结构,而适配器模式仍保持类的结构(并未修改类的结构关系)。外观模式通常是为一组对象提供统一接口,目的在于提供统一风格的接口;而一个适配器模式,往往针对一个真实对象(类)提供适配接口。

    外观模式的简单实现

    假设你在度假并入住了一家酒店。你计划先用晚餐,然后去看演出。如果不用外观模式,你需要先给餐厅打电话预订晚餐,接着打电话给剧院预订座位,可能还需要叫出租车来接你。在C++中,可将这3件事表示为3个独立的对象,并逐一处理每个对象。

    class Taxi
    {
    public:
        bool BookTaxi(int npeople, time_t pickup_time); // 预订出租车
    };
    
    class Restaurant
    {
    public:
        bool ReserveTable(int npeople, time_t arrival_time); // 订晚餐
    };
    
    class Theater
    {
    public:
        time_t GetShowTime();
        bool ReserveSeats(int npeople, int tier); // 预订座位
    };
    

    假设你入住的是一家高档酒店,酒店的礼宾部能帮你完成所有事情。礼宾部首先查演出时间,然后根据掌握的当地情况,计算出合适的晚餐时间,并在最佳时间为你预订出租车。转换成C++术语,你只需要对接礼宾部对象即可,而且该对象的接口比使用上面3个对象接口更简单。

    class ConciergeFacde
    {
    public:
        enum ERestaurant {
            RESTAURANT_YES,
            RESTAURANT_NO
        };
        enum ETaxi {
            TAXI_YES,
            TAXI_NO
        };
    
        // 现在, 你只需要关心这一个对象的接口即可
        time_t BookShow(int npeople, ERestaurant addRestaurant, ETaxi addTaxi);
    };
    

    外观模式优点

    1)隐藏遗留代码。原有系统可能较为陈旧、脆弱,不提供一致的对象模型。而外观模式能有效基于原有代码创建一组设计良好的API,用户只需要使用新API即可。

    2)创建便捷API。例如,OpenGL的GL库提供底层的基础例程,功能强大,但使用繁琐;GLU库基于GL库,提供高层次易于使用的接口。

    3)支持简化功能或替代功能的API。抽象出对底层的子系统的访问后,就能替换某个子系统,且捕获影响客户代码。


    参考

    [1]Martin Reddy, 刘晓娜, 臧秀涛,等. C++ API设计[M]. 人民邮电出版社, 2013.

  • 相关阅读:
    DGbroker三种保护模式的切换
    oracle11G使用DGbroker创建dg
    oracle数据泵示例
    oracle DB_LINK
    oracle数据库rman备份计划及恢复
    oracle查看对象信息
    mybatis学习:mybatis的注解开发和编写dao实现类的方式入门
    mybatis学习:mybatis的环境搭建与入门
    mybatis框架学习:
    入门servlet:request请求转发和共享数据
  • 原文地址:https://www.cnblogs.com/fortunely/p/16418080.html
Copyright © 2020-2023  润新知