尽管存在强依赖类,但设计灵活
Flexible design despite strongly dependent classes
我正在编写一个代码,它本质上需要非常灵活,也就是说,以后很容易被其他人扩展。但我现在面临着一个问题,我甚至不知道如何正确处理这个问题:
我有一个相当复杂的Algorithm
,它在某个点应该收敛。但由于其复杂性,有几个不同的标准来检查收敛性,根据情况(或输入),我希望激活不同的收敛标准。此外,应该可以很容易地创建新的收敛标准,而不必接触算法本身。因此,理想情况下,我希望有一个抽象的ConvergenceChecker
类,我可以从中继承,并让算法有一个向量,例如:
//Algorithm.h (with include guards of course)
class Algorithm {
//...
vector<ConvergenceChecker*> _convChecker;
}
//Algorithm.cpp
void runAlgorithm() {
bool converged=false;
while(true){
//Algorithm performs a cycle
for (unsigned i=0; i<_convChecker.size(); i++) {
// Check for convergence with each criterion
converged=_convChecker[i]->isConverged();
// If this criterion is not satisfied, forget about the following ones
if (!converged) { break; }
}
// If all are converged, break out of the while loop
if (converged) { break; }
}
}
这样做的问题是,每个ConvergenceChecker
都需要了解有关当前运行的Algorithm
的一些信息,但每个CCD_4可能需要了解与算法完全不同的信息。假设Algorithm
在每个周期中改变_foo
、_bar
和_fooBar
,但一个可能的ConvergenceChecker
只需要知道_foo
,另一个_foo
和_bar
,可能有一天会实现需要_fooBar
的ConvergenceChecker
。以下是我已经尝试过的解决方法:
- 给函数
isConverged()
一个大参数列表(包含_foo
、_bar
和_fooBar
)。缺点:在大多数情况下,大多数用作参数的变量都不会被使用,如果Algorithm
将由另一个变量扩展(或类似的算法从中继承并添加一些变量),则必须修改相当多的代码。->可能,但丑陋 - 将函数
isConverged()
本身(或指向它的指针)作为参数。问题:循环依赖关系 - 将
isConverged()
声明为友元函数。问题(以及其他问题):无法定义为不同ConvergenceChecker
s的成员函数 - 使用函数指针数组。根本不能解决问题,而且:在哪里定义它们
- (刚在写这个问题时想到的)使用另一个保存数据的类,比如
AlgorithmData
,将Algorithm
作为友元类,然后提供AlgorithmData
作为函数参数。所以,就像2。但也许可以解决循环依赖问题。(尚未对此进行测试。)
我很高兴听到你对此的解决方案(以及你在5中看到的问题)
进一步说明:
- 问题标题:我知道"强依赖类"已经表明,很可能有人在设计代码时做错了什么,但我想很多人最终可能会遇到这个问题,并希望听到避免它的可能性,所以我宁愿保留这个丑陋的表达
- 太容易了?:事实上,我在这里提出的问题并不完整。代码中会有很多不同的
Algorithm
,它们相互继承,当然,即使出现新的Algorithm
,ConvergenceChecker
也应该在适当的情况下工作,而不需要任何进一步的修改。欢迎对此发表评论 - 提问风格:我希望这个问题不要太抽象也不要太特别,我希望它不要太长,可以理解。因此,请不要犹豫,对我提出这个问题的方式发表评论,以便我能够改进
实际上,您的解决方案5听起来不错。
当面临引入循环依赖关系的危险时,最好的补救措施通常是提取两者都需要的部分,并将其移动到一个单独的实体;就像将算法使用的数据提取到单独的类/结构中一样!
另一个解决方案是向检查器传递一个对象,该对象提供当前算法状态,以响应以字符串名称表示的参数名称。这使得单独编译转换策略成为可能,因为即使在算法中添加更多参数,这个"回调"接口的接口也保持不变:
struct AbstractAlgorithmState {
virtual double getDoubleByName(const string& name) = 0;
virtual int getIntByName(const string& name) = 0;
};
struct ConvergenceChecker {
virtual bool converged(const AbstractAlgorithmState& state) = 0;
};
这就是收敛检查器的所有实现者需要看到的:他们实现检查器,并获得状态。
现在,您可以构建一个与算法实现紧密耦合的类来实现AbstractAlgorithmState
,并根据其名称获取参数。这个紧密耦合的类对您的实现是私有的:调用者只看到它的接口,它永远不会改变:
class PrivateAlgorithmState : public AbstractAlgorithmState {
private:
const Algorithm &algorithm;
public:
PrivateAlgorithmState(const Algorithm &alg) : algorithm(alg) {}
...
// Implement getters here
}
void runAlgorithm() {
PrivateAlgorithmState state(*this);
...
converged=_convChecker[i]->converged(state);
}
使用单独的数据/状态结构似乎很容易——只需将其作为只读访问的常量引用传递给检查器即可。
class Algorithm {
public:
struct State {
double foo_;
double bar_;
double foobar_;
};
struct ConvergenceChecker {
virtual ~ConvergenceChecker();
virtual bool isConverged(State const &) = 0;
}
void addChecker(std::unique_ptr<ConvergenceChecker>);
private:
std::vector<std::unique_ptr<ConvergenceChecker>> checkers_;
State state_;
bool isConverged() {
const State& csr = state_;
return std::all_of(checkers_.begin(),
checkers_.end(),
[csr](std::unique_ptr<ConvergenceChecker> &cc) {
return cc->isConverged(csr);
});
}
};
也许decorator模式可以帮助简化一组(未知)收敛检查。通过这种方式,您可以使算法本身与可能发生的收敛检查无关,并且不需要用于所有检查的容器。
你会得到这样的东西:
class ConvergenceCheck {
private:
ConvergenceCheck *check;
protected:
ConvergenceCheck(ConvergenceCheck *check):check(check){}
public:
bool converged() const{
if(check && check->converged()) return true;
return thisCheck();
}
virtual bool thisCheck() const=0;
virtual ~ConvergenceCheck(){ delete check; }
};
struct Check1 : ConvergenceCheck {
public:
Check1(ConvergenceCheck* check):ConvergenceCheck(check) {}
bool thisCheck() const{ /* whatever logic you like */ }
};
然后,您可以进行收敛检查的任意复杂组合,同时在Algorithm
中只保留一个ConvergenceCheck*
成员。例如,如果要检查两个条件(在Check1
和Check2
中实现):
ConvergenceCheck *complex=new Check2(new Check1(nullptr));
代码并不完整,但你已经明白了。此外,如果您是一个性能狂热者,并且害怕虚拟函数调用(thisCheck
),则可以应用奇怪的返回模板模式来消除这种情况。
以下是一个完整的装饰器示例,用于检查int
上的约束,以了解其工作原理:
#include <iostream>
class Check {
private:
Check *check_;
protected:
Check(Check *check):check_(check){}
public:
bool check(int test) const{
if(check_ && !check_->check(test)) return false;
return thisCheck(test);
}
virtual bool thisCheck(int test) const=0;
virtual ~Check(){ delete check_; }
};
class LessThan5 : public Check {
public:
LessThan5():Check(NULL){};
LessThan5(Check* check):Check(check) {};
bool thisCheck(int test) const{ return test < 5; }
};
class MoreThan3 : public Check{
public:
MoreThan3():Check(NULL){}
MoreThan3(Check* check):Check(check) {}
bool thisCheck(int test) const{ return test > 3; }
};
int main(){
Check *morethan3 = new MoreThan3();
Check *lessthan5 = new LessThan5();
Check *both = new LessThan5(new MoreThan3());
std::cout << morethan3->check(3) << " " << morethan3->check(4) << " " << morethan3->check(5) << std::endl;
std::cout << lessthan5->check(3) << " " << lessthan5->check(4) << " " << lessthan5->check(5) << std::endl;
std::cout << both->check(3) << " " << both->check(4) << " " << both->check(5);
}
输出:
0 1 1
1 1 0
0 1 0
- C++模板来检查友元函数的存在
- 如何从C++中的依赖类型中获得它所依赖的类型
- 检查是否存在模板方法,而不依赖自动模板扣除
- 在C++的松弛记忆模型中是否存在具有依赖周期的非因果行为
- OOP设计:一个对象依赖于其所有依赖项的存在
- CMAKE:目标依赖项存在问题(add_dependency不起作用?
- 当存在许多相互依赖的项目时,Visual C++中的文件夹结构
- 如何使用type_traits生成依赖于是否存在类专用化的代码?
- 如何检查给定文件是否存在依赖项错误
- 当C++模板类中存在循环依赖关系时,修复包含顺序
- 使用发布 CRT 在调试中构建应用程序,并在提升时存在依赖问题
- 强制 cmake 选择依赖位置(如果存在多个)
- 为什么构造函数调用依赖于默认析构函数的存在?
- 尽管存在强依赖类,但设计灵活
- 对象依赖关系和处理其中一个不再存在的情况
- 基于依赖类型存在的重载
- CreateService() 调用成功,即使依赖项不存在
- GCC、Clang和IBM在如何执行依赖模板参数的名称查找方面存在分歧.哪一个是对的
- 用户界面-是否存在用于C++的跨平台GUI库(具有依赖于平台的UI和基于脚本的布局)
- 这里是否存在循环依赖