c++ - 需要大量计算,并且需要在耦合策略模式下的两个行为之间共享。应该在哪里完成并举行

标签 c++ design-patterns

我正在使用策略模式来具有可以具有不同行为的计算模型。

[snippet1]

class BehaviourInterface
{
public:
    BehaviourInterface() {}
    virtual double func() = 0;
};

class Model
{
public:
    std::vector<std::shared_ptr<BehaviourInterface>> behaviours_;
};

class BehaviourA : public BehaviourInterface
{
public:
    BehaviourA(double a) : BehaviourInterface(), a_(a), c_(0) {}
    double func() { return a_; }
private:
    double a_;
};

class BehaviourB : public BehaviourInterface
{
public:
    BehaviourB(double b) : BehaviourInterface(), b_(b) {}
    double func() { return b_; }
private:
    double b_;
};

然后,我可以创建一个具有两种行为的模型。在此示例中,模型仅将每种行为的值相加。

[snippet2]
class SomeModel : public Model
{
public:
    SomeModel()
    {
        // Construct model with a behaviourA and behaviourB.
        behaviours_.push_back(std::shared_ptr<BehaviourInterface>(new BehaviourA(1))); 
        behaviours_.push_back(std::shared_ptr<BehaviourInterface>(new BehaviourB(2)));
    }

    double GetResult()
    {   
        // Sum the values from each behaviour.
        double result = 0;
        for (auto bItr = behaviours_.begin(); bItr != behaviours_.end(); ++bItr)
            result += (*bItr)->func();
        return result;
    }
}

int main(int argc, char** argv)
{
    SomeModel sm;
    double result = sm.GetResult();     // result = behaviourA + behaviourB = 1 + 2 = 3;
}

运行良好,允许我实现不同的行为,每个行为都与所有其他行为沙盒化(分离策略)。

我对此进行了略微扩展,以允许行为能够访问它们所属的模型(通过引入另一个保存模型的行为接口(interface)),这使一个行为能够通过两个行为都属于的模型挂接到另一个行为上。在这方面,它不是纯粹的策略模式(因为行为可以了解其他行为),但是它仍然足够通用,因此行为无需了解其他行为的实现细节。例如

我介绍BehaviourWithModelKnowledgeInterface
[snippet3]
class BehaviourWithModelKnowledgeInterface : public BehaviourInterface
{
public:
    BehaviourWithModelKnowledgeInterface(Model& model) : model_(model) {}
protected:
    Model& model_;
}
BehaviourABehaviourB从较新的接口(interface)派生而来...

[snippet4]
class BehaviourA : public BehaviourWithModelKnowledgeInterface
{
public:
    BehaviourA(Model& model, double a) : BehaviourWithModelKnowledgeInterface(model), a_(a), c_(0) {}
    double func() { return a_; }
private:
    double a_;
};

class BehaviourB : public BehaviourWithModelKnowledgeInterface
{
public:
    BehaviourB(Model& model, double b) : BehaviourWithModelKnowledgeInterface(model), b_(b) {}
    double func() { return b_; }
private:
    double b_;
};

这意味着我可以通过让一种行为执行Model::GetResult()曾经执行的逻辑来改变从模型中获得结果的方式。

例如我将BehaviourA::func()更改为现在将其值与BehaviourB的值相加。

[snippet5]
class BehaviourA : public BehaviourWithModelKnowledgeInterface
{
public:
    BehaviourA(Model& model, double a) : BehaviourWithModelKnowledgeInterface(model), a_(a), c_(0) {}
    double func() 
    {
        // Get the value of behaviourB, and add to this behaviours value..
        return a_ + model_.behaviours_[1].func();
    }
private:
    double a_;
};

然后SomeModel::GetResult()变成...

[snippet6]
class SomeModel : public Model
{
public:
    SomeModel()
    {
        // Construct model with a behaviourA and behaviourB.
        behaviours_.push_back(std::shared_ptr<BehaviourInterface>(new BehaviourA(1))); 
        behaviours_.push_back(std::shared_ptr<BehaviourInterface>(new BehaviourB(2)));
    }

    double GetResult()
    {   
        // Just get the result from behaviourA, as this will add BehaviourB as part of BehaviourA's implementation.
        double result = behaviours_[0].func();
    }
}

int main(int argc, char** argv)
{
    SomeModel sm;
    double result = sm.GetResult();     // result = behaviourA = 1 + behaviourB = 1 + 2 = 3
}

因此,BehaviourA现在只能是具有ModelBehaviourB的一部分。不是纯粹的策略模式(因为一种行为依赖于另一种行为),但是此限制仍然可以,因为这些行为可以再次扩展,从而提供了策略模式的灵活性元素,尽管与原始示例相比容量有限(TIL这称为耦合策略;))。

[snippet7]
class BehaviourAInterface : public BehaviourWithModelKnowledgeInterface
{
public:
    BehaviourAInterface(Model& model) : BehaviourWithModelKnowledgeInterface(model) {}
    virtual double funcA() {}
    double func() { return funcA(); }
}

class BehaviourBInterface : public BehaviourWithModelKnowledgeInterface
{
public:
    BehaviourBInterface(Model& model) : BehaviourWithModelKnowledgeInterface(model) {}
    virtual double funcA() {}
    double func() { return funcB(); }
}

然后行为实现变成了...

[snippet8]
class BehaviourA : public BehaviourAInterface
{
public:
    BehaviourA(Model& model, double a) : BehaviourWithModelKnowledgeInterface(model), a_(a), c_(0) {}
    double funcA() { return a_; }
private:
    double a_;
};

class BehaviourB : public BehaviourBInterface
{
public:
    BehaviourB(Model& model, double b) : BehaviourWithModelKnowledgeInterface(model), b_(b) {}
    double funcB() { return b_; }
private:
    double b_;
};

这意味着我仍然可以使用BehaviourA知道的关于BehaviourB的情况(snippet5和snippet6),但是A仍然不了解B的实现细节。


class BehaviourA : public BehaviourAInterface
{
public:
    BehaviourA(Model& model, double a) : BehaviourAInterface(model), a_(a), c_(0) {}
    double funcA() { return a_ + model_.behaviours_[1].func(); }
private:
    double a_;
};

仍然有效,与snippet5和snippet6相同。

问题

我的问题是,对于某些BehaviourABehaviourB来说,它们使用共同的计算值,并且此值很重,因此我只想执行一次,但希望两种行为都使用它(可能)。我不希望此计算值包含在行为A或B接口(interface)的一部分中,因为可能会有其他行为A或B不使用它,并且这也暗示这两种行为都可能必须实现,因为它们不能依赖于另一个拥有它。

为了解决这个问题,可以使用许多不同的解决方案,但是我不太确定哪个是正确的/最适合使用。

解决方案1

该模型具有此实现来对其进行计算,并且具有可选功能,因此仅计算一次。
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_;
}

优点:BehaviourABehaviourB都不必保留它。
BehaviourABehaviourB都不需要彼此了解(点头指向分离的策略模式)

任何行为都可以使用它。

缺点:
并非每个行为(或某些实现中的任何行为)都可能需要它。

现在,模型有些特化,并且缺乏概括性。

模型可能会变成一些 super 状态对象,其中包含所有可能由不同行为使用或可能不使用的可能值。杂乱的大界面可能。

解决方案2

一个“模型全局状态”对象,可以保存某些行为可能填充而其他行为使用的任意值。
class ModelState
{
public:
    double GetC() 
    { 
        if (c_)
            return *c_;
        c_ = SomeHeavyCalculation();        // c_ not set yet, so calculate it (heavy heavy calc).
        return c_;
    }
private:
    std::optional<double> c_;
}

它由Model持有,并且行为可以使用它(如果不存在,则可以填充它)
class Model
{
public:
    ModelState& GetModelState() { return modelState_; }
private:
    ModelState modelState_;
}

优点:
Model与状态解耦意味着Model仍然是通用的,并且其行为依赖于所使用的ModelState对象。 (当实例化Model时,它可以根据所使用的行为来推断需要重用的状态对象)。

任何行为都会触发繁重的计算,因此行为调用顺序是不可知的。

缺点:
需要一些逻辑来推断要使用的状态对象。实例化需要更多的复杂性。

某些状态对象可能最终是包含对象负载的超对象,这些负载可能由不同行为使用或可能不使用。引入更多的行为,这些行为使用模型“全局”值,并且可能我必须引入其他状态对象来保存此模型“全局”值。

解决方案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_;
};

优点:
保持Model通用,不会比使用行为可以了解其他行为的“设计”设计再次降低策略模式的灵活性(同样不是完全纯净的策略模式,但仍然很灵活)。

缺点:
我们想对行为执行其他行为所需要的操作进行多细化(尽管仔细考虑,这类似于三个重构的规则...如果两个或多个行为需要进行沉重的计算,那么calc”这件事成为另一种行为)。

行为的依赖关系可能会变成雷区...突然要使用BehaviourA,我们需要BehaviourCBehaviourBBehaviourD等...虽然我已经介绍了行为之间的潜在依赖关系(耦合),但目前相当少,并且希望保留尽量减少。

使用另一个Behaviour意味着将来该列表可能会变大,从而失去行为模式的更多纯度,并且由于某些行为而需要大量依赖关系。非常耦合!

解决方案4

每种行为都会计算出自己的值(value)。
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_;
};

优点:
每个行为都可以在某种程度上进行沙盒化,即使其他行为使用相同的计算值,也不需要其他行为。去耦增加。

缺点:
每种行为将SomeHeavyCalculation()计算两次。这正是我要缓解的问题!

该计算本身可能需要以不同的方式实现(实际上,这表明解决方案3是最佳解决方案)。

我不知道该怎么办?

解决方案1我不喜欢,因为我希望使用更通用的模型接口(interface),也不希望它成为一些更具体的类。
我认为解决方案2优于1,但是在状态1变为 super 接口(interface)的情况下,也会遇到与解决方案1相同的问题。在维护方面,这也意味着更多的麻烦,因为需要某种逻辑或设计,这些逻辑或设计与行为相关,以便在给定模型中的行为的情况下使用正确的状态对象。现在,耦合不仅在行为之间,而且还存在相应的状态对象。

解决方案3是我对应该做什么的直觉,但是让我担心的是将来的某个时刻...突然之间,要使用BehaviourA我需要一个BehaviourC,要使用CI需要一个D等等...很重可能会发生耦合,使得在不知道包括其他策略的情况下很难构建某些模型。

我真的不知道要使用哪个模式,或者我是否正确使用此模式以发挥全部潜力....或者我是否应该使用另一种模式(我不知道)。对于问题的长度,我们深表歉意,我真的希望我不要在这里遗漏一些明显的东西。

最佳答案

最好的方法是将任何预先计算的值传递给其构造函数中的行为,以便您具有以下调用代码:

const double x = SomeHeavyCalculation();
const double y = SomethingElseWhichIsHeavy();

Behavior1 b1(..., x);
Behavior2 b2(..., y);
Behavior3 b3(..., x, y);

这样,行为仍然具有相同的接口(interface),并且彼此不依赖:
b1.func();
b2.func();
b3.func();

现在,您可以通过将您的行为分解为子步骤并在所有行为之间共享这些子步骤来对此进行概括。您也可以将步骤建模为对象/行为,而不是原始值,并缓存结果等。

此外,您可以泛化更多内容,并允许以任何依赖关系(甚至在它们之间)进行计算图的生成,并自动计算最佳方法来解决该图,缓存中间结果甚至使任务并行化。当然,这是一项重大的工作,但这是常规的基于任务的框架所要做的。

关于c++ - 需要大量计算,并且需要在耦合策略模式下的两个行为之间共享。应该在哪里完成并举行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60508091/

相关文章:

c++ - boost::shared_ptr 循环中断与weak_ptr

oop - 依赖倒置原则的第二个陈述

javascript揭示模块模式和jquery

python - Python 导演类中的 Swig 异常

c++ - 带有初始化列表的空构造函数是否被认为是微不足道的?

java - 在 Spring bean 中实现空对象设计模式

java - 我如何用枚举中每种环境类型的值列表来表示键?

c# - 在C#中使用继承类作为实例List

c++ - 程序能否计算出其 Oracle 资源使用情况?

c++ - 为什么 std::abs(9484282305798401ull) = 9484282305798400?