如何理解DCI模式

时间:2017-07-24 10:32:22

标签: oop design-patterns dci

根据维基百科数据,上下文和交互(DCI)是计算机软件中用于编程通信对象系统的范例。在这里,我不清楚DCI试图解决的问题。你能用简单的例子解释一下吗?您的示例中的数据,上下文和交互是什么?

3 个答案:

答案 0 :(得分:5)

让我理解它的一个简单方法是使用经典的银行应用程序示例。在这个例子中,我将使用Rails。

假设我们的应用有一项功能,用户可以将资金从一个帐户转移到另一个帐户。

我们可能有一个看起来像这样的控制器:

# app/controllers/accounts_controller.rb
class AccountsController < ApplicationController
  def transfer
    @source_account = Account.find(params[:id])
    @destination_account = Account.find(params[:destination_id])
    @amount = params[:amount]

    if @source_account.transfer_to(@destination_account, @amount)
      flash[:success] = "Successfully transferred #{@amount} to #{@destination_account}"
      redirect_to @source_account
    else
      flash[:error] = "There was a problem transferring money to #{@destination_account}"
      render :transfer
    end
  end
end

在这里,我们在其中一个transfer_to对象上调用Account。此方法在Account模型中定义。

# app/models/account.rb
class Account < ActiveRecord::Base
  def transfer_to(destination_account, amount)
    destination_account.balance += amount
    self.balance -= amount
    save
  end
end

这是一个传统的MVC解决方案 - 当调用控制器上的transfer方法时,我们实例化几个模型对象并调用模型上定义的行为。就像罗伯特所说,业务逻辑是分开的,我们必须在几个不同的地方看一下才能理解代码。

这种方法的缺点是我们最终可能会在模型中定义很多行为,而这些行为并不总是需要并且缺乏上下文。如果你之前曾经在一个大型项目上工作过,不久之后模型文件会变成几百甚至几千行代码,因为所有行为都是在它们内部定义的。

DCI可以通过仅在需要使用该行为的特定上下文中提供数据模型行为来帮助解决此问题。我们将其应用于我们的银行应用程序示例。

在我们的例子中,Context正在转移资金。数据将是Account个对象。行为是转移资金的能力。互动是将资金从一个账户转移到另一个账户的实际行动。它可能看起来像这样:

# app/contexts/transferring_money.rb
class TransferringMoney # this is our Context
  def initialize(source_account, destination_account) # these objects are our Data
    @source_account = source_account
    @destination_account = destination_account

    assign_roles(source_account)
  end

  def transfer(amount) # here is the Interaction
    @source_account.transfer_to(@destination_account, amount)
  end

  private

  def assign_roles(source_account)
    source_account.extend Transferrer
  end

  module Transferrer
    def transfer_to(destination_account, amount)
      destination_account.balance += amount
      self.balance -= amount
      save
    end
  end
end

从示例中可以看出,当我们调用source_account.extend Transferrer时,数据在运行时在Context中被赋予其行为。 transfer方法是发生交互的地方。这可以防止我们将逻辑分成单独的文件,并且它们都包含在一个Context类中。

我们会从控制器这样调用它:

# app/controllers/accounts_controller.rb
class AccountsController < ApplicationController
  def transfer
    @source_account = Account.find(params[:id])
    @destination_account = Account.find(params[:destination_id])
    @amount = params[:amount]

    if TransferringMoney.new(@source_account, @destination_account).transfer(@amount)
      flash[:success] = "Successfully transferred #{@amount} to #{@destination_account}"
      redirect_to @source_account
    else
      flash[:error] = "There was a problem transferring money to #{@destination_account}"
      render :transfer
    end
  end
end

通过这样一个简单的例子,这可能看起来比它的价值更麻烦,但是当应用程序变得非常大并且我们为模型添加越来越多的行为时,DCI变得更有用,因为我们只在我们的模型中添加行为某些特定互动的背景。这样,模型行为就是上下文的,我们的控制器和模型要小得多。

答案 1 :(得分:2)

DCI架构的关键方面是:

  • 分离系统(数据)与其功能(功能)。数据和函数具有不同的变化率,因此它们应该分开,而不是像现在一样,将它们放在一起。
  • 创建从用户的心智模型到代码的直接映射。计算机应该视为用户,而不是相反,代码应该反映出来。
  • 使系统行为成为第一类实体。
  • 良好的代码可读性,在运行时没有任何意外。

我突出显示了用户的心理模型,因为这就是它的真正含义。系统架构应基于用户思考过程,而不是工程师。

当然,心理模型应该由与项目相关的每个人讨论和制作,但这种情况很少见。通常,工程师将根据模式,分解,继承,多态性进行编码,并且对用户有意义的代码部分在结构层后面进行模糊处理。

这就是DCI试图解决的问题。多年来,它遇到了一些阻力,在我看来,因为工程师喜欢他们的结构和课程,所以他们主要关注那个。

一个说明性的代码示例太长了,无法在此发布,但无论如何,心态更重要。它是关于动态协同工作的对象,以解决特定问题。我在这里做了一个更大的教程,有一些代码:https://github.com/ciscoheat/haxedci-example

此外,我强烈推荐视频A glimpse of Trygve作为进一步解释,由DCI的一位作者James Coplien撰写。

答案 2 :(得分:0)

如果你从原作者那里读到this paper,特别是第34章;我们哪里出错?&#34;,作者给出了他们觉得需要新方法的原因。

简而言之:作者抱怨说,正确的面向对象方法会导致&#34;分裂&#34;业务逻辑。这是事实,因为这是我们分解问题的主要原因,因此我们不必一次解决整个逻辑。

作者认为(在上面的同一章中),前面的程序方法更好(给出FORTRAN代码的例子),因为人们可以顺序读取代码并决定它是否做了应该做的事情。

他们还争论(在下一章:回到用户和头脑中),开发人员更容易思考&#34;数据&#34;首先,以及稍后的程序(例如交互)。

作者基本上主张至少部分回归到程序编程,明确区分数据和逻辑,而不是面向对象的数据和逻辑&#34;在一起。

我个人认为,将这种方法称为面向对象略有误导,因为它是对它的强烈批评,明显有偏离它的意图。但是,请不要接受我的话,阅读文章。