有时在修复现有代码库中的缺陷时,我可能(通常是出于懒惰)决定更改方法:
void
MyClass::foo(uint32_t aBar)
{
// Do something with aBar...
}
为:
void
MyClass::foo(uint32_t aBar, bool aSomeCondition)
{
if (aSomeCondition)
{
// Do something with aBar...
}
}
在代码审查期间,一位同事提到更好的方法是使用子类MyClass
来提供这种专门的功能。
但是,我认为,只要aSomeCondition
没有违反MyClass
的目的或凝聚力,它就是一种可以使用的模式。只有当代码被标志和if
语句渗透时,继承才是更好的选择,否则我们可能会进入宇航员领域。
这里的临界点是什么?
注意:我刚刚看到this related answer,这表明enum
可能更好
选择而不是bool
,但我认为我的问题仍然适用于这种情况。
答案 0 :(得分:26)
这种问题不仅有一种解决方案。
布尔的语义非常低。如果您想在将来添加新条件,则必须添加新参数...
经过四年的维护,你的方法可能有六个参数,如果这些参数都是布尔值,那么它对于维护者来说是非常好的陷阱。
Enum 是一个不错的选择。 枚举可以轻松迁移到位掩码或上下文对象。
位掩码:C ++包含C语言,可以使用一些简单的旧实践。有时无符号int上的一个掩码是一个不错的选择(但你放松了类型检查),你可以错误地传递一个错误的掩码。这是从布尔或枚举参数平滑移动到这种模式的便捷方式。
可以通过一些努力将位掩码迁移到上下文对象。如果必须保持构建时兼容性,则可能必须实现某种按位算术,例如operator |
和operator &
。
继承有时是一个不错的选择。请注意,您还必须使用多态,如果大量使用此方法,这可能会降低方法的速度 最后,继承会引起所有工厂代码的变化......如果你有多种方法可以以独家方式改变,你会怎么做?你会弄乱你的特定类的代码...... 事实上,我认为这通常不是一个好主意。
方法拆分:另一种解决方案是将方法拆分为多个私有方法并提供两个或更多公共方法。
上下文对象:可以通过添加上下文参数来绕过C ++和C缺少命名参数。我经常使用这种模式,特别是当我必须在复杂框架的层次上传递许多数据时。
class Context{
public:
// usually not a good idea to add public data member but to my opinion this is an exception
bool setup:1;
bool foo:1;
bool bar:1;
...
Context() : setup(0), foo(0), bar(0) ... {}
};
...
Context ctx;
ctx.setup = true; ...
MyObj.foo(ctx);
注意: 这对于最小化访问(或使用)静态数据或查询单例对象,TLS也很有用...... Context对象可以包含更多与算法相关的缓存数据。 ... 我让你的想象力自由......
反模式
我在这里添加了几个反模式(以防止签名的某些变化): * 永远不要这样做
答案 1 :(得分:19)
不幸的是,我不认为问题有一个明确的答案(这是我在自己的代码中经常遇到的问题)。使用布尔值:
foo( x, true );
电话很难理解。
使用枚举:
foo( x, UseHigherAccuracy );
这很容易理解,但你最终会得到这样的代码:
foo( x, something == someval ? UseHigherAccuracy : UseLowerAccuracy );
这几乎不是一种改进。并具有多种功能:
if ( something == someval ) {
AccurateFoo( x );
}
else {
InaccurateFoo( x );
}
你最终得到了更多的代码。但我想这是最容易阅读的,也是我倾向于使用的,但我仍然不喜欢它: - (
但是,我绝对不会做的一件事是子类。继承应该是您可以使用的 last 工具。答案 2 :(得分:9)
主要问题是标志是否会影响类或函数的行为。函数本地更改应该是参数,而不是子类。运行时继承应该是最后达到的工具之一。
答案 3 :(得分:4)
我使用的一般准则是:如果aSomeCondition
以主要方式改变了函数的性质,那么我考虑子类化。
与添加仅具有轻微影响的标志相比,子类化是一项相对较大的工作。
一些例子:
当然,最后一个可以通过完全隐藏基础数据结构来更好地处理,但我假设您希望能够选择其中一个,原因如性能
答案 4 :(得分:0)
恕我直言,aSomeCondition
标志更改或取决于当前实例的状态,因此,在某些条件下,此类应更改其状态并以不同方式处理上述操作。在这种情况下,我可以建议使用State Pattern。希望它有所帮助。
答案 5 :(得分:-1)
我只是更改代码:
void MyClass::foo(uint32_t aBar, bool aSomeCondition)
{
if (aSomeCondition)
{
// Do something with aBar...
}
}
收件人:
void MyClass::foo(uint32_t aBar)
{
if (this->aSomeCondition)
{
// Do something with aBar...
}
}
我总是忽略bool作为函数参数,并且喜欢放入struct中,即使我必须调用
myClass->enableCondition();