我今天通过同事遇到了这个问题。他的前端系统设计如下:
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:)
答案 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生成的,以避免拼写错误。如果你需要实现涉及凌乱声明的东西,那么宏可能有意义,但到目前为止,情况似乎并非如此。
您的同事可能感兴趣的实际问题:
所以基本上你的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)
规则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)