Comments 58
Имхо, по большей части все перечисленное давно реализовывалось в ОО через архитектуру SOA. Среди ее принципов и контракты (то, что автор называет ролями) и loose coupling (с вынесением сервис-логики) и т.п.
Интересно. Но как в такую модель вписывается иерархия объектов? Имеет ли право объект напрямую взаимодействовать с объектами, лежащими в его полях? Или для такого взаимодействия тоже нужно создавать контекст?
Поднимал эту тему в прошлую субботу на devmeetups.ru в Красноярске. Спасибо за интересные ссылки.
Очень прикольно. Как я понимаю, это пока на уровне паттернов реализуется… А прикольно было бы иметь ЯП с конструкциями типа:
class MyClass { ... }
role MyRole extends MyClass { ... }
context MyContext uses MyClass as MyRole, MyClass2 as MyRole2 { ... }
Вы можете создать DSL на руби, например и получить там все эти конструкции.
Я бы поспорил о необходимости такого расширения языка.
Хотя похожие опыты были — тот же SmallTalk
Хотя похожие опыты были — тот же SmallTalk
fork, мне кажется, что для ролей можно было вложенные унаследованные классы.
class User #< ActiveRecord::Base
class Author < User
#...
end
class Reader < User
# ...
end
end
мда…
Вы предлагаете наследовать роль от модели, что неверно, потому что роль может быть дана совершенно разным объектам. Например объекты Человек, Собака, Лошадь, роль — Бегун. Очень общий пример, но думаю мысль ясна.
В руби ваш подход лучше реализовать через модули
но и это противоречит DCI, роль назначается в контексте.
В руби ваш подход лучше реализовать через модули
class User
include Author
...
end
но и это противоречит DCI, роль назначается в контексте.
Спасибо, почитал по ссылкам о DCI более подробно — понял что к чему. Кстати, спасибо за статью.
Но тогда вам придется писать реализацию роли «Бегун» отдельно для Человека, Собаки и Лошади вместо того чтобы выделить общее поведение в базовом классе. И тогда уже роли обретут базовый класс «Бегун» с наследниками «БегунЧеловек», «БегунСобака», «БегунЛошадь». Видите как разрослась уже иерархия?
Нет, не придется. Отступление: я говорю в рамках руби. Чтобы Быть бегуном у всех должны быть ноги и все. А в роли Бегун у нас просто определена функция реализующая бег через ноги.
Я не силён в Ruby, вы имеете в виду что перечисленные сущности не имеют общего предка, но имеют некий одинаковый признак (метод) ноги? Ну тогда да, аналог в C++ можно будет реализовать шаблонами (template).
на плюсах как-то так:
Передаем в конструктор
class MyRole
{
public:
MyRole(const MyClass&); // не-explicit конструктор!
...
}
class MyRole2 ....
class Context
{
public:
Context(const MyRole&, const MyRole2&);
...
}
...
MyClass obj1; MyClass2 obj2;
Context context(obj1, obj2);
Передаем в конструктор
Context
ссылки на объекты сущностей (счетов), а по-факту создаются нужные роли.Хм, а не проще использовать интерфейсы, например в java можно было сделать так:
И тогда возникает вопрос, а нафига тут DCI? Интерфейсы, вот уже как десятки лет, служат нам для определения ролей класса. В общем, я пока не понял в чем фишка этого DCI…
public class Account implements SourceAccount, DestinationAccount {
...
}
public interface SourceAccount {
transfer_out(double amoun);
}
public interface DestinationAccount {
transfer_in(double amount);
}
public class TransferringMoney {
public TransferringMoney(SourceAccount, DestinationAccount) {
}
}
И тогда возникает вопрос, а нафига тут DCI? Интерфейсы, вот уже как десятки лет, служат нам для определения ролей класса. В общем, я пока не понял в чем фишка этого DCI…
Штука в том, что определенные роли нужны в определенном контексте и не стоит нагружать модель всеми ролями сразу. В парадигме DCI мы присваиваем роль объекту в контексте прямо перед использованием, мы не расширяем класс, а только конкретный объект. Это возвращает нас к думанию объектами, а не классами.
Плюс такого подхода — чистые доменные модели, которые ничего не знают о ролях и роли, которые не привязаны ни к одной из моделей.
Плюс такого подхода — чистые доменные модели, которые ничего не знают о ролях и роли, которые не привязаны ни к одной из моделей.
Хм, видимо я не сталкивался с такой сложной бизнес логикой, как по мне, в большинстве случаев, выше приведенного кода с интерфейсами хватает за глаза. Есть модель, роли и классы выполняющие операции над ролями. Дальнейший рефакторинг в DCI парадигму будет оверхедом, в случаях, с которыми я сталкивался.
у вас тут нет доступа из ролей в контекст.
Раза 3 прочитал статью и пытался понять, зачем городить такую сложную структуру классов когда можно написать одну функцию вроде:
Аккаунт — это данные, трансфер — это некоторое действие. Всё просто и понятно. Вся логика локализована в одном месте.
Я что-то не понимаю, да?
def transfer source_account_id, destination_account_id, amount
def transfer_out account, amount
raise "Insufficient funds" if account.balance < amount
account.decrease_balance amount
account.update_log "Transferred out", amount
end
def transfer_in account, amount
account.increase_balance amount
account.update_log "Transferred in", amount
end
transfer_out Account.find(source_account_id), amount
transfer_in Account.find(destination_account_id), amount
end
Аккаунт — это данные, трансфер — это некоторое действие. Всё просто и понятно. Вся логика локализована в одном месте.
Я что-то не понимаю, да?
Угу, не понимаете :)
В большом проекте в таких функциях потом легко утонуть. К тому же к чему привязана эта функция, где она лежит? Это не ООП подход.
Разделяя функционал на роли и контексты мы улучшаем читаемость кода. Модели — отдельно, действия (роли) — отдельно, события (контексты) — отдельно.
В идеале нужно разделить даже доменные модели и модели хранения данных (ORM) и абстрагировать свою бизнес логику от фреймворка вплоть до выделения ее в отдельную библиотеку.
Такой код проще тестировать, поддерживать и развивать.
Разделяя функционал на роли и контексты мы улучшаем читаемость кода. Модели — отдельно, действия (роли) — отдельно, события (контексты) — отдельно.
В идеале нужно разделить даже доменные модели и модели хранения данных (ORM) и абстрагировать свою бизнес логику от фреймворка вплоть до выделения ее в отдельную библиотеку.
Такой код проще тестировать, поддерживать и развивать.
Со всем полностью согласен. Но ключевая фраза «Это не ООП подход.»
Я именно это и хотел сказать, что не всё так гладко в ООП.
В данной статье, по сути, описывается метод реализации на ООП читабильности функционального подхода, когда выделяются отдельно стуктуры данных (в данном случае аккаунт), и отдельно действия (в данном случае транзакция и её контекст).
Из чего следует вопрос, а зачем тогда так следовать ООП?
Это напоминает ситуацию когда для вместо того, чтобы написать простую функцию с аргументами делают класс, в конструктор которого передают аргументы, и пишут отдельный метод вроде «run» для вызова. Спрашивается, зачем?
Я именно это и хотел сказать, что не всё так гладко в ООП.
В данной статье, по сути, описывается метод реализации на ООП читабильности функционального подхода, когда выделяются отдельно стуктуры данных (в данном случае аккаунт), и отдельно действия (в данном случае транзакция и её контекст).
Из чего следует вопрос, а зачем тогда так следовать ООП?
Это напоминает ситуацию когда для вместо того, чтобы написать простую функцию с аргументами делают класс, в конструктор которого передают аргументы, и пишут отдельный метод вроде «run» для вызова. Спрашивается, зачем?
Кажется, что замкнутые функции недоступны снаружи, поэтому их невозможно затестить. Вытаскивание наружу в ооп стиле приведет к появлению сервисного класса. К сожалению я не знаю руби, поэтому могу ошибаться.
self.transfer source_account_id, destination_account_id, amount
Выглядит как процедура. Поэтому кажется, что то ли отсылка к ооп протянула за уши, то ли пример для иллюстрации выбран не удачно.
Интересно, как подобное проворачивать в том же .NET? И несколько смущает факт, что в рамках контекста мы расширяем объект ролью, а затем… Обратно то её никто не забирает :) Понятно, что в контроллерах с кодом вида account = Account.find(account_id) такое прокатит, но в других случаях?
А затем объект вместе с ролью собирается GC. Контексты должны быть лаконичными и односложными, как и контроллеры. Это идеальная ситуация конечно, но к этому нужно стремиться.
В ruby нельзя раз-extend-ить класс =) В этом небольшая загвоздка DCI получается. В .net можно динамически добавлять методы классу?
А зачем класс? можно extend'ить объект:
?> module Talkable; def say; puts "Hi!"; end; end
=> nil
>> u = User.last; u.extend Talkable; u.say
Hi!
=> nil
А как забрать роль?
Для web приложений имхо это не актуально — объекты каждый раз создаются при запросе
а вообще, DCI через этот способ выглядит коряво
а вообще, DCI через этот способ выглядит коряво
Нет, в .NET нельзя динамически расширять класс\объект. Можно конечно сгенерировать прокси-наследник :))
Вообще, в итоге мне больше нравится способ, где классы-роли не наследуются от объектов, а принимают их в конструкторе и опираются на public-интерфейс переданных объектов. Тогда и забирать ничего не нужно, и в .NET и прочих языках применимо.
Вообще, в итоге мне больше нравится способ, где классы-роли не наследуются от объектов, а принимают их в конструкторе и опираются на public-интерфейс переданных объектов. Тогда и забирать ничего не нужно, и в .NET и прочих языках применимо.
module Talkable
def say
puts "Hi!"
end
end
u = User.last
u.extend Talkable
u.say
=> Hi!
Так в том и штука, чтобы думать объектами, а не классами. И расширять конкретный объект.
А я вот не понял, куда девать функциональность, используемую в нескольких контекстах, если она касается взаимодействия объектов, а не их внутренней жизни?
В Java все уже давно используют для этого Spring и кучу Service, в которые инжектируют DAO.
Это всё же разные вещи
Смысл тот же, только вместо ролей — интерфейсы
Смысл тот же, только вместо ролей — интерфейсы
Смысл тот же, только вместо ролей — интерфейсы
Где-то выше уже обсудили. Куча Service — это куча service. Здесь идея в едином контексте одной бизнес-транзакции. Один запрос — один контекст. Логика всех ролей собрана в одном месте, а не размазана ровным слоем по всем классам модели.
Либо вы в своём комментарии опустили подробности.
Либо вы в своём комментарии опустили подробности.
Выделять по контексту на каждую операцию — ну не знаю, это как-то чересчур.
А вот группировать роли и операции по сервисам — это нормально.
Например, может быть AccountingService, с методами generateInvoice(), acceptPayment(), DeliveryService с методами deliver() и confirmDelivery(), TaxService с методом sendTaxReport()
Эти сервисы могут использовать одни и те же DAO, но в разных контекстах. Так что — все очень похоже.
А вот группировать роли и операции по сервисам — это нормально.
Например, может быть AccountingService, с методами generateInvoice(), acceptPayment(), DeliveryService с методами deliver() и confirmDelivery(), TaxService с методом sendTaxReport()
Эти сервисы могут использовать одни и те же DAO, но в разных контекстах. Так что — все очень похоже.
Если бы это было похоже на то, как поступают в мире .NET/Java — этой статьи бы не было, всё же.
Правильно ли я понимаю что весь сыр-бор заключается по факту только в том, что мы на одну операцию заводим по одному классу (называем его SomeContext вместо SomeService) и парочку доп, классов с суффиксом Role?
Помоему «эволюция ОО парадигмы» это громко сказано. Больше тянет на паттерн, либо на дизайн гайдлайн по типу DDD'шных.
Проблема сама по себе интересная и актуальная, стоит напоминания.
Помоему «эволюция ОО парадигмы» это громко сказано. Больше тянет на паттерн, либо на дизайн гайдлайн по типу DDD'шных.
Проблема сама по себе интересная и актуальная, стоит напоминания.
Блин, DDoS достал, мне 3 раза показало ошибку, а реально — коммент отправлялся.
Спасибо за пост. Примечательно, что автору паттерна DCI сейчас 82 года.
это всё было в Common Lisp, а конкретнее — в CLOS, уже больше 20 лет назад.
Обобщенные функции плюс комбинаторы методов.
И выглядело приятнее.
Обобщенные функции плюс комбинаторы методов.
И выглядело приятнее.
Как мне кажется DCI способен проявить себя в приложениях со сложной и проработанной предметной областью.
Есть множество приложений, для которых DCI излишен.
DCI позволяет перекинуть мост между требованиями к создаваемому приложению (в формате USE CASE) и уже имеющейся предметной моделью. Вы реализуете USE CASES как вложенные Contexts и добавляете поведение к предметным моделям, с использованием Roles (без замутнения модели «случайным» поведением).
Есть множество приложений, для которых DCI излишен.
DCI позволяет перекинуть мост между требованиями к создаваемому приложению (в формате USE CASE) и уже имеющейся предметной моделью. Вы реализуете USE CASES как вложенные Contexts и добавляете поведение к предметным моделям, с использованием Roles (без замутнения модели «случайным» поведением).
Sign up to leave a comment.
Data Context Interaction (DCI) — эволюция объектно-ориентированной парадигмы