WTL 的文档-视图基础。第一部分






3.50/5 (2投票s)
2000年3月10日

86310

1556
一个提供最简单方式来实现松耦合组件的库。
引言
我不知道你怎么样,但当我了解到 WTL 时,我简直兴奋极了。我并不是要建议人们放弃 MFC,每个人在选择工具时都有自己的考虑。真正重要的是,不必再依赖它变得更容易了。正如你们中的一些人可能已经知道的,WTL 中缺失的一项功能是文档-视图体系结构,也称为 DVA。现在,相当多的人批评 DVA。虽然我觉得它在 MFC 中并不特别灵活,甚至不够通用,但也不应因此削弱将视觉表示与其余逻辑分离的想法。所以我想我应该尝试一下。当然,我的方法会与众不同。下面是具体做法:
首先,也是最重要的,我们将从比分离 UI 与应用程序更通用的东西开始。然后,我们将在此基础上构建这种分离。
这个想法并不新,它以不同的名称存在已有一段时间了——发布-订阅、事件源和事件监听器、连接点、委托……所有这些都是同一编程范式的形式:对象 A 做某事,对象 B 对此做出反应。你可以说它们之间存在联系。这就引出了一个名字——可连接对象。好吧,这个名字也不是我发明的。这个名字以及模型基本上是从 COM 借鉴过来的。既然如此,我猜想我不会用新发明的术语来混淆人们,而是会坚持使用熟悉的术语。所以我们有连接点、接收器对象,以及所有的相关概念。这里有一个关于该概念更详细描述的链接。
我们将从解释设计该库时的优先事项开始。
- 强类型
这意味着不再有eventCode
、notificationCode
、LPARAM
等等,只有直接调用。 - 对现有设计的最小影响
尝试避免“为了继承香蕉而得到整个大猩猩”的做法。 - 易用性和易懂性
这里的目标是使其如此无缝,以至于它几乎成为了语言的一部分。(我确实希望它是这样,因为这是我最喜欢的技术之一。) - 要高效。如果一个对象需要通知某人有关其事件的事宜,我们不希望这会显着减慢其速度。
- 这更像是 ATL 风格的代码,利用了 RTTI 和多重继承等特性,而这些特性的缺失或避免,在我看来,在当时限制了 MFC 的设计。这并不重要,但我还是想提一下。
我们没有线程封送处理,所以如果你的接收器对象正在观察运行在不同线程中的对象,你需要自己处理。
抱歉,这是我们为强类型和效率付出的代价。不过,你可以在需要时实现自己的封送处理,这样就不会给大多数根本不使用多线程的应用程序增加负担。
我也计划研究一种通用的解决方案。
示例代码
讲座有点枯燥了,让我们直接深入研究并检查一些代码。我们如何连接两个对象?
我们需要三个类
- 主题(触发事件的对象,事件源)
- 观察者(或接收器对象,接收第一个对象触发的事件通知的对象)
- 接收器接口(观察者必须实现的接口,以便主题能够与之通信)
它看起来会是这样的
class CSinkInterface { virtual void onEvent1(int p1, LPCTSTR p2) = 0; .... // other events }; class CObserver : public CSinkInterface { COSinkOwner m_sinkOwner; virtual void onEvent1(int p1, LPCTSTR p2) { ... // react somehow } }; class CSubject : public COConnectionPointContainer { ... CO_BEGIN_CONNECTIONPOINTS(CSubject) CO_CONNECTIONPOINT(CSinkInterface) CO_END_CONNECTIONPOINTS() void method1() { ... CO_LOOP(CSinkInterface) eventSink->onEvent1(p1, p2); CO_ENDLOOP } };
最后,我们如何连接它们?
CSubject subject;
CObserver observer;
COConnectionPointImpl<CSinkInterface> *cp;
if(subject.coFindConnectionPoint(&cp))
cp->Advise(&observer, &observer.m_sinkOwner);
我特意使用了不相关的名称(CSubject
和 CObserver
)来清楚地区分应用程序和库。我还决定在类名中使用前缀“CO”而不是“C”来避免与 COM 冲突,并在可覆盖的方法中使用前缀“co”来避免与你的方法冲突。
简要描述
所以这是最简单的例子。几乎所有的名称都来自 COM 连接点。唯一的新术语是COSinkOwner
。这是负责在接收器对象失效时断开连接的对象。另一方面,如果事件源死亡,连接会被隐藏在 CO_CONNECTIONPOINT
下的对象断开。我们也可以让 CObserver
从 COSinkOwner
派生,这样我们就可以去掉 m_sinkOwner。这也将使我们能够覆盖一些方法(确切地说是 coOnAdvised
和 coOnUnadvised
),并在我们连接到另一个对象时收到通知。顺便说一句,CSubject
可以覆盖 coOnConnection
和 coOnDisconnection
来做同样的事情。
分离接收器所有者和接收器对象的能力非常重要。例如,我们可以让一个表单成为其控件的接收器所有者。它还可以帮助我们使用类似于 Java 内部类的方法来解决多重继承下的名称冲突。
只记住一件事——接收器所有者应该在接收器对象之前死亡,或者你必须负责调用 Unadvise
。否则……是的,华生医生将接管对话。
我认为无需多言,一个对象可以暴露多个连接点,同样也可以连接到多个源。这与 COM 连接点的情况一样。
问答
- 那么,既然我们有这么多相似之处,为什么不直接使用 COM 呢?
- 因为并非每个类都适合成为 COM 对象,并非每个接收器接口都需要在类型库中声明。我认为可连接对象本身的想法非常有价值,因为它可以帮助使组件更加松散耦合,从而更具可重用性。
- 文档-视图怎么样?
- 使用可连接对象可以编写很多东西。
由于 UI 分离是最明显的、最广泛使用的,本文的第二部分将完全专注于此。我认为这个主题足够重要和复杂,值得单独写一篇文章。
我能猜到一件事,很可能它会与 MFC 的文档-视图不同。
- 如何联系我
- 如果您有任何问题或建议,请随时通过 ICQ 联系我,号码是 11411966(只需在授权请求中输入“co”,因为我禁用了接收不在我列表中的人的消息),或者直接给我发电子邮件。
- 更新
- 请 点击 此链接。