装饰器模式的线程安全
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 对象创建新实例。
如果您需要计划的不同数据类型相对可以相互替代(例如 int
与 double
),那么您可以尝试模板化 IFoo
.
实际上,引入 IFooWorkspace
只有在子类之间有很多共享状态时才有意义——即如果 IFooWorkspace
的实际实现中包含某些内容。不然何苦呢?您没有实现 Workspace
和 FooInt
之间的解耦——前者只会为后者而构建,没有前者,后者将一事无成。您不妨将 _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);
}
};
这很长,但我会尽量讲清楚。
我有一个接口,让我们称它为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 对象创建新实例。
如果您需要计划的不同数据类型相对可以相互替代(例如 int
与 double
),那么您可以尝试模板化 IFoo
.
实际上,引入 IFooWorkspace
只有在子类之间有很多共享状态时才有意义——即如果 IFooWorkspace
的实际实现中包含某些内容。不然何苦呢?您没有实现 Workspace
和 FooInt
之间的解耦——前者只会为后者而构建,没有前者,后者将一事无成。您不妨将 _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);
}
};