2010-07-26 68 views
0

我有一组类描述了一组逻辑框,它们可以容纳东西并对它们执行操作。我有测试C++类的功能

struct IBox // all boxes do these 
{ 
    .... 
} 

struct IBoxCanDoX // the power to do X 
{ 
    void x(); 
} 

struct IBoxCanDoY // the power to do Y 
{ 
    void y(); 
} 

我不知道什么是“最好的”或可能对这些类的客户端它只是“喜欢”的成语来处理这些可选功能

一)

if(typeid(box) == typeid(IBoxCanDoX)) 
    { 
     IBoxCanDoX *ix = static_cast<IBoxCanDoX*>(box); 
     ix->x(); 
    } 

b)

IBoxCanDoX *ix = dynamic_cast<IBoxCanDoX*>(box); 
    if(ix) 
    { 
     ix->x(); 
    } 

C)

if(box->canDoX()) 
{ 
    IBoxCanDoX *ix = static_cast<IBoxCanDoX*>(box); 
    ix->x(); 
} 

d)不同类结构现在

struct IBox 
{ 
    void x(); 
    void y(); 
} 
... 
box->x(); /// ignored by implementations that dont do x 

E)相同,除了

box->x() // 'not implemented' exception thrown 

六)明确的测试功能

if(box->canDoX()) 
{ 
    box->x(); 
} 

我肯定还有其他的了。

编辑:

只是为了让使用更清晰的情况下

我揭露这些东西通过交互式用户界面给最终用户。他们可以输入'make box do X'。我需要知道盒子是否可以做x。或者,我需要禁用“使当前框做X”命令

EDIT2:THX所有回答者

诺亚·罗伯茨指出,(一)不工作(解释了我的一些问题!)。 我落得这样做(b)和轻微变异

template<class T> 
    T* GetCurrentBox() 
    { 
     if (!current_box) 
      throw "current box not set"; 
     T* ret = dynamic_cast<T*>(current_box); 
     if(!ret) 
      throw "current box doesnt support requested operation"; 
     return ret; 
    } 
    ... 
    IBoxCanDoX *ix = GetCurrentBox<IBoxCanDoX>(); 
    ix->x(); 

,让UI管道处理很好地与例外(我不是真的抛出赤裸裸的字符串)。

我还打算探索游客

+1

你正在处理指针......好像你忘了把'*'ehm ...无处不在?! – smerlin 2010-07-26 16:36:08

+0

您是否需要在运行时选择不同的*框?也就是说,你需要运行时多态还是一切都可以在编译时解决?这是你必须回答的第一个问题,因为这会引导你进入动态或静态多态类型的解决方案(即继承和虚拟方法与模板) – 2010-07-26 17:13:47

回答

1

如果您使用的是“我”前缀表示“接口”,因为它会在Java中,这将在C++抽象的基地来完成的意思,那么你的第一个选项将无法正常工作....所以,一个人的出。尽管我已经使用过它。

不要做'd',它会污染你的层次结构。保持界面清洁,你会很高兴你做到了。因此,车辆类没有踏板()功能,因为只有一些车辆可以踏板。如果客户端需要使用pedal()函数,那么它确实需要知道可以使用哪些类。

保持扫清道路“E”出于同样的原因为“d” PLUS,它违反了里氏替换原则的。如果客户端在调用之前需要检查一个类是否对pedal()进行响应,以便它不会爆炸,那么最好的方法是尝试将其转换为具有该函数的对象。 'f'与支票一样。

'c'是多余的。如果您的层次结构设置为应该如此,那么投射到ICanDoX就足以检查x是否可以执行X()。

因此,'b'成为您给出选项的答案。然而,正如Gladfelter所表明的那样,在你的文章中还有一些你没有考虑过的选项。

编辑说明:我没有注意到'c'使用了static_cast而不是动态的。正如我在回答中提到的那样,dynamic_cast版本更清晰,应该是首选的,除非特殊情况另有规定。它与以下选项相似,因为它会污染基础接口。

编辑2:我要指出,对于“A”,我一直用它,但我不使用静态类型像你这样在您的文章。任何时候我使用typeid来分割基于类型的流,它总是基于在运行时注册的东西。例如,打开正确的对话框以编辑某个未知类型的对象:对话管理器根据其编辑的类型向工厂注册。这使我无需在添加/删除/更改对象时更改任何流控制代码。我通常不会在不同的情况下使用这个选项。

+0

为什么不(a)工作? – pm100 2010-07-26 18:14:23

+1

@pm - 因为如果变量是接口的子类,那么你不会得到那个typeid。 – 2010-07-26 19:09:04

+0

啊 - 我没有意识到这一点。我认为如果对象可以被转换成这种类型,那将是真的。我再一次期待C++能像c#或jave一样 – pm100 2010-07-26 21:52:32

4

我建议在C双调度问题,像这样++ Visitor模式:

class IVisitor 
{ 
public: 
    virtual void Visit(IBoxCanDoX *pBox) = 0; 
    virtual void Visit(IBoxCanDoY *pBox) = 0; 
    virtual void Visit(IBox* pBox) = 0; 
}; 

class IBox // all boxes do these 
{ 
public: 
    virtual void Accept(IVisitor *pVisitor) 
    { 
     pVisitor->Visit(this); 
    } 
}; 

class BoxCanDoY : public IBox 
{ 
public: 
    virtual void Accept(IVisitor *pVisitor) 
    { 
     pVisitor->Visit(this); 
    } 
}; 
class TestVisitor : public IVisitor 
{ 
public: 
    // override visit methods to do tests for each type. 
}; 

void Main() 
{ 
    BoxCanDoY y; 
    TestVisitor v; 
    y.Accept(&v); 
} 
+0

是的,但不要忘记研究许多类型的实现的访客模式,以便您使用适合您需求的内容。除了这个答案中的一个(从GoF直接出来)之外,还有非循环,合作和层次......可能更多。每个人都有自己的长处和短处,所以在开始使用错误之前你最好仔细考虑。 – 2010-07-26 17:06:21

+0

@Noah,好点。理解可用的选项当然不会伤害。但是,我所看到的变化是为了更好地分离访问者和受访者之间的编译时间依赖关系。测试代码几乎总是与测试代码直接绑定并编译,所以香草访问者模式可能就足够了。 – 2010-07-26 17:30:28

+0

我不明白测试代码的相关性。测试代码通常不会使用访问,因为您自己测试每个实现。 – 2010-07-26 18:03:59

2

你给的选项中,我会说是b或者d是“最好的”。然而,需要做很多这类事情往往是一个糟糕的设计,或者是一个可以更好地在动态类型语言而不是C++中实现的设计。

+1

动态打字并不能真正解决任何问题。你仍然必须做你的支票,并且经常也必须做你的演员。 dynamic_cast选项提供了所需的动态类型的所有功能。 – 2010-07-26 17:20:44

0

如果您试图从代码的或然部分调用这些类中的任何一个,我建议您将该代码包装在模板函数中,并以相同的方式命名每个类的方法来实现duck typing,因此您的客户端代码看起来像这样。

template<class box> 
void box_do_xory(box BOX){ 
    BOX.xory(); 
} 
0

对您的问题没有一般的答案。一切都依赖。我只能说:
- 不要用a),用b)代替
- b)很好,需要最少的代码,不需要虚拟方法,但dynamic_cast有点慢
- c)is类似于b),但它更快(没有dynamic_cast),并需要更多的内存
- e)没有意义,你仍然需要发现,如果你可以调用该方法,所以不会抛出异常
- d) f)(编写较少的代码)
-d)e)和f)产生更多垃圾代码,然后生成更多垃圾代码,但速度更快,内存消耗更少

1

A和B需要运行时类型识别(RTTI),如果您正在进行大量检查,则可能会变慢。就我个人而言,我不喜欢“canDoX”方法的解决方案,如果出现这种情况,设计可能需要升级,因为您暴露的信息与课程无关。

如果你只需要执行X或Y,取决于类,我会去在IBOX一个虚拟的方法,获得在子类中重写。

class IBox{ 
    virtual void doThing(); 
} 
class IBoxCanDoX: public IBox{ 
    void doThing() { doX(); } 
    void doX(); 
} 
class IBoxCanDoY: public IBox{ 
    void doThing() { doY(); } 
    void doY(); 
} 

box->doThing(); 

如果该解决方案不适用或者您需要更复杂的逻辑,请查看Visitor设计模式。但请记住,当您定期添加新类时,或者方法更改/添加/删除(但您的建议替代方案也适用)时,访问者模式不是非常灵活。

+0

更正:当您添加新课程或更改行为时,某些*类型的访问者是僵化的。非循环访问者实际上很擅长处理这类事情。 – 2010-07-26 17:26:18

+0

toefel,可悲的是一个盒子可以做x和y – pm100 2010-07-26 18:12:55

0

我假设你不仅在这里使用一种类型的一个对象。

我会列出您正在使用的数据,并尝试了解如何将其放置在内存中以执行数据驱动编程。内存中一个好的布局应该反映出你将数据存储在你的类中的方式,以及这些类是如何放在内存中的。一旦你有了基本的设计结构(应该不会超过餐巾纸),我将开始根据你打算对数据进行的操作将对象组织成列表。如果您计划在子集X中的对象集合{Y}上执行X(),我可能会确保从头开始创建一个Y静态数组。如果你想偶尔访问整个X,可以通过将列表收集到一个动态指针列表(使用std :: vector或您最喜欢的选项)来安排。

我希望这是有道理的,但一旦实施,它提供了易于理解且易于使用的简单直接解决方案。

0

有一种通用的方法来测试一个类是否支持某个概念,然后执行最合适的代码。它使用SFINAE黑客。这个例子受到Abrahams和Gurtovoy的“C++模板元编程”书的启发。功能doIt将使用x方法如果它存在,否则将使用y方法。您也可以扩展CanDo结构来测试其他方法。您可以根据需要测试任意多种方法,只要可以唯一解决超载问题。

#include <iostream> 
#include <boost/config.hpp> 
#include <boost/utility/enable_if.hpp> 

typedef char yes;  // sizeof(yes) == 1 
typedef char (&no)[2]; // sizeof(no) == 2 

template<typename T> 
struct CanDo { 
    template<typename U, void (U::*)()> 
    struct ptr_to_mem {}; 

    template<typename U> 
    static yes testX(ptr_to_mem<U, &U::x>*); 

    template<typename U> 
    static no testX(...); 

    BOOST_STATIC_CONSTANT(bool, value = sizeof(testX<T>(0)) == sizeof(yes)); 
}; 

struct DoX { 
    void x() { std::cout << "doing x...\n"; } 
}; 

struct DoAnotherX { 
    void x() { std::cout << "doing another x...\n"; } 
}; 

struct DoY { 
    void y() { std::cout << "doing y...\n"; } 
}; 

struct DoAnotherY { 
    void y() { std::cout << "doing another y...\n"; } 
}; 

template <typename Action> 
typename boost::enable_if<CanDo<Action> >::type 
doIt(Action* a) { 
    a->x(); 
} 

template <typename Action> 
typename boost::disable_if<CanDo<Action> >::type 
doIt(Action* a) { 
    a->y(); 
} 

int main() { 

    DoX   doX; 
    DoAnotherX doAnotherX; 
    DoY   doY; 
    DoAnotherY doAnotherY; 

    doIt(&doX); 
    doIt(&doAnotherX); 
    doIt(&doY); 
    doIt(&doAnotherY); 
}