Как понять паттерн DCI
Согласно Wikipedia Data, контекст и взаимодействие (DCI) - это парадигма, используемая в компьютерном программном обеспечении для программирования систем взаимодействия объектов. Здесь мне не ясно о проблеме, которую DCI пытается решить. Можете ли вы объяснить это на простом примере? Что такое данные, контекст и взаимодействия в вашем примере?
3 ответа
Простой способ понять это - пример с классическим банковским приложением. В этом примере я буду использовать 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 может помочь решить эту проблему, предоставив нашим моделям данных поведение только в определенных контекстах, в которых им необходимо использовать это поведение. Давайте применим это к нашему примеру банковского приложения.
В нашем примере контекст переводит деньги. Данные будут 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
Как вы можете видеть из примера, Data получает свое поведение в контексте во время выполнения, когда мы вызываем 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:
- Отделение системы (данных) от того, что она делает (функцию). Данные и функции имеют разные скорости изменения, поэтому их следует разделять, а не как это делается в настоящее время, объединять в классы.
- Создайте прямое сопоставление ментальной модели пользователя с кодом. Компьютер должен думать как пользователь, а не наоборот, и код должен отражать это.
- Сделайте поведение системы первоклассной сущностью.
- Отличная читаемость кода без сюрпризов во время выполнения.
Я выделил ментальную модель пользователя, потому что это то, что на самом деле. Архитектура системы должна основываться на мыслительных процессах пользователей, а не инженеров.
Конечно, ментальная модель должна обсуждаться и создаваться всеми, кто связан с проектом, но это редко. Обычно инженеры кодируют в соответствии с шаблонами, декомпозицией, наследованием, полиморфизмом, а та часть кода, которая имеет смысл для пользователя, скрывается за слоями структуры.
Это то, что DCI пытается исправить. На протяжении многих лет он сталкивался с некоторым сопротивлением, по моему мнению, потому что инженеры любят свою структуру и классы, поэтому они сосредоточены в первую очередь на этом.
Пример иллюстрирующего кода был бы слишком длинным, чтобы публиковать здесь, но мышление в любом случае более важно. Речь идет об объектах, динамически работающих вместе, для решения конкретных задач. Я сделал более крупное руководство здесь с небольшим кодом: https://github.com/ciscoheat/haxedci-example
Кроме того, я очень рекомендую видео "Проблеск Трюгве" для дальнейшего объяснения одним из авторов DCI Джеймсом Коплиеном.
Если вы читаете эту статью от оригинальных авторов, в частности главу "Где мы ошиблись?", Авторы приводят некоторые причины, по которым они считают, что необходим новый подход.
Вкратце: авторы жалуются, что правильная объектно-ориентированная методология приводит к "расщеплению" бизнес-логики. Это верно, так как это основная причина, по которой мы разлагаем проблемы, так что нам не нужно решать всю логику за один раз.
Авторы утверждают (в той же главе, что и выше), что предыдущий процедурный подход был лучше (на примере кода FORTRAN), поскольку можно было последовательно прочитать код и решить, будет ли он делать то, что должен делать.
Они также утверждают (в следующей главе: Назад в голову пользователя), что разработчикам легче сначала думать о "данных", а потом о процедурах (например, взаимодействиях).
Авторы в основном утверждают, по крайней мере, частичную регрессию к процедурному программированию, четко разделяя данные и логику, в отличие от объектной ориентации, которая объединяет "данные и логику" вместе.
Мое личное мнение таково, что слегка ошибочно называть этот подход объектно-ориентированным, так как это его серьезная критика с явным намерением отклониться от него. Но, не верьте мне на слово, прочитайте статью.