《C++20设计模式》学习笔记——第12章代理模式

本文详细介绍了代理模式中的几种类型,包括智能指针作为基础的代理形式,属性代理提供访问器和修改器,虚拟代理支持惰性加载,通信代理适应物理位置变化,以及值代理增强单个值的功能。这些代理在IT技术中被用来增强对象功能和优化资源管理。
摘要由CSDN通过智能技术生成


代理模式也是用于强化对象的功能,但其目标是在提供某些内部功能的同时准确(或尽可能地)保留正在使用的API。

代理模式不是一种同质的模式,因为人们构建的不同类型的代理相当多,并且服务于不同的目的。本章将介绍一些不同的代理对象。

1. 智能指针

智能指针是代理模式最简单而且最直接的展示:智能指针是一个包装类,其中封装了原始指针,同时维护着一个引用计数,并重载了部分运算符。但总体来说,智能指针提供了原始指针所具有的接口,基本上在原始指针出现的位置,都可以使用智能指针。

当然二者之间也有差别,最明显地差异在于,对于智能指针,不必再调用delete。

2. 属性代理

在其他编程语言中,术语“属性”表示底层成员与该成员的getter/setter方法的组合。

C++中没有内置属性的支持;最常见的方法是创建一对与该属性成员名称类似的get/set方法。

然而,这意味着如果要操作x.foo,我们必须分别调用x.get_foo()x.set_foo(value)

但是,如果我们想继续使用属性成员的访问语法(即x.foo),同时为其提供特定的访问器/修改器,那么就可以构建一个属性代理

本质上,属性代理是一个可以根据使用语义伪装成普通成员的类。可以这样定义:

template <typename T>
struct Property
{
    T value_;
    Property(const T init_value)
    {
        *this = init_value;
    }

    operator T()  //隐式类型转换,将Property隐式转换为T类型
    {
        // perform some getter action
        return value_;
    }
    T operator =(T new_value)  //赋值运算符重载
    {
        //perform some setter here
        return value_ = new_value;
    }
};

本质上,类Property<T>是底层T类型的替代品,不管这个类型是什么。它仅仅是允许与T相互转换,让二者都在幕后使用value成员。

3. 虚拟代理

如果试图对nullptr或未初始化指针解引用,那么就是在自找麻烦。

但是在某些情况下,我们只希望在访问对象时再构造该对象,而不希望过早地为它分配内存,因此在实际使用它之前将其保持为nullptr或类似未初始化的状态。
这种方法称为惰性实例化(lazy instantiation)或惰性加载(lazy loading)。

如果确切地知道哪些地方需要这种延迟行为,则可以提前计划并为它们制定特别的规定。
但如果不知道,则可以构建一个代理,让该代理接受现有对象并使其成为惰性对象。
我们称之为虚拟代理,因为底层对象可能根本不存在,所以我们不是在访问具体的对象,而是在访问虚拟的对象。

以Image接口为例进行说明:一个典型的Image接口如下:

struct Image
{
    virtual void draw() = 0;
};

Bitmap实现了Image接口,它的eager模式的实现将在构建时就从文件加载图像,即使该图像实际上并不需要任何东西:

struct Bitmap : Image
{
    std::string filename_;
    Bitmap(const std::string& filename) : filename_{filename}
    {
        std::cout << "Loading image from " << filename << std::endl;
        //image gets loaded here
    }

    void draw() override
    {
        std::cout << "Drawing image " << filename_ << std::endl;
    }
};

上面的实现并不是我们想要的,我们需要的是在调用draw()方法时才加载图像。现在我们将它变为惰性(Lazy)模式,但要假设它是固定不变的且不可修改(或者说是不可继承的):

struct LazyBitmap : Image
{
private:
    Bitmap* bmp{nullptr};
    std::string filename_;
public:
    LazyBitmap(const std::string& filename) : filename_{filename} {}
    ~LazyBitmap() {
        delete bmp; //允许对空指针进行delete操作
        // bmp = nullptr; //关于指针是否在delete之后置空,存在争议。
    }
    void draw() override
    {
        if(!bmp) {
            bmp = new Bitmap(filename_);
        }
        bmp->draw();
    }
};

如上代码所示,这个LazyBitmap的构造函数要轻量得多,它所做的只是存储要从中加载图像的文件名,仅此而已——图像不会立即加载。所有神奇的事情都发生在draw()中。

4. 通信代理

允许在改变了对象的物理位置(例如移动到了云上)的情况下使用同样的API;
具体介绍略。

5.值代理

值代理是某个值的代理,通常封装原语类型,并根据其用途提供增强的功能。

例子:我们考虑需要将一些值传递到一个函数中。该函数既可以接受具体的固定值,也可以在运行时从预定义数据集合中选择随机值。

一种方法是修改这个函数并引入几个重载函数,不过我们要修改函数的参数类型。

另一种方法是使用值代理:我们引入一个辅助类,这个类只有一个纯虚函数,负责执行隐式类型转换:

    template <typename T>
    struct Value
    {
        //该函数负责执行隐式类型转换
        virtual operator T() const = 0;
    };

基于该辅助类,引入一个代表常量值的类:

    template <typename T>
    struct Const : Value<T>
    {
        const T v_;
        Const() :v_{} {}
        Const(T v) : v_{v} {}

        operator T() const override
        {
            return v_;
        }
    };

类似地,引入一个从一组不同的值中以相同的概率随机选择一个值的类:

    template <typename T>
    struct OneOf : Value<T>
    {
        std::vector<T> values_;

        OneOf() : values_{{T{}}} {} //这是什么语法??
        OneOf(std::initializer_list<T> val) : values_{val} {}

        operator T() const override
        {
            return values_[rand() % values_.size()];
        }
    };

然后,就可以在应用程序中使用这些类型了。

6. 总结

与装饰器模式不同,代理不会尝试通过添加新成员来扩展对象的功能————它所做的只是强化现有成员的潜在行为。代理主要作为一种替代品,并且有许多不同的类型:

  1. 属性代理:是底层成员的替身,可以在分配或访问期间替换成员并执行其他操作。
  2. 虚拟代理:为底层成员提供虚拟访问的接口,并且实现了诸如惰性加载的功能。
  3. 通信代理:允许在改变了对象的物理位置的情况下使用同样的API。
  4. 值代理:可以替换单个(标量)值,并为其赋予额外的功能。

除此之外,还有很多其他代理,我们自己构建的代理很可能不属于预先存在的类别,不过它们会在我们的应用程序中执行具体而便捷的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值