如何理解 DCI 模式
Posted
技术标签:
【中文标题】如何理解 DCI 模式【英文标题】:How to understand DCI pattern 【发布时间】:2017-12-29 21:39:40 【问题描述】:根据***的数据,上下文和交互 (DCI) 是计算机软件中用于对通信对象系统进行编程的范例。在这里,我不清楚 DCI 试图解决的问题。你能用简单的例子解释一下吗?您的示例中的数据、上下文和交互是什么?
【问题讨论】:
【参考方案1】:DCI 架构的关键方面是:
将系统是什么(数据)与它的作用(功能)区分开来。数据和函数具有不同的变化率,因此它们应该分开,而不是像现在这样,放在一起类中。 创建从用户的心理模型到代码的直接映射。计算机应该像用户一样思考,而不是相反,代码应该反映这一点。 使系统行为成为一流的实体。 出色的代码可读性,运行时毫无意外。我强调了用户的心理模型,因为这才是真正的意义所在。系统架构应该基于用户的思维过程,而不是工程师。
当然,与项目相关的每个人都应该讨论和制作心智模型,但这种情况很少见。通常工程师会根据模式、分解、继承、多态进行编码,而对用户有意义的部分代码在结构层之后被混淆了。
这就是 DCI 试图解决的问题。多年来,它遇到了一些阻力,在我看来,因为工程师喜欢他们的结构和课程,所以他们主要关注这一点。
示例代码示例太长,无法在此处发布,但无论如何心态更重要。它是关于对象动态地协同工作,以解决特定问题。我在这里做了一个更大的教程,有一些代码:https://github.com/ciscoheat/haxedci-example
另外,我强烈推荐视频A glimpse of Trygve 进行进一步解释,由 DCI 的作者之一 James Coplien 制作。
【讨论】:
【参考方案2】:对我来说,理解它的一个简单方法是使用经典的银行应用程序示例。在本例中,我将使用 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
在这里,我们在Account
对象之一上调用transfer_to
。此方法在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
方法时,我们实例化了几个模型对象并调用模型上定义的行为。正如 Robert 所说,业务逻辑是分开的,我们必须查看几个不同的地方才能理解代码。
这种方法的缺点是我们最终会在模型内部定义很多行为,而这些行为并不总是需要并且缺乏上下文。如果您之前从事过大型项目,那么模型文件很快就会增长到数百甚至数千行代码,因为所有行为都在其中定义。
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
从示例中可以看出,当我们调用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
通过这样一个简单的示例,这似乎比它的价值更麻烦,但是当应用程序变得非常大并且我们向模型添加越来越多的行为时,DCI 变得更加有用,因为我们只在模型中添加行为某些特定交互的上下文。这样一来,模型行为是上下文相关的,我们的控制器和模型要小得多。
【讨论】:
像上面那样将 DCI 置于仅代码的角度是错过了更大的图景。与用户心智模型的联系,以及直接通过用例映射到代码的一些原因使它不仅仅是一种设计模式。【参考方案3】:如果您阅读原作者的this paper,特别是“我们哪里出错了?”一章,作者给出了他们认为需要采用新方法的一些理由。
简而言之:作者抱怨说,正确的面向对象方法会导致“拆分”业务逻辑。这是真的,因为这是我们分解问题的主要原因,所以我们不必一次性解决整个逻辑。
作者认为(在与上述相同的章节中),以前的程序方法更好(以 FORTRAN 代码为例),因为人们可以按顺序阅读代码并决定它是否执行它应该执行的操作。
他们还争辩说(在下一章:回到用户的头脑中),开发人员更容易先考虑“数据”,然后再考虑程序(例如交互)。
与将“数据和逻辑”捆绑在一起的面向对象相反,作者基本上主张至少对过程编程进行部分回归,明确分离数据和逻辑。
我个人的看法是,将这种方法称为面向对象有点误导,因为这是对它的强烈批评,并且有明显偏离它的意图。但是,不要相信我的话,请阅读文章。
【讨论】:
作者确实不认为程序方法更好。真实对象是 DCI 的重点,而不是当前的面向类的方法,您所说的分解方法使程序远离基于用户心理模型的东西(这是 DCI 的主要思想,而不是工程的东西.)以上是关于如何理解 DCI 模式的主要内容,如果未能解决你的问题,请参考以下文章