OOP与宏观问题

时间:2010-12-02 10:42:54

标签: c++ oop

我今天通过同事遇到了这个问题。他的前端系统设计如下:

class LWindow
{
   //Interface for common methods to Windows
};

class LListBox : public LWindow
{
   //Do not override methods in LWindow.
   //Interface for List specific stuff
}

class LComboBox : public LWindow{} //So on

Window系统应该可以在多个平台上运行。假设目前我们的目标是Windows和Linux。对于Windows,我们在LWindow中有一个接口实现。我们对所有LListBox es,LComboBox es等有多个实现。我的反应是将LWindow*(实现对象)传递给基类LWindow类,以便它可以做到这一点:

void LWindow::Move(int x, int y)
{
   p_Impl->Move(x, y);   //Impl is an LWindow*
}

并且,对LListBox的实施等做同样的事情

最初给出的解决方案有很大不同。它归结为:

#define WindowsCommonImpl {//Set of overrides for LWindow methods}

class WinListBox : public LListBox
{
    WindowsCommonImpl     //The overrides for methods in LWindow will get pasted here.
    //LListBox overrides
}

//So on

现在,我已经阅读了所有关于宏的邪恶和良好的设计实践,我立即反对这个计划。毕竟,这是伪装的代码重复。但我无法使我的同事相信这一点。我很惊讶,情况确实如此。所以,我向你提出这个问题。后一种方法可能存在哪些问题?我想要实际的答案。我需要说服一个非常实用的人(并且习惯于做这种事情。他提到MFC中有很多宏!)这很糟糕(和我自己)。不教他美学。此外,我提出的建议有什么问题吗?如果是这样,我该如何改进呢?感谢。

编辑:请给我一些理由让我对自己支持oop感觉良好:(

寻求赏金。请询问您是否需要任何澄清。我想知道针对宏的参数和vs OOP:)

7 个答案:

答案 0 :(得分:2)

不能直接回答你的问题,但是无法告诉你阅读GOF中的Bridge Design模式。这完全是为了那个。

  

将抽象与其抽象联系起来   实施使两者可以   独立变化。

据我所知,除了MACRO之外,你已经走在了正确的道路上。

  

我的反应是通过   LWindow *(实现对象)来了   基础LWindow类,所以它可以这样做:

答案 1 :(得分:2)

LListBox和LComboBox应该接收WindowsCommonImpl的实例。

在第一个解决方案中,使用继承,以便LListBox和LComboBox可以使用一些常用方法。但是,继承并不意味着这一点。

答案 2 :(得分:2)

您的同事可能正在考虑使用MFC消息映射宏;这些用于每个MFC派生类中重要的地方,所以我可以看到你的同事来自哪里。但是,这些不是用于实现接口,而是用于与其他Windows操作系统交互的详细信息。

具体地说,这些宏实现了Windows消息泵系统的一部分,其中表示对MFC类执行操作的请求的“消息”被定向到正确的处理程序函数(例如,将消息映射到处理程序)。如果您可以访问visual studio,您将看到这些宏将消息映射条目包装在一个稍微复杂的结构数组中(调用操作系统代码知道如何阅读),并提供访问此映射的函数。

作为MFC用户,宏系统让我们看起来很干净。但这主要是因为基础Windows API是明确指定的并且不会有太大变化,并且大多数宏代码都是由IDE生成的,以避免拼写错误。如果你需要实现涉及凌乱声明的东西,那么宏可能有意义,但到目前为止,情况似乎并非如此。

您的同事可能感兴趣的实际问题:

  • 重复的宏调用。看起来你需要将“WindowsCommonImpl”行复制到每个类声明中 - 假设宏扩展为一些内联函数。如果它们只是声明而实现是在一个单独的宏中,那么你也需要在每个.cpp文件中执行此操作 - 并且每次都更改传递给宏的类名。
  • 更长的重新编译时间。对于您的解决方案,如果您在LWindow实现中更改了某些内容,则可能只需要重新编译LWindow.cpp。如果您在宏中更改某些内容,则需要重新编译包含宏头文件的所有内容,这可能是您的整个项目。
  • 更难调试。如果错误与宏中的逻辑有关,则调试器可能会中断调用者,您不会立即看到错误。您甚至可能不会考虑检查宏定义,因为您认为您确切知道它的作用。

所以基本上你的LWindow解决方案是一个更好的解决方案,可以最大限度地减少麻烦。

答案 3 :(得分:1)

我同意你的看法。使用WindowsCommonImpl宏的解决方案非常糟糕。它容易出错,难以扩展且很难调试。 MFC就是设计Windows库的一个很好的例子。如果它看起来像MFC,那你的确是错误的。

因此,您的解决方案明显优于基于宏的解决方案。无论如何,我不同意这是好的。我最重要的缺点是你混合了接口和实现。分离接口和实现的最实际价值是能够轻松编写模拟对象以进行测试。

无论如何,您试图解决的问题似乎是如何在C ++中将接口继承与实现继承相结合。我建议使用模板类来实现窗口。

// Window interface
class LWindow
{
};

// ListBox interface (inherits Window interface)
class LListBox : public LWindow
{
};    

// Window implementation template
template<class Interface>
class WindowImpl : public Interface
{
};

// Window implementation
typedef WindowImpl<LWindow> Window;

// ListBox implementation
// (inherits both Window implementation and Window interface)
class ListBox : public WindowImpl<LListBox>
{
};

我记得WTL Windows库是基于组合接口和实现的类似模式。我希望它有所帮助。

答案 4 :(得分:0)

哦,这让人很困惑。

好的,所以L ***是接口的层次结构,没关系。现在您使用p_Impl的是什么,如果您有一个接口,为什么要在其中包含实现?

宏观的东西当然是丑陋的,而且通常是不可能的。重点是你将有不同的实现,如果你没有,那么为什么要首先创建几个类?

答案 5 :(得分:0)

OP似乎很困惑。在这里'做什么,它非常复杂但是有效。

规则1:设计抽象。如果您有“is-A”关系,则必须使用公共虚拟继承。

struct Window { .. };
struct ListBox : virtual Window { .. };

规则2:实现,如果要实现抽象,则必须使用虚拟继承。您可以自由使用继承来保存重复。

class WindowImpl : virtual Window { .. };
class BasicListBoxImpl : virtual ListBox, public WindowImpl { .. };
class FancyListBoxImpl : public BasicListBoxImpl { };

因此,您应该将“virtual”改为“isa”,其他继承只是在重写方法上保存。

Rule3:尝试确保在具体类型中只有一个有用的函数:构造函数。这有时很难,你可能需要一些默认设置和一些设置方法来解决问题。设置对象后抛弃实现。理想情况下,你会在施工时这样做:

ListBox *p = new FancyListBoxImpl (.....);

注意:您不会直接在实现上或实现中调用任何抽象方法,因此抽象基础的私有继承就可以了。您的任务是专门定义这些方法,而不是使用它们:仅适用于抽象的客户端。出于同样的原因,基础上的虚拟方法的实现也可能是私有的。重用的继承可能是公共的,因为您可能希望在构造之后在派生类中使用这些方法,或者在构造之后配置对象,然后再删除实现细节。

规则4:许多抽象都有一个标准的实现,称为委托,这是你所说的:

struct Abstract { virtual void method()=0; };

struct AbstractImpl_Delegate: virtual Abstract { 
  Abstract *p;
  AbstractImpl_Delegate (Abstract *q) : p(q) {}
  void method () { p->method(); }
};

这是一个可爱的实现,因为它不需要您了解抽象或如何实现抽象......:)

答案 6 :(得分:0)

我找到了

  

使用   预处理器 #define 指令   定义常量 不精确

[src]

宏显然不那么精确,我甚至都不知道...... 预处理器的经典​​隐患如:

#define PI_PLUS_ONE (3.14 + 1)`
  

这样做可以避免这种可能性   操作顺序问题将会发生   破坏你常数的含义:

x = PI_PLUS_ONE * 5;`
  

无   括号,以上就是   转换为

x = 3.14 + 1 * 5;

[src]