C++ 在耦合策略模式中,两种行为之间需要大量计算并共享。应该在哪里进行和举行
我使用策略模式来建立一个计算模型,该模型可以有不同的行为 [第1段] 行为A和行为B源自较新的界面 [短片4] 这意味着我可以通过让其中一个行为执行model::GetResult用来执行的逻辑来改变从模型中获得结果的方式 e、 g.我将Behavior A::func更改为现在将其值与Behavior B的值相加 [短片5] 然后SomeModel::GetResult变成 [短片6] 然后行为实现变成 [第8段] 这意味着我仍然可以使用Behavior A了解Behavior B snippet5和snippet6的情况,但A仍然不知道B的实现细节 i、 e 仍然有效,与之前的代码段5和代码段6相同 问题 我的问题是,对于某些行为a和行为B,它们使用一个共同的计算值,该值很难计算,因此我只想做一次,但希望两种行为都能使用它。我不希望该计算值成为行为A或B接口的一部分,因为可能有其他行为A或B不使用它,这也意味着两种行为可能都必须实现它,因为它们不能依赖另一种行为 为了解决这个问题,可以使用许多不同的解决方案,但我不太确定哪一个是正确的/最好的 解决方案1 该模型有这个实现来计算它,并持有一个可选值,因此它只计算一次C++ 在耦合策略模式中,两种行为之间需要大量计算并共享。应该在哪里进行和举行,c++,design-patterns,C++,Design Patterns,我使用策略模式来建立一个计算模型,该模型可以有不同的行为 [第1段] 行为A和行为B源自较新的界面 [短片4] 这意味着我可以通过让其中一个行为执行model::GetResult用来执行的逻辑来改变从模型中获得结果的方式 e、 g.我将Behavior A::func更改为现在将其值与Behavior B的值相加 [短片5] 然后SomeModel::GetResult变成 [短片6] 然后行为实现变成 [第8段] 这意味着我仍然可以使用Behavior A了解Behavior B snipp
class Model
{
public:
double CalculateC()
{
if (c_)
return *c_;
c_ = SomeHeavyCalculation(); // c_ not set yet, so calculate it (heavy heavy calc).
return c_;
}
private:
std::optional<double> c_;
}
它由模型持有,行为可以使用它或填充它(如果不存在)
class Model
{
public:
ModelState& GetModelState() { return modelState_; }
private:
ModelState modelState_;
}
优点:
将模型与状态解耦表示模型保持通用性,其行为依赖于所使用的ModelState对象。当模型被实例化时,它可以根据使用的行为推断出它需要哪个状态对象
任何行为都可能触发重计算,因此行为调用顺序是不可知的
缺点:
需要一些逻辑来推断要使用哪些状态对象。实例化需要更多的复杂性
一些状态对象可能最终成为uber对象,其中包含不同行为可能使用或不使用的大量内容。引入更多使用模型“全局”值的行为,我可能需要引入其他状态对象来保存该模型“全局”值
解决方案3
引入另一种行为来执行此操作
class BehaviourC : public BehaviourInterface
{
public:
BehaviourC() : BehaviourInterface() {}
double func()
{
if (c_)
return *c_;
c_ = SomeHeavyCalculation(); // c_ not set yet, so calculate it (heavy heavy calc).
return c_;
}
private:
std::optional<double> c_;
};
优点:
每个行为都可以被沙盒化,并且对另一个行为没有要求,即使另一个行为使用相同的计算值。增加了脱钩
缺点:
每个行为执行两次SomeHeavy计算。这正是我想要减轻的
该计算本身可能希望以不同的方式实现,这实际上表明解决方案3是最佳解决方案
我不知道该怎么办????
解决方案1我不喜欢,因为我更喜欢一个更通用的模型接口,也不希望它变成某种超级具体的类。
我认为解决方案2比1好,但是,随着状态变为uber接口,解决方案1可能会遇到同样的问题。这也意味着在维护方面更令人头痛,因为需要一些与行为相关的逻辑或设计来使用模型中给定行为的正确状态对象。耦合现在不仅存在于行为之间,还存在相应的状态对象
解决方案3是我对应该做什么的直觉,但让我担心的是未来的某个时刻。。。突然之间,要使用行为a我需要行为C,要使用C我需要D等等。。。可能会发生严重耦合,使得在不知道包含其他策略的情况下很难构建某些模型
我真的不知道该用哪一个,或者我是否正确地使用了这个模式来充分发挥它的潜力。。。。或者我应该使用另一种我不知道的模式。对于问题的长度表示歉意,我真的希望我没有遗漏一些明显的东西。最好的方法是将任何预先计算的值传递给它们的构造函数中的行为,这样您就有了以下调用代码:
const double x = SomeHeavyCalculation();
const double y = SomethingElseWhichIsHeavy();
Behavior1 b1(..., x);
Behavior2 b2(..., y);
Behavior3 b3(..., x, y);
这样,行为仍然具有相同的接口,并且不相互依赖:
b1.func();
b2.func();
b3.func();
现在,您可以通过将您的行为分解为子步骤并在所有行为之间共享这些子步骤来概括这一点。您也可以将步骤建模为对象/行为,而不是原始值,并缓存结果等
此外,您还可以进行更多的泛化,允许生成计算图,甚至在计算图之间存在任何依赖关系,并自动计算解决该图的最佳方法,缓存中间结果,甚至并行化任务。当然,这是一个重大的影响
ort,但这是一般基于任务的框架所做的。最好的方法是将任何预先计算的值传递给它们的构造函数中的行为,这样您就有了以下调用代码:
const double x = SomeHeavyCalculation();
const double y = SomethingElseWhichIsHeavy();
Behavior1 b1(..., x);
Behavior2 b2(..., y);
Behavior3 b3(..., x, y);
这样,行为仍然具有相同的接口,并且不相互依赖:
b1.func();
b2.func();
b3.func();
现在,您可以通过将您的行为分解为子步骤并在所有行为之间共享这些子步骤来概括这一点。您也可以将步骤建模为对象/行为,而不是原始值,并缓存结果等
此外,您还可以进行更多的泛化,允许生成计算图,甚至在计算图之间存在任何依赖关系,并自动计算解决该图的最佳方法,缓存中间结果,甚至并行化任务。当然,这是一项重大的工作,但这是一般的基于任务的框架所做的。我将尝试根据评论来回答,尽管这个问题有点过于复杂。您的行为BehaviorA和BehaviorB都需要访问一些公共资源,并且该资源也可能稍微依赖于模型,对吗?让我们这样做:
class BehaviourInterface
{
public:
virtual ~BehaviourInterface() = default;
virtual double func() = 0;
};
class ICommonResourcesGenerator
{
public:
virtual ~ICommonResourcesGenerator() = default;
virtual double gimmeMyresource() = 0;
};
class Gen1: public ICommonResourcesGenerator
{
public:
~Gen1() override = default;
double gimmeMyresource() override {
if (!needToCalculate()) return res_;
calculate();
return res_;
}
private:
bool needToCalculate() { return res_ < 0; } // or whatever check you need to do
void calculate() { /* calc */ }
static double res_;
}
double Gen1::res_ = -1;
class BehaviourA : public BehaviourInterface
{
public:
BehaviourA(double a, std::shared_ptr<ICommonResourcesGenerator> gen):
a_(a), gen_(gen) {}
double func() { return a_ + gen_->gimmeMyresource(); }
private:
double a_;
std::shared_ptr<ICommonResourcesGenerator> gen_;
};
class BehaviourB : public BehaviourInterface
{
public:
BehaviourB(double b, std::shared_ptr<ICommonResourcesGenerator> gen):
b_(b), gen_(gen) {}
double func() { return b_ + gen_->gimmeMyresource(); }
private:
double b_;
std::shared_ptr<ICommonResourcesGenerator> gen_;
};
然后,您的模型可以使用适当的共享资源生成器初始化所需的行为:
class SomeModel
{
public:
SomeModel() {
behaviours_.push_back(std::shared_ptr<BehaviourInterface>(
new BehaviourA(1, std::make_shared<Gen1>())));
behaviours_.push_back(std::shared_ptr<BehaviourInterface>(
new BehaviourB(2, std::make_shared<Gen1>())));
}
private:
std::vector<std::shared_ptr<BehaviourInterface> > behaviours_;
};
或者,您可以将双res_u作为特定生成器的非静态字段,并将std::shared_ptr作为创建模型时创建的私有字段保留在模型中。这要看情况了
无论如何,这种方法保证行为A和B都能够访问相同的资源,而无需多次计算,并且A和B不知道资源的类型,但由模型决定。我将尝试根据评论回答,尽管问题有点过于复杂。您的行为BehaviorA和BehaviorB都需要访问一些公共资源,并且该资源也可能稍微依赖于模型,对吗?让我们这样做:
class BehaviourInterface
{
public:
virtual ~BehaviourInterface() = default;
virtual double func() = 0;
};
class ICommonResourcesGenerator
{
public:
virtual ~ICommonResourcesGenerator() = default;
virtual double gimmeMyresource() = 0;
};
class Gen1: public ICommonResourcesGenerator
{
public:
~Gen1() override = default;
double gimmeMyresource() override {
if (!needToCalculate()) return res_;
calculate();
return res_;
}
private:
bool needToCalculate() { return res_ < 0; } // or whatever check you need to do
void calculate() { /* calc */ }
static double res_;
}
double Gen1::res_ = -1;
class BehaviourA : public BehaviourInterface
{
public:
BehaviourA(double a, std::shared_ptr<ICommonResourcesGenerator> gen):
a_(a), gen_(gen) {}
double func() { return a_ + gen_->gimmeMyresource(); }
private:
double a_;
std::shared_ptr<ICommonResourcesGenerator> gen_;
};
class BehaviourB : public BehaviourInterface
{
public:
BehaviourB(double b, std::shared_ptr<ICommonResourcesGenerator> gen):
b_(b), gen_(gen) {}
double func() { return b_ + gen_->gimmeMyresource(); }
private:
double b_;
std::shared_ptr<ICommonResourcesGenerator> gen_;
};
然后,您的模型可以使用适当的共享资源生成器初始化所需的行为:
class SomeModel
{
public:
SomeModel() {
behaviours_.push_back(std::shared_ptr<BehaviourInterface>(
new BehaviourA(1, std::make_shared<Gen1>())));
behaviours_.push_back(std::shared_ptr<BehaviourInterface>(
new BehaviourB(2, std::make_shared<Gen1>())));
}
private:
std::vector<std::shared_ptr<BehaviourInterface> > behaviours_;
};
或者,您可以将双res_u作为特定生成器的非静态字段,并将std::shared_ptr作为创建模型时创建的私有字段保留在模型中。这要看情况了
无论如何,这种方法保证行为A和B都能够访问相同的资源,而无需多次计算,并且A和B不知道资源的类型,但由模型决定。您可以做的是找到一种方法,将问题减少到当前大小的1/20左右。如果有人出于某种原因,花了一个小时的时间试图通读所有内容,理解代码,找出这里要问的问题,并且碰巧也知道答案,我会非常惊讶。您的基类模型和行为接口缺少必要的虚拟析构函数。这与你提出的问题无关,我认为应该指出。这有很多代码和文本需要阅读。我支持山姆,因为你应该对这个问题稍加澄清。还要注意的是,设计模式的实际功能在您实际需要更改某些内容时发挥作用,而不是在您规划初始实现时发挥作用。在编程中,高级别的膨胀是不寻常的需要。你为什么需要它?如果您不满意bloat的级别,最好编写一些python脚本来生成此类bloat模式。向上投票获取详细问题,并列出您尝试过的内容。您可以做的是找到一种方法,将问题减少到当前大小的1/20左右。如果有人出于某种原因,花了一个小时的时间试图通读所有内容,理解代码,找出这里要问的问题,并且碰巧也知道答案,我会非常惊讶。您的基类模型和行为接口缺少必要的虚拟析构函数。这与你提出的问题无关,我认为应该指出。这有很多代码和文本需要阅读。我支持山姆,因为你应该对这个问题稍加澄清。还要注意的是,设计模式的实际功能在您实际需要更改某些内容时发挥作用,而不是在您规划初始实现时发挥作用。在编程中,高级别的膨胀是不寻常的需要。你为什么需要它?如果您对bloat.Upvoting的级别不满意,最好编写一些python脚本来生成这样的bloat模式,并列出您尝试过的内容。
class Model
{
public:
ModelState& GetModelState() { return modelState_; }
private:
ModelState modelState_;
}
class BehaviourC : public BehaviourInterface
{
public:
BehaviourC() : BehaviourInterface() {}
double func()
{
if (c_)
return *c_;
c_ = SomeHeavyCalculation(); // c_ not set yet, so calculate it (heavy heavy calc).
return c_;
}
private:
std::optional<double> c_;
};
class BehaviourA : public BehaviourAInterface
{
public:
BehaviourA(Model& model, double a) : BehaviourWithModelKnowledgeInterface(model), a_(a) {}
double funcA() { return SomeHeavyCalculation() + a_; }
private:
double a_;
};
class BehaviourB : public BehaviourBInterface
{
public:
BehaviourB(Model& model, double b) : BehaviourWithModelKnowledgeInterface(model), b_(b) {}
double funcB() { return SomeHeavyCalculation() + b_; }
private:
double b_;
};
const double x = SomeHeavyCalculation();
const double y = SomethingElseWhichIsHeavy();
Behavior1 b1(..., x);
Behavior2 b2(..., y);
Behavior3 b3(..., x, y);
b1.func();
b2.func();
b3.func();
class BehaviourInterface
{
public:
virtual ~BehaviourInterface() = default;
virtual double func() = 0;
};
class ICommonResourcesGenerator
{
public:
virtual ~ICommonResourcesGenerator() = default;
virtual double gimmeMyresource() = 0;
};
class Gen1: public ICommonResourcesGenerator
{
public:
~Gen1() override = default;
double gimmeMyresource() override {
if (!needToCalculate()) return res_;
calculate();
return res_;
}
private:
bool needToCalculate() { return res_ < 0; } // or whatever check you need to do
void calculate() { /* calc */ }
static double res_;
}
double Gen1::res_ = -1;
class BehaviourA : public BehaviourInterface
{
public:
BehaviourA(double a, std::shared_ptr<ICommonResourcesGenerator> gen):
a_(a), gen_(gen) {}
double func() { return a_ + gen_->gimmeMyresource(); }
private:
double a_;
std::shared_ptr<ICommonResourcesGenerator> gen_;
};
class BehaviourB : public BehaviourInterface
{
public:
BehaviourB(double b, std::shared_ptr<ICommonResourcesGenerator> gen):
b_(b), gen_(gen) {}
double func() { return b_ + gen_->gimmeMyresource(); }
private:
double b_;
std::shared_ptr<ICommonResourcesGenerator> gen_;
};
class SomeModel
{
public:
SomeModel() {
behaviours_.push_back(std::shared_ptr<BehaviourInterface>(
new BehaviourA(1, std::make_shared<Gen1>())));
behaviours_.push_back(std::shared_ptr<BehaviourInterface>(
new BehaviourB(2, std::make_shared<Gen1>())));
}
private:
std::vector<std::shared_ptr<BehaviourInterface> > behaviours_;
};