装饰器模式的线程安全

Thread safety with decorator pattern

这很长,但我会尽量讲清楚。

我有一个接口,让我们称它为IFoo

class IFoo
{
public:
    virtual void reset(const Bar* bar) = 0;
    virtual int calculate(int i) const = 0;
};

基本实现

class FooBasic : public IFoo
{
private:
    int _value;
public:
    FooBasic(int value) : _value(value) {}

    virtual void reset(const Bar* bar) override {}
    virtual int calculate(int i) const override { return _value; }
};

我有一个基本的界面装饰器

class FooDecorator : public IFoo
{
protected:
    IFoo* _foo;
public:
    FooDecorator(IFoo* foo) : _foo(foo) {}

    virtual void reset(const Bar* bar) override { _foo->reset(bar); }
    virtual int calculate(int i) const override { return _foo->calculate(i); }
};

我有大量的装饰器实现,它们的形式几乎相同

class FooInt : public FooDecorator
{
private:
    int _y;
    int _z;
public:
    FooInt(IFoo* foo) : FooDecorator(foo), _y(0), _z(0) {}

    virtual void reset(const Bar* bar) override
    {
        _foo->reset(bar);
        _y = bar->expensiveIntFunction(15);
        _z = bar->expensiveIntFunction(10);
    }
    virtual int calculate(int i) const override { return _y*_foo->calculate(i) + _z; }
};

class FooIntStar : public FooDecorator
{
private:
    const int* _z;
public:
    FooIntStar(IFoo* foo) : FooDecorator(foo), _z(nullptr) {}

    virtual void reset(const Bar* bar) override
    {
        _foo->reset(bar);
        _z = bar->expensiveIntStarFunction();
    }
    virtual int calculate(int i) const override { return _foo->calculate(i)*_z[i]; }
};

这里的想法是,IFoo 接口的客户端将首先调用传入其 Bar 对象的 reset() 方法。然后他们将使用不同的整数参数调用 calculate() 方法数百万次。 IFoo 实现将在调用 reset() 时从 Bar 对象中提取一些信息,并使用它来设置一些内部成员,然后它们将使用这些成员来修饰底层 IFoo 对象。他们从 bar 对象中提取的内容取决于实现,可能会有所不同 types/values.

只要有一个客户端使用 IFoo 实现,这一切都可以正常工作。我现在正在尝试引入一些线程,其中我有不同的客户端同时使用 IFoo 实现。每个客户端都有自己的 Bar 对象,但我希望他们使用 IFoo 对象的相同实例(如果可能)。在当前状态下,这将不起作用,因为对 reset() 的调用设置了成员变量,即 reset() 方法不是 const.

我计划创建一个工作区对象,然后将其传递。每个客户端都将拥有自己的工作区实例,以避免在多个客户端使用同一对象时发生冲突。在调用重置时,IFoo 实现将在工作区中设置临时数据,而不使用内部成员。但是,由于不同实现所需的数据类型不同,因此提前设计此工作区非常棘手。

我想到的一个想法是使这个工作区抽象并让实现扩展它。所以我正在考虑将界面更改为

// Totally undefined object
struct IFooWorkspace
{
    virtual ~IFooWorkspace(){}
};

class IFoo
{
public:
    virtual IFooWorkspace* createWorkspace() const = 0;
    virtual void reset(IFooWorkspace* workspace, const Bar* bar) const = 0;
    virtual int calculate(IFooWorkspace* workspace, int i) const = 0;
};

例如 FooInt 实现

class FooInt : public FooDecorator
{
private:
    // Struct definition instead of member variables
    struct WorkSpace : IFooWorkspace
    {
        WorkSpace() : _y(0), _z(0), _ws(nullptr) {}
        virtual ~WorkSpace(){ delete _ws; }
        int _y;
        int _z;
        IFooWorkspace* _ws;
    };
public:
    FooInt(IFoo* foo) : FooDecorator(foo) {}

    virtual IFooWorkspace* createWorkspace() const override
    {
        WorkSpace* ws = new WorkSpace;
        ws->_ws = _foo->createWorkspace();
        return ws;
    }

    virtual void reset(IFooWorkspace* workspace, const Bar* bar) const override
    {
        WorkSpace& ws = dynamic_cast<WorkSpace&>(*workspace);
        ws._y = bar->expensiveIntFunction(15);
        ws._z = bar->expensiveIntFunction(10);
        _foo->reset(ws._ws, bar);
    }

    virtual int calculate(IFooWorkspace* workspace, int i) const override
    {
        const WorkSpace& ws = dynamic_cast<WorkSpace&>(*workspace);
        return ws._y*_foo->calculate(ws._ws, i) + ws._z;
    }
};

所以新的计划是客户端首先调用 createWorkspace() 并拥有返回的 IFooWorkspace 对象。然后对 reset()calculate() 的后续调用应该传递此工作区。

我的问题是它看起来很复杂,而且我不确定 dynamic_cast 的安全性和性能。所以问题是:这里有没有更简单、更干净的方法来实现线程安全?理想情况下,我想避免为 IFoo 对象创建新实例。

如果您需要计划的不同数据类型相对可以相互替代(例如 intdouble),那么您可以尝试模板化 IFoo.

实际上,引入 IFooWorkspace 只有在子类之间有很多共享状态时才有意义——即如果 IFooWorkspace 的实际实现中包含某些内容。不然何苦呢?您没有实现 WorkspaceFooInt 之间的解耦——前者只会为后者而构建,没有前者,后者将一事无成。您不妨将 _y_z 作为 FooInt 的一部分。但是如果工作区子类有一个有意义的层次结构,那么我认为你的设计是好的,除了这些工作区将在哪里被调用的问题,但我想调用代码会处理这个问题。

Ideally I would like to avoid creating new instances for the IFoo objects.

但是创建工作区的新实例没有问题,所以我可以假设它是 foo 对象的构造在某种程度上是痛苦的。

我认为工厂模式会适合。

class IFooFactory
{
public:
    virtual IFoo create(const Bar* bar) = 0;
};

class IFoo
{
public:
    virtual int calculate(int i) const = 0;
};

Foo 会变得像您的 Workspace 对象,即使它们创建起来很复杂,工厂也会封装该工作。 reset 已成为 create 方法。

工厂可以在线程之间安全地共享,因为它们没有可变状态。

示例工厂:

class FooIntFactory : public IFooFactory
{
public:
    virtual IFoo create(const Bar* bar) const override
    {
        return new FooInt(bar);
    }
};