DCI架构

以前的架构

  • 面向对象编程先驱的目标是在代码中捕获最终用户的心理模型
  • 用户在界面上所做的任何工作都会操作代码中的对象,如果程序提供有关用户操作如何影响程序状态的实时反馈,
    则可以减少用户错误和意外。一个好的GUI服务应该提供这种可以让用户感知到实际情况的交互表示。
  • MVC的目标就是提供一种从最终用户大脑到计算机“大脑”直接连接的错觉
  • MVC是为了将信息的表示与用户的交互分开。
  • 信息是最终用户心智模型的关键要素。
  • 设计良好的程序应该要捕获数据模型中的信息模型,如果做到这一点,用户就会觉得计算机内存是他内存的延伸。
  • 也就是说,最终用户实际上实在操作记忆中他们头脑中想象的对象 broadcasting
  • 这种直接操纵错觉是关于计算机是什么以及它们如何为人们服务的客观视角的核心。
  • 视图在屏幕上显示模型。不同的视图可能以完全不同的方式支持相同的数据,即相同的模型。
  • 控制器创建视图,并协调视图和模型。它通常作为按键、定位设备、其它事件接收,输入用户手势的角色。 broadcasting

遇到的问题

  • 面向对象的设计过程,在多个对象进行交互时,这个交互过程没有被捕获到。
  • 这些协作与交互也具有结构,构成了最终用户心智模型的一部分,但是代码中没有内聚这些交互。

思考数据

  • 数据(Data)代表用户对他们世界中事物的心理模型(对象)
  • 以前的经验法则:名词是对象,动词是方法。
  • 假设一个 savingAccount对象,从这个账户中取款,就是这个账户减少余额的动作
  • 其中减少余额容易理解为这个savingAccount的行为,但是取款这个数据目的就被丢失了
  • 事实上取款是整个系统的行为,并不同与减少余额这个savingAccount对象的行为
  • 以前面向对象的解决方法就是通过继承,即 取款继承和扩展了savingAccount类,保持了基类的稳定性,实现了
    开闭原则:对扩展开放,对修改关闭。
  • 但是过度的继承,导致难以追溯业务行为;以致对程序员造成沉重负担。

思考角色

  • 这里说的角色(role),一种新的行为概念,它也存在于用户的脑海中
  • 以转账为例,程序员识别的算法:
    源账户开始交易
    源账户验证可用资金(请注意,这必须在交易内部完成,以避免干预提款!)
    源账户减少自身余额
    源帐户请求目标帐户增加其余额
    源账户更新其日志以指出这是一次转账(而不是,例如,简单的提款)
    源帐户请求目标帐户更新其日志
    源账户结束交易
    源账户通知账户持有人转账成功
    

    代码实现的行为如下:

    template <class ConcreteAccountType>
    class TransferMoneySourceAccount: public MoneySource
    {
    private:
     ConcreteDerived *const self() {
        return static_cast<ConcreteDerived*>(this);
     }
     void transferTo(Currency amount) {
        // This code is reviewable and
        // meaningfully testable with stubs!
        beginTransaction();
        if (self()->availableBalance() < amount) {
          endTransaction();
          throw InsufficientFunds();
        } else {
          self()->decreaseBalance(amount);
          recipient()->increaseBalance (amount);
          self()->updateLog("Transfer Out", DateTime(), amount);
          recipient()->updateLog("Transfer In", DateTime(), amount);
        }
        gui->displayScreen(SUCCESS_DEPOSIT_SCREEN);
        endTransaction();
      }
    }
    
  • 这种实现方式比把行为分布在多个类中更容易理解。更像用户心中想的那样。
  • 本质上讲,角色(role)体现了通用的抽象算法
  • 用户识别单个对象及其域的存在,但每个对象还必须实现来自用户交互模型的行为。这些行为通过它在给定用例中扮演的角色将其与其它对象联系在一起。
    • 例如:银行及其账户的系统是什么数据模型,以及在账户间进行资金转义算法模型中系统是做什么的。
  • 下图中 broadcasting
  • 右侧,我们将最终用户角色抽象捕获为接口
  • 顶部,我们发现从右侧角色抽象的克隆开始的角色,但其填充了方法。
  • 左侧,类
  • 角色和类都存在于最终用户的脑海中,两者在运行时融合为一个对象。
  • 与场景用例相关的更多动态操作来自对象所扮演的角色
  • 从用例场景中截取的操作集合称为角色。
  • 希望如下图:将角色的逻辑注入到对象中,以便他们与对象在实例化时从其类接收的方法一样多地成为对象的一部分。 broadcasting
  • 如果语言支持,可以更聪明些在运行时向每个对象中注入足够多的逻辑

协调工作的角色: 上下文和交互

  • 以转账为例:为了实施转账用例,就需要储蓄账户扮演源账户的角色,投资账户扮演目标账户的角色。
  • 每个角色“方法”都将在它被粘到的对象的上下文中执行
  • 可以使用例如:委托、注入等技巧为了对象提供必要的智能以扮演他们必须扮演的角色 broadcasting
    • 在MVC中,controller将model对象放在用户操作用例中执行,这些对象此时都是在内存中
    • 以转账为例:最终用户考虑一个过程或算法来根据所涉及的角色进行汇款。 我们需要挑选出可以运行该算法的代码,然后我们要做得就是将正确的对象与正确的角色对齐并让代码运行。
    • 如上图所示,算法和角色到对象的映射都属于一个Context目的
    • 典型实现中,每个用例都有一个上下文对象,每个上下文都包含一个标识符,用于该用例中所涉及的每个角色 Context所要做得就是将角色标识符绑定到正确的对象,然后我们只需要启动上下文的“入口”–角色的触发器 方法,代码就可以运行了。
    • Context可以视为一个表,它将角色成员函数映射到一个对象方法

DCI特性

  • 使用role来捕获参与用例需求的主要用户概念,角色是最终用户认知模型的一流自己,我们希望在代码中反映它们
  • 使用对象来捕获来自经验和隐性知识的深层领域概念,作为勉强智能的数据
  • 软件展现开闭原则而仅基于继承的开闭原则导致信息隐藏不佳,DCI风格保持了领域类和角色的完整性
  • 类对修改是封闭的,通过角色的注入对扩展开放
  • DCI非常适合敏捷软件开发,它运行程
  • 序员直接与最终用户的心智模型连接

参考文档

  1. DCI 架构:面向对象编程的新愿景


blog comments powered by Disqus
—  原创作品许可 — 署名-非商业性使用-禁止演绎 3.0 未本地化版本 — CC BY-NC-ND 3.0   —