线程安全与装饰图案

时间:2015-08-25 20:31:18

标签: c++ multithreading oop design-patterns

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

我有一个界面,我们称之为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接口的客户端将首先调用传递其reset()对象的Bar方法。然后,他们将使用不同的整数参数调用calculate()方法数百万次。 IFoo实现将在调用reset()时从Bar对象中提取一些信息并使用它来设置一些内部成员,然后他们将使用这些内部成员来装饰底层IFoo的结果。 1}}对象。他们从bar对象中提取的内容取决于实现,可能是不同的类型/值。

只要有一个客户端使用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对象创建新实例。

2 个答案:

答案 0 :(得分:2)

  

理想情况下,我想避免为IFoo对象创建新实例。

但是你创建工作空间的新实例没有问题,所以我可以假设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);
    }
};

答案 1 :(得分:0)

如果您需要规划的不同数据类型是相对可互换的(例如int vs double),那么您可以尝试模板IFoo

实际上,只有在子类之间存在大量共享状态时才引入IFooWorkspace才有意义 - 即。如果IFooWorkspace的实际实现有其中的内容。否则为什么要打扰?你没有实现WorkspaceFooInt之间的脱钩 - 前者只能为后者建立,后者只有没有前者才能实现。您也可以将_y_z作为FooInt的一部分。但是如果有一个有意义的工作区子类层次结构,那么我认为你的设计是可以的,除了这些工作空间将被调用的地方的问题,但我想调用代码将处理它。