[英]Heavy calculation required and shared between two behaviours in coupled strategy pattern. Where should it be done and held
我正在使用策略模式來擁有一個可以具有不同行為的計算模型。
[片段1]
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_;
};
然后我可以創建一個具有兩種行為的模型。 在這個例子中,模型只是簡單地將每個行為的值相加。
[片段2]
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;
}
效果很好,允許我實現不同的行為,每個行為都與所有其他行為(解耦策略)隔離。
我稍微擴展了這一點,以允許行為訪問它們所屬的模型(通過引入另一個保存模型的行為接口),這允許行為能夠通過兩個行為所屬的模型掛鈎到另一個行為。 在這方面,它不是純粹的策略模式(因為行為可以知道其他行為),但是它仍然可以足夠通用,因此行為不需要知道其他行為的實現細節。 例如
我介紹BehaviourWithModelKnowledgeInterface
[片段3]
class BehaviourWithModelKnowledgeInterface : public BehaviourInterface
{
public:
BehaviourWithModelKnowledgeInterface(Model& model) : model_(model) {}
protected:
Model& model_;
}
並且BehaviourA
和BehaviourB
派生自較新的界面...
[片段4]
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
的值相加。
[片段5]
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()
變成...
[片段6]
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
現在只能是具有BehaviourB
的Model
一部分。 不是純粹的策略模式(因為一種行為依賴於另一種行為),但是這個限制仍然可以,因為這些行為可以再次擴展,提供策略模式的靈活性元素,盡管與原始示例相比容量有限(TIL這稱為耦合策略;))。
[片段7]
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(); }
}
然后行為實現變成......
[片段8]
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 的實現細節。
IE
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 相同。
我遇到的問題是,對於某些BehaviourA
和BehaviourB
,它們使用一個共同的計算值,並且這個值計算量很大,所以我只想做一次,但希望它被兩種行為(可能)使用。 我不希望這個計算值成為行為 A 或 B 接口的一部分,因為可能有其他行為 A 或 B 不使用它,這也意味着這兩種行為可能都必須實現它,因為它們不能依賴另一個擁有它。
為了解決這個問題,可以使用許多不同的解決方案,但我不太確定哪一個是正確/最好的。
該模型有這個實現來計算它,並持有一個可選的所以它只計算一次。
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_;
}
優點: BehaviourA
或BehaviourB
都不必持有它。
BehaviourA
和BehaviourB
都不需要相互了解(向解耦策略模式點頭)
任何行為都可以使用它。
缺點:並非每個行為(或某些實現中的任何行為)甚至都需要它。
模型現在有些專業化,失去了一些概括性。
模型可能會變成一些超級狀態對象,其中包含所有可能的值,這些值可能會或可能不會被不同的行為使用。 潛在的大接口凌亂。
一個“模型全局狀態”對象,它可以保存某些行為可能填充的任意值,而其他行為可能會使用這些值。
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
被實例化時,它可以根據使用的行為推斷出它需要哪個狀態對象)。
任何行為都可以觸發重計算,因此行為調用順序是不可知的。
缺點:需要一些邏輯來推斷使用哪些狀態對象。 實例化需要更多的復雜性。
一些狀態對象可能最終成為包含大量事物的超級對象,這些事物可能會或可能不會被不同的行為使用。 引入更多使用模型“全局”值的行為,並且可能我必須引入其他狀態對象來保存此模型“全局”值。
引入另一種行為來做到這一點。
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
通用性,不會比使用行為可以了解其他行為的“一些東西”的設計降低策略模式的靈活性(同樣不是完全純粹的策略模式,但仍然靈活)。
缺點:對於執行其他行為所需的操作的行為,我們想要多細化(盡管考慮一下,這類似於三重重構規則......如果兩個或多個行為需要一些難以計算的東西,那就是'計算重的事情變成了另一種行為)。
行為的依賴可能會變成雷區……突然要使用BehaviourA
,我們需要BehaviourC
, BehaviourB
BehaviourD
等等……雖然我已經介紹了行為之間的這種潛在依賴(耦合),但它目前相當小,希望保持它盡可能小。
擁有另一種Behaviour
意味着將來該列表可能會變大,從而失去行為模式的更多純度,並且需要大量的依賴項給定某些行為。 很耦合!
每個行為都會計算它自己的價值。
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 我不喜歡,因為我更喜歡更通用的模型接口,並且不希望它成為某個超級具體的類。 我認為解決方案 2 比 1 更好,但是遇到與解決方案 1 相同的問題,狀態變為超級接口。 這也意味着在維護方面更令人頭疼,因為需要有一些與行為相關的邏輯或設計,以使用給定模型中的行為的正確狀態對象。 耦合現在不僅僅是行為之間,還有相應的狀態對象。
解決方案 3 是我對應該做什么的直覺,但讓我擔心的是在未來的某個時刻......突然之間,使用BehaviourA
我需要一個BehaviourC
,使用 CI 需要一個 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();
現在,您可以通過將您的行為分解為子步驟並在所有行為之間共享這些子步驟來概括這一點。 您也可以將步驟建模為對象/行為,而不是原始值,並緩存結果等。
此外,您可以進行更多概括並允許計算圖發生,具有任何依賴關系(甚至它們之間),並自動計算解決圖的最佳方法,緩存中間結果,甚至並行化任務。 當然,這是一項重大的努力,但這是基於任務的通用框架所做的。
我會盡量根據評論來回答,不過這個問題有點過於復雜了。 你的行為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_;
};
或者,您可能將double res_
作為特定生成器的非靜態字段,並將std::shared_ptr<Gen>
保留在Model
作為創建模型時創建的私有字段。 “這一切都取決於……”。
無論如何,這種方法保證了行為 A 和 B 都可以訪問相同的資源而無需多次計算,並且 A 和 B 不知道資源的種類,而是由模型決定。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.