2011-05-24 47 views
18

有时在现有的代码基础固定到缺陷时我可能(经常出懒惰的)决定从改变的方法:将标志传入方法的替代方法?

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,但我想我的问题仍然适用于这种情况。

+2

恕我直言,不要启动。随着时间的推移将难以维持。那么最终的重构要困难得多。 – Keith 2011-05-24 07:51:57

+0

@凯斯 - 对不起,我不清楚你的意思。不要开始什么? – LeopardSkinPillBoxHat 2011-05-24 07:52:50

+4

不要开始使用像那样的条件方法。调用者可以决定的子类或单独的方法。 – Keith 2011-05-24 07:55:43

回答

24

不仅是没有为这类问题的解决方案。

布尔具有非常低的语义。如果你想在未来添加一个新的条件,你将不得不添加一个新的参数...
经过四年的维护,你的方法可能有六个参数,如果这些参数都是布尔值,那么它是非常好的陷阱为维护者。

枚举是一个不错的选择,如果情况是排他的。枚举可以很容易地迁移到位掩码或上下文对象。

位掩码:C++包含C语言,您可以使用一些普通的旧做法。有时候,unsigned 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 ... 上下文对象可以包含与算法的缓存数据的多很多。 ... 我让你的想象力自由...

反模式

我在这里补充几个反模式(阻止签名的一些变化): * 永远不要这样做*

  • * 永远不要这样做*使用参数传递的静态INT /布尔(一些人这样做,这是一个噩梦去除这种东西)。打破至少多线程...
  • * 永远不要这样做*添加数据成员参数传递给方法。
+3

+1特别为了永远不要这个清单 – sleske 2011-05-27 10:24:12

+0

这个问题有很多很好的答案,但是这个问题对我来说最合适。 – LeopardSkinPillBoxHat 2011-05-30 03:19:25

18

不幸的是,我不认为这个问题有明确的答案(这是我在自己的代码中经常遇到的问题)。使用布尔值:

foo(x, true); 

该调用很难理解。

随着枚举:

foo(x, UseHigherAccuracy); 

很容易理解,但你会用代码来结束这样的:

foo(x, something == someval ? UseHigherAccuracy : UseLowerAccuracy); 

这几乎是一种改进。并具有多种功能:

if (something == someval) { 
     AccurateFoo(x); 
} 
else { 
     InaccurateFoo(x); 
} 

你最终得到更多的代码。但我想这是最容易阅读,我倾向于使用,但我仍然不完全喜欢它:-(

但我绝对不会做的一件事是子类。继承应该是在最后工具,你曾经达到

+7

“继承应该是你达到的最后一个工具。”特别是如果它是这样的一个小功能。其他选项的范围为+1。 – Xeo 2011-05-24 07:56:13

+0

C#用命名参数解决了这个问题。 – 2011-06-11 02:09:41

4

我使用的一般原则是:如果aSomeCondition改变了函数的性质在主要方式,那么我考虑子类

子类是。与添加仅具有较小效果的标志相比,努力相对较大克拉。

一些例子:

  • 如果它改变其中一个排序的集合返回给调用者的方向的标志,这在本质上(标志)的微小变化。
  • 如果这是一个一次性标志(影响当前调用的东西而不是对对象的持续改变),它可能不应该是一个子类(因为沿着该轨道走下去很可能会导致大量的数字类)。
  • 如果它是改变从数组链表或平衡树类的底层数据结构的枚举,这是一个复杂的变化(子类)。

当然,这最后一个可以更好地通过完全隐藏底层数据结构,但我在此假设您希望能够选择许多之一,原因等表现处理。

0

IMHO,aSomeCondition标志变化或取决于当前实例的状态,因此,在某些条件下这类应该改变其状态和不同的方式处理上述操作。在这种情况下,我可以建议使用状态模式。希望能帮助到你。

9

主要问题是,如果该标志影响的类的行为,或者一个功能的。函数本地更改应该是参数,而不是子类。运行时继承应该是达到的最后一个工具之一。

+0

+1,直到点。 – Xeo 2011-05-24 08:30:58