如何理解DCI模式

Mal*_*tha 5 oop design-patterns dci

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

Jar*_*der 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
Run Code Online (Sandbox Code Playgroud)

在这里,我们正在调用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
Run Code Online (Sandbox Code Playgroud)

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

这种方法的缺点是,我们可能在模型内部定义了很多行为,而这些行为并非总是需要且缺少上下文。如果您以前曾从事大型项目,则不久之后模型文件就会增长为几百甚至几千行代码,因为所有行为都在其中定义。

DCI仅在我们的数据模型需要使用某些行为的特定上下文中提供行为,才能帮助解决此问题。让我们将其应用于我们的银行应用示例。

在我们的示例中,上下文正在转移资金。数据将是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
Run Code Online (Sandbox Code Playgroud)

从示例中可以看到,当我们调用时,会在运行时在Context中为数据提供其行为source_account.extend Transferrer。该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
Run Code Online (Sandbox Code Playgroud)

通过这样一个简单的示例,这似乎比其应有的麻烦还多,但是当应用程序变得非常庞大并且我们向模型添加越来越多的行为时,DCI变得更加有用,因为我们仅在某些情况下向模型添加了行为具体的互动。这样,模型行为是上下文相关的,我们的控制器和模型要小得多。


cis*_*eat 5

DCI 架构的关键方面是:

  • 将系统是什么(数据)与其功能(功能)分开。数据和功能具有不同的变化率,因此它们应该分开,而不是像现在这样,放在一起。
  • 创建从用户心理模型到代码的直接映射。计算机应该像用户一样思考,而不是相反,代码应该反映这一点。
  • 使系统行为成为一流的实体。
  • 代码可读性出色,运行时不会出现意外。

我强调了用户的心理模型,因为这才是它的真正含义。系统架构应该基于用户的思维过程,而不是工程师。

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

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

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

另外,我强烈推荐DCI 作者之一 James Coplien 制作的视频《Trygve 一瞥》以进行进一步解释。