65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.50/5 (2投票s)

2000年3月10日

viewsIcon

86310

downloadIcon

1556

一个提供最简单方式来实现松耦合组件的库。

  • 下载演示项目 - 157 Kb
  • 下载源文件 - 7 Kb

    引言

    我不知道你怎么样,但当我了解到 WTL 时,我简直兴奋极了。我并不是要建议人们放弃 MFC,每个人在选择工具时都有自己的考虑。真正重要的是,不必再依赖它变得更容易了。

    正如你们中的一些人可能已经知道的,WTL 中缺失的一项功能是文档-视图体系结构,也称为 DVA。现在,相当多的人批评 DVA。虽然我觉得它在 MFC 中并不特别灵活,甚至不够通用,但也不应因此削弱将视觉表示与其余逻辑分离的想法。所以我想我应该尝试一下。当然,我的方法会与众不同。下面是具体做法:

    首先,也是最重要的,我们将从比分离 UI 与应用程序更通用的东西开始。然后,我们将在此基础上构建这种分离。

    这个想法并不新,它以不同的名称存在已有一段时间了——发布-订阅、事件源和事件监听器、连接点、委托……所有这些都是同一编程范式的形式:对象 A 做某事,对象 B 对此做出反应。你可以说它们之间存在联系。这就引出了一个名字——可连接对象。好吧,这个名字也不是我发明的。这个名字以及模型基本上是从 COM 借鉴过来的。既然如此,我猜想我不会用新发明的术语来混淆人们,而是会坚持使用熟悉的术语。所以我们有连接点、接收器对象,以及所有的相关概念。这里有一个关于该概念更详细描述的链接。

    我们将从解释设计该库时的优先事项开始。

    • 强类型
      这意味着不再有 eventCodenotificationCodeLPARAM 等等,只有直接调用。
    • 对现有设计的最小影响
      尝试避免“为了继承香蕉而得到整个大猩猩”的做法。
    • 易用性和易懂性
      这里的目标是使其如此无缝,以至于它几乎成为了语言的一部分。(我确实希望它是这样,因为这是我最喜欢的技术之一。)
    • 要高效。如果一个对象需要通知某人有关其事件的事宜,我们不希望这会显着减慢其速度。
    • 这更像是 ATL 风格的代码,利用了 RTTI 和多重继承等特性,而这些特性的缺失或避免,在我看来,在当时限制了 MFC 的设计。这并不重要,但我还是想提一下。

    我们没有线程封送处理,所以如果你的接收器对象正在观察运行在不同线程中的对象,你需要自己处理。

    抱歉,这是我们为强类型和效率付出的代价。不过,你可以在需要时实现自己的封送处理,这样就不会给大多数根本不使用多线程的应用程序增加负担。

    我也计划研究一种通用的解决方案。


    示例代码

    讲座有点枯燥了,让我们直接深入研究并检查一些代码。

    我们如何连接两个对象?

    我们需要三个类

    1. 主题(触发事件的对象,事件源)
    2. 观察者(或接收器对象,接收第一个对象触发的事件通知的对象)
    3. 接收器接口(观察者必须实现的接口,以便主题能够与之通信)

    它看起来会是这样的

    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);
    

    我特意使用了不相关的名称(CSubjectCObserver)来清楚地区分应用程序和库。我还决定在类名中使用前缀“CO”而不是“C”来避免与 COM 冲突,并在可覆盖的方法中使用前缀“co”来避免与你的方法冲突。


    简要描述

    所以这是最简单的例子。几乎所有的名称都来自 COM 连接点。唯一的新术语是 COSinkOwner。这是负责在接收器对象失效时断开连接的对象。另一方面,如果事件源死亡,连接会被隐藏在 CO_CONNECTIONPOINT 下的对象断开。

    我们也可以让 CObserverCOSinkOwner 派生,这样我们就可以去掉 m_sinkOwner。这也将使我们能够覆盖一些方法(确切地说是 coOnAdvisedcoOnUnadvised),并在我们连接到另一个对象时收到通知。顺便说一句,CSubject 可以覆盖 coOnConnectioncoOnDisconnection 来做同样的事情。

    分离接收器所有者和接收器对象的能力非常重要。例如,我们可以让一个表单成为其控件的接收器所有者。它还可以帮助我们使用类似于 Java 内部类的方法来解决多重继承下的名称冲突。

    只记住一件事——接收器所有者应该在接收器对象之前死亡,或者你必须负责调用 Unadvise。否则……是的,华生医生将接管对话。

    我认为无需多言,一个对象可以暴露多个连接点,同样也可以连接到多个源。这与 COM 连接点的情况一样。


    问答

    那么,既然我们有这么多相似之处,为什么不直接使用 COM 呢?
    因为并非每个类都适合成为 COM 对象,并非每个接收器接口都需要在类型库中声明。我认为可连接对象本身的想法非常有价值,因为它可以帮助使组件更加松散耦合,从而更具可重用性。

    文档-视图怎么样?
    使用可连接对象可以编写很多东西。

    由于 UI 分离是最明显的、最广泛使用的,本文的第二部分将完全专注于此。我认为这个主题足够重要和复杂,值得单独写一篇文章。

    我能猜到一件事,很可能它会与 MFC 的文档-视图不同。

    如何联系我
    如果您有任何问题或建议,请随时通过 ICQ 联系我,号码是 11411966(只需在授权请求中输入“co”,因为我禁用了接收不在我列表中的人的消息),或者直接给我发电子邮件。

    更新
    点击 此链接。

  • © . All rights reserved.