Сучасна диджитал-освіта для дітей — безоплатне заняття в GoITeens ×
Mazda CX 30
×

Парадигма програмування Data Context Interaction (DCI)

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Доброго ранку/ дня/ ночі, ми з України.

Шановне панство, нещодавно був на онлайн-конференції Architecture fwdays’2022. Сподобалась більшість матеріалу. Але йшов туди заради однієї доповіді: «An introduction to object-oriented programming for those who have never done it before... which probably includes you» by James Coplien, тому ця стаття присвячена їй.

James Coplien відомий своїми книгами по C++, патернами проєктування та активною позицією щодо використання Agile та SCRUM підходів у розробці ПЗ.

Але це не все. Він також активно просуває DCI парадигму створення ПЗ, винахідником якої є Trygve Reenskaug.
На цю тему багато усього сказано та написано, але на практиці я рідко зустрічаю проєкти, де явно використовується DCI. Тому хочу зупинитись на суті цієї парадигми, щоб сворити інтерес у читача для подальшого дослідження цієї теми.

Які проблеми бачить Джеймс у сьогоденній парадигмі програмування ООП.

Проблематика

По-перше, він стверджує, що більшість розробників зазраз займаються не об’єктно орієнтовним, а клас орієнтовним програмуванням. Тобто розробник здебільшого мислить класами, таблицями БД, а не динамічними об’єктами. Ідея ООП, на думку Алана Кея, людини, яка зробила великий вклад у розвиток ООП, полягає у тому, щоб створювати у системах ПЗ проксі-об’єкти, які є відображенням об’єктів свідомості користувачів системи. Алан Кей приводить наступний приклад: коли дитина досліджує навколишнє серидовище, вона створює у свідомості ментальні образи — об’єкти, якими надалі оперує. І вони повинні бути відображені в системі ПЗ.

Також сьогоднішні інструменти розробки, такі як мови програмування, у парадигмі ООП створюють умови для фокусування розробника здебільшого на деталях реалізації, ніж на Use Cases, які своєю чергою відображають поведінку системи, пов’язану з діями користувача. Тому в коді іноді стає не зрозумілим, де реалізація того чи іншого Use Case.

А треба нагадати, ми продаємо користувачеві, ні REST services, ні класи, а Use Cases!

По-друге, однією з проблем сучасного ООП, на думку Джеймса, є комбінація успадкування та поліморфізма. Коли здійснюється визов метода інстанса класа, десь у глибині є механізм (базований на використанні таблиці віртуальних методів), який вирішує, який насправді метод визвати, щось накшталт блока з багатьма goto. Це ускладнює розуміння поведінки системи й того, як реалізовано тий чи інший Use Case.

Рішення

Щоб покращити ситуацію, він пропонує іншу парадигму під назвою DCI. Як я написав раніше, її автором є Trygve Reenskaug, який також є винахідником патерну MVC.

Отже як розшифровується абревіатура DCI?

DCI — Data, Context, Interaction (дані, контекст, взаємодія).

  • Data(State) — це формалізована предметна область, доменна модель, behaviourless стан об’єктів чи їх data складова.
  • Context(Use Case) — це контекст, у якому діють об’єкти. Context як правило є імплементацією якогось Use Case.
  • Interaction(Behaviour) — це stateless імплементація ролей, в залежності від контексту. Вона додає до data складової об’єктів поведінкову, в залежності від контексту.

Аналогія. Майже кожна людина (об’єкт з Data) у житті виступає в тій чи іншій ролі. Наприклад, приходячи у магазин (Сontex), вона діє у ролі (Interaction) покупця. Граючи у футбол (Сontext), вона можете бути у ролі (Interaction) нападника, захистника, півзахистника чи голкіпера.

Висновки

Повертаючись до DCI та підсумовуючи написане, робимо висновки.

DCI фокусує розробника на Use Cases. Кожен раз створюючи Context, розробник створює реалізацію того чи іншого варіанта використання системи. Його діячами є об’єкти системи — прообрази ментальної моделі користувача. Вони своєю чергою розкладаюсться на дві складові: станова складова Data та поведінкова Interaction. Остання накладається на об’єкт в залежності від конкретного контексту.

Слід також зауважити, що Джеймс є критиком TDD. Він ввжає, що це формує у свідомості розробника невірний спосіб мислення. Замість того, щоб почати з формування high-level бачення системи, створити її «скелет» та йти зверху до низу, TDD пропонує фокусуватись на конкретній реалізаціі тих чи інших методів класів. I я з ним у цьому погоджуюсь. Використовуючи TDD, з початку створення проекту, перестаєш бачити ліс за деревом.

Наведу класичний приклад: трансфер грошей з одного акаунта на інший.

Спочатку на C#, у моїй інтерпретації, яку я накидав ввечері після конференції, по «свіжій» пам’яті. Вважаю цей приклад простішим для сприйняття, та більш type safe, ніж приклад на C# від Джеймса. Думаю, він більш мислить як C++ розробник, ніж C#.

Потім — на C# з книги Джеймса «The DCI Architecture: Lean and Agile at the Code Level».

І, врешті, — на спеціально свореній для DCI мові програмування trygve, яка має Java-синтакс і є pure мовою, де все expression.

Насправді у його книзі є приклади на багатьох мовах програмування.

Приклади

Money Transfer приклад на С#. Мій варіант.

using System;
namespace Dci.Console
{
    /// <summary>
    /// Data(state, behaviorless) component.
    /// The class for the data component of the user's mind account object.
    /// </summary>
    class Account
    {
        public Account(decimal balance)
        {
            Balance = balance; 
        }
        public decimal Balance { get; set; }
    }
    /// <summary>
    /// Interaction(stateless, behavior) component.
    /// The class for the behavior component of the user's mind source account object.
    /// Acts as a SOURCE role in the DataTransferContext.
    /// Applying the behavior to the Account objects.
    /// </summary>
    sealed class SourceAccountRole
    {
        private readonly Account _account;
        public SourceAccountRole(Account account)
        {
            _account = account;
        }
        public void Transfer(DestinationAccountRole destination, decimal amount)
        {
            if (_account.Balance - amount < 0)
                throw new ArgumentOutOfRangeException(nameof(amount));
            _account.Balance -= amount;
            destination.IncreaseAmount(amount);
        }
    }
    /// <summary>
    /// Interaction(stateless, behavior) component.
    /// The class for the behavior component of the user's mind destination account object.
    /// Acts as a DESTINATION role in the DataTransferContext.
    /// Appling the behavior to the Account objects.
    /// </summary>
    sealed class DestinationAccountRole
    {
        private readonly Account _account;
        public DestinationAccountRole(Account account)
        {
            _account = account;
        }
        public Account Account { get; }
        public void IncreaseAmount(decimal amount)
        {
            _account.Balance += amount;
        }
    }
    /// <summary>
    /// The context(use case).
    /// Context for transferring money from one account to another.
    /// </summary>
    sealed class MoneyTransferContext
    {
        private readonly SourceAccountRole _source;
        private readonly DestinationAccountRole _destination;
        private readonly decimal _amount;
        public MoneyTransferContext(Account source, Account destination, decimal amount)
        {
            _source = new SourceAccountRole(source);
            _destination = new DestinationAccountRole(destination);
            _amount = amount;
        }
        public void Execute()
        {
            _source.Transfer(_destination, _amount);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            // Init accounts. In the real cases came from the data access layer
            var sourceAccount = new Account(100);
            var destinationAccount = new Account(0);
            System.Console.WriteLine($"[Before] source.Balance={sourceAccount.Balance}, destination.Balance={destinationAccount.Balance}");
            // Create context(Use Case)
            MoneyTransferContext context = new MoneyTransferContext(sourceAccount, destinationAccount, 20);
            // Excute context(Use Case)
            context.Execute();
            System.Console.WriteLine($"[After] source.Balance={sourceAccount.Balance}, destination.Balance={destinationAccount.Balance}");
        }
    }
}


Money Transfer, приклад на С#. з книги «The DCI Architecture: Lean and Agile at the Code Level».

using System;
namespace DCI
{
    // Methodless role types
    public interface TransferMoneySink
    {
    }
    // Methodful roles
    public interface TransferMoneySource
    {
    }
    public static class TransferMoneySourceTraits
    {
        public static void TransferFrom(
                this TransferMoneySource self,
                TransferMoneySink recipient, double amount)
        {
            // This methodful role can only
            // be mixed into Account object (and subtypes)
            Account self_=self as Account;
            Account recipient_=recipient as Account;
            // Self-contained readable and testable
            // algorithm
            if (self_ != null && recipient_ != null)
            {
                self_.DecreaseBalance(amount);
                self_.Log("Withdrawing " + amount);
                recipient_.IncreaseBalance(amount);
                recipient_.Log("Depositing " + amount);
            }
         }
      }
     // Context object
     public class TransferMoneyContext
     {
         // Properties for accessing the concrete objects
         // relevant in this context through their
         // methodless roles
         public TransferMoneySource Source {
             get; private set;
         }
         public TransferMoneySink Sink {
             get;
             private set;
         }
         public double Amount {
             get; private set;
         }
         public TransferMoneyContext()
         {
             // logic for retrieving source and sink accounts
         }
         public TransferMoneyContext(
                         TransferMoneySource source,
                         TransferMoneySink sink,
                         double amount)
           {
               Source = source;
               Sink = sink;
               Amount = amount;
           }
           public void Doit()
           {
               Source.TransferFrom(Sink, Amount);
               // Alternatively the context could be passed
               // to the source and sink object.
           }
       }
       ///////////// Model ////////////////
       // Abstract domain object
       public abstract class Account
       {
           public abstract void DecreaseBalance(
                           double amount);
           public abstract void IncreaseBalance(
                           double amount);
           public abstract void Log(string message);
       }
       // Concrete domain object
       public class SavingsAccount :
           Account,
           TransferMoneySource,
           TransferMoneySink
       {
           private double balance;
           public SavingsAccount()
           {
               balance = 10000;
           }
           public override void DecreaseBalance(double amount)
           {
               balance -= amount;
           }
           public override void IncreaseBalance(
                                             double amount)
           {
               balance += amount;
           }
           public override void Log(string message)
           {
               Console.WriteLine(message);
           }
           public override string ToString()
           {
               return "Balance " + balance;
           }
       }
        ///////////// Controller ////////////////
        // Test controller
        public class App
        {
            public static void Main(string[] args)
            {
                SavingsAccount src=new SavingsAccount();
                SavingsAccount snk=new SavingsAccount();
                Console.WriteLine("Before:");
                Console.WriteLine("Src:" + src);
                Console.WriteLine("Snk:" + snk);
                Console.WriteLine("Run transfer:");
                new TransferMoneyContext(src, snk, 1000).Doit();
                Console.WriteLine("After:");
                Console.WriteLine("Src:" + src);
                Console.WriteLine("Snk:" + snk);
                Console.ReadLine();
          }
     }
}

Money transfer, приклад на Trygve.

context TransferMoneyContext
{
    // Roles
    role AMOUNT {
        public double amount() const;
    } requires {
        double amount() const;
    }
    role GUI {
        public void displayScreen(int displayCode)
    } requires {
        void displayScreen(int displayCode)
    }
    
    role SOURCE_ACCOUNT {
        public void transferTo() {
            // This code is reviewable and meaningfully testable with stubs!
            int SUCCESS_DEPOSIT_SCREEN = 10;
            beginTransaction();
            
            if (this.availableBalance() < AMOUNT) {
                endTransaction();
                assert(false, "Unavailable balance")
            } else {
                this.decreaseBalance(AMOUNT);
                DESTINATION_ACCOUNT.increaseBalance(AMOUNT);
                this.updateLog("Transfer Out", new Date(), AMOUNT);
                DESTINATION_ACCOUNT.updateLog("Transfer In", new Date(), AMOUNT);
            }
            GUI.displayScreen(SUCCESS_DEPOSIT_SCREEN);
            endTransaction();
        }
    } requires {
        void decreaseBalance(Currency amount);
        Currency availableBalance() const;
        void updateLog(String msg, Date time, Currency amount)
    }
    role DESTINATION_ACCOUNT {
        public void transferFrom() {
            this.increaseBalance(AMOUNT);
            this.updateLog("Transfer in", new Date(), AMOUNT);
        }
        public void increaseBalance(Currency amount);
        public void updateLog(String msg, Date time, Currency amount)
    } requires {
        void increaseBalance(Currency amount);
        void updateLog(String msg, Date time, Currency amount)
    }
    public TransferMoneyContext(Currency amount, Account source, Account destination) {
        SOURCE_ACCOUNT = source;
        DESTINATION_ACCOUNT = destination;
        AMOUNT = amount
    }
    public TransferMoneyContext() {
        lookupBindings()
    }
    public void doit() {
        SOURCE_ACCOUNT.transferTo()
    }
    private void lookupBindings() {
        // These are somewhat arbitrary and for illustrative
        // purposes. The simulate a database lookup
        InvestmentAccount investmentAccount = new InvestmentAccount();
        investmentAccount.increaseBalance(new Euro(100.00)); // prime it with some money
    
        // Assign SOURCE_ACCOUNT role to the investmentAccount object
        SOURCE_ACCOUNT = investmentAccount;
        
        // Assign DESTINATION_ACCOUNT role to anonymous SavingsAccount object
        DESTINATION_ACCOUNT = new SavingsAccount();
        DESTINATION_ACCOUNT.increaseBalance(new Euro(500.00)); // start it off with money
        // Assign new value to the AMOUNT role
        AMOUNT = new Euro(30.00)
    }
}
int main() {
    // Main
    TransferMoneyContext  aNewUseCase = new TransferMoneyContext();
    aNewUseCase.doit();
}
👍ПодобаєтьсяСподобалось5
До обраногоВ обраному5
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Так, теж зацікавила та доповідь. І на мій погляд було доволі обґрунтовано пояснено, чому поширений нині «об’єктно-орієнтований підхід» програмування насправді орієнтований на класи, а не об’єкти як це передбачалося засновниками підходу.
Хто бажає розібратися в аргументах цього ствердження — шукайте запис тієї доповіді. Оскільки ця стаття не для того щоби адвокатувати бачення доповідача. А скоріш про те, як це зрозумів один зі слухачів конференції.

Зрозуміло, що сучасне ООП в більшості мов орієнтоване на класи. Але, можливо, ці мови популярні тому, що така орієнтація зручна?

Постійно працюю в розробці і перший раз стикнувся з цим. Перше враження від прочитанного — Це анемічний антипатерн у ооп моделюванні, або якась інтерпритація gof паттерну command — а не парадигма. Хоча я можу помилятися — детально не вивчав що підхід пропонує як заміну моделювання взаємодії обьєктів, процедур або функцій властивих дійсно тому що ми звикли називати парадигмами.

Доброго дня.

Парадигма — це спосіб класифікації підходів у програмуванні.

Наприклад у процедурному програмуванні данні відокремленні від поведінки, а поведінка реалізована у процедурах. У клас орієнтовному програмуванні(то що ми називаємо ООП) — навпаки, данні об’єднані з методами у класи. Є ще декілька парадигм, наприклад Prototype-орієнтоване програмування, та т.і.

Відносно, DCI — як я писав у статті, головними структурами є об’экти (не класи), які:
— мають data складову, яка описуэться класами, чи структурами, чи спеціальними конструкціями (зележно від мови програмування)
— є учасниками якихость UseCases, котрі реалізовани у вигляди Contextів
— а їх поведінка, чи взаъмодія (Interaction) описана ролями у конкретних конекстах.

Тобто є UseCase, перевод коштів з одного акаунта на другий. Йому відповідає Context — TransferMoney. Акторами у цьому контексті э два акаунта (Data складов). І на ці акаунти, у момент створення контескту, накладаються дві ролі: на перший акаунт роль SourceAccountRole, на другий: DestinationAccountRole. Це э у прикладі.

Також у DCI немаэ поліморфізму (через успадкування) — що спрощує спрйняття коду.

Відносно, DCI — як я писав у статті, головними структурами є об’экти (не класи),

У ООП теж ключовим є поняття об‘єкту, а не классу. Дивись js, до появи класів там було ооп реалізоване на об‘єктах прототипах. В вашому випадку це теж за великим рахунком той же ооп.

Також у DCI немаэ поліморфізму (через успадкування) — що спрощує спрйняття коду.

Якщо відкинути теоретичну складову і говорити мовую практиків — Не зрозуміло, що є building block програми у вашій новій парадигмі — у ооп — класси/об‘єкти що взаємодіють, у фп — функції та їх композиція.. у вас відсутні фундаментально відмінні від існуючих парадигм речі, власне кажучи ви використовуєте інструментарій ооп программуваня, та заявляєте що маєте принципово щось відмінне від ооп, тому що не використовуєте частково положення/інструментірій ооп — цього мало для того щоб говорити про новую парадигму, може це й описано десь на більш глибокому концептуальному рівні, але у прикладах вище це звичайний ооп під різним соусом.

У ООП теж ключовим є поняття об‘єкту, а не классу. Дивись js, до появи класів там було ооп реалізоване на об‘єктах прототипах.

Ну так JS це одна із небагатьох мов яка по справжньому підтримує ООП (а не КОП — клас орієнтовне програмування). Ти міг(і доречі можеш зараз) зробити:

Object obj = new Object();
obj.my_method = functiion()
{
  // Do work
}
У цьому випадку та якраз займаэшься ООП, беспосередньо: конкретному об’єтку інкапсулюєшь метод. Це точка зору Анана Кея (якого я приводив у статті), та Джеймса.

А ось тут, ти займаэшься розробкою класу, а не песпосередньо об’єкта, тому вони вважають це більш class-орієнтед, ніж object-орієнтед.

class MyClass
{
  public my_method()
  {
    // Do Work
  }
}
Якщо відкинути теоретичну складову і говорити мовую практиків — Не зрозуміло, що є building block програми у вашій новій парадигмі

Ну як не зрозуміло. Все зрозуміло.
Я сприймаю DCI — як високорівневеву надстройку над справжнім ООП програмуванням (без використання поліморфізму). Ось її building blocks:

— Data — це data складова об’’эктів
— Interaction — поведінкова, залежно від контекста
— Context — це якійсь Use Case

Якщо ви дивились приклад на trygve, то ви бачили як в run-time до data складової об’єтка додається поведінкова у контексті MoneyTransfer. От Вам і справжнє ООП.

До речі ця парадигма не є такою ж і новою. Вона створена на прикінці 90-x — як більш прозора, та зрозуміла альтернатива КОП. Її автор дуже не задоволений КОП, і більш за все поліморфізмом. DCI почала популяризуватись десь на початку 2000-х. Але у той момент гіганти бізнесу продвигали клас орієнтовні мови, наприклад C++ та Java. В популяризацію Java наприклад, було вбухано багато грошей і на цьому фоні багато цікавих речей стали непомітними.

але у прикладах вище це звичайний ооп під різним соусом.

Ще раз Вам кажу, придевитесь. Java, C#, ... — це клас орієнтовні мови. Ви будуєте класи.
В JS, та trygve наприклад, ви беспосередньо констуюєте об’єкти в рантаймі. Тобто додаєте до об’єктів(ще раз підкреслюю, не класів) нові методи, та свойства.

Ну як не зрозуміло. Все зрозуміло.
Я сприймаю DCI — як високорівневеву надстройку над справжнім ООП програмуванням (без використання поліморфізму).

Ok, я про це впринципі і кажу. Але спочатку мені здалося що ви протиставляєте ці практики ооп по вашим тезам.

В такому стилі написання найбільш проблематичним є побудова однотипних залежних use cases що можуть мати повторення якоїсь поведінки — наприклад saving account, current account мають спільного при переказі- невід’ємний баланс, активність рахунку, ліміти банку, клієнта на розмір операції, відмінності — що з current можна зняти гроші на вимогу , а з savings — ні, на current можна відкрити овердрафт , на savings ні — ну і власне питання як воно було б реалізовано у запропонованому підході. Зазвичай таке моделювання виливається в композицію сервісів(interactions) які виглядають зовсім непривабливо.

Ok, я про це впринципі і кажу. Але спочатку мені здалося що ви протиставляєте ці практики ооп по вашим тезам.

Ні в якому разі не протиставляю. На більшості сучасних мов, можна емулювати DCI за допомогою власноруч створених абстракцій, як показано у прикладах. Але building blocks DCI не вбудовані на рівні мов, і не форсять девелопера їх використовувати, окрім спеціально створеної мови trygve.

Також поліморфізм через успадкування, не дуже гарна річ(у бізнес логіці), але він активно використовується на великій кількості проектів. Тому що на практиці іноді зручно його використовувати при створенні коду, але читати полотна чужого кода, де великі класи і активно використовується поліморфізм через успадкування не дуже зручно — перевірено.

Тому я бачу DCI наступним чином:
Є объекти з данними, у них немаэ поведінки. І це деколи добре, тому що, якщо об’єкт може бути актором у багатьох контекстах, і для кожного такого контекста він має у собі самому поведінку — то це перевантажуэ відповідальність об’єкта, порушується принцип Single Responsibility. І як результат ми маємо величехні класи, які потім складніше розуміти, та сапортити. Я з таким песпосередньо зустрічався на декількох проектах. Ти дійсно не розумієш чому система веде себе так. А тому що, десь там у якомусь предку, є віртуальна функція яка не перегружена у іншому предку (хоча у більшості однорівневих йому предках вона перегружена) і т.і.

А ось коли э окремий контекст для конкретного UseCase, і поведінка об’єкта прописана у цьому контексті — це на мою думку добре. Код добре структурується таким чином. QA каже, ось у цьому UseCase баг. Ти відкриваєш UseCase, дивишся які там об’єкти фігурують, яка у них поведінка, і зразу все стає зрозумілим. Тому що весь код у одному місті, код не перетинається з іншими поведінками, немає ніякого поліморіфзму, тому немає там десь у глибині якоїсь несподіванки.

Але це відноситься тільки до бізнес логіки. Я вважаю що у Frameworkaх потрібен будти поліморфізм. Це дуже гарний інструмент, коли треба мати якусь дефолтну поведінку, та можливість її кастемізувати. Перегружаєш метод, прописуєш іншу поведінку і вуаля. Чи використання template method паттернів іноді корисне.

Тому я бачу DCI наступним чином:
Є объекти з данними, у них немаэ поведінки. І це деколи добре, тому що, якщо об’єкт може бути актором у багатьох контекстах, і для кожного такого контекста він має у собі самому поведінку — то це перевантажуэ відповідальність об’єкта, порушується принцип Single Responsibility.

Це — класичне процедурне програмування. Такі об’єкти звуться структурами (в С).

Це так, але в C в мову не вбудовані контексти, не можливо цій структурі у рантаймі апплайати якусь поведінку. Топто так, у DCI э риси процедурної мови програмування, наприкалд відсутність поліморфізму через успадкування, данні без поведінки, але ж є такі речі, яких немає в процедурному програмуванні.

C має вказівники на функції, котрі дозволяють в рантаймі аплаїти що завгодно.
Так працює завантаження драйверів в операційних системах.

А коли контекст — це процедура, то процедури вбудовані в С.

Так, на Assembler теж все це можна зобити...
А чому ні, ствоюємо на стеці необхідні данні(або адреса даних у випадку якихось строк), ось тобі — D, потім туди ща пушаєм адреси процедур, ось тобі — I. А потім робимо кол контекстної процедупри, ось тобі — С! Евріка, теж DCI! ))

От і питаннячко — що ж в ньому нового?)

Високорівневі абстакції, які маюь певну семантику, відсутню у процерури.
Наприклад тут зрозуміло що э певна роль SourceAccount яка маэ метод transfert_monery, також вона вимагаэ від data объекта наявість метода decrease_balance і таке інше. Тобто ця мова виражає деяку суть.

role SourceAccount
{
   void transfert_monery(DestinationAccount destination)
   {
     ...
   }
   require
   {
     decrease_balance(decimal);
   }
}

Але структура з адресами процедур, не маэ такої виразності — йде втрата інформації.

Звісно це можна подалати якимось конвенціями йменування структур, та процедур, що з імені було б зрозуміло чи то роль, ци щесть що. Але все одно не буде семантичної перевірки використаня тих чи інших конструкцій компілятором — він нічого не знає, про ролі, контексти, тому буде допускати якісь неприпустимі речі з концептукльної точки зору.

Та я гадаю це теж має право на життя, у деяких випадках, але потребує більшої дисципліни від девелоперів.

Але якщо є мова у яку вбудовані всі ці абстакції, то це ІМНО гуд. Код стає більш структурованим, та зрозумілим, компілярор на етапі компіляції може виявити більше помилок та таке інше.

Схоже на наслідування (імплементацію) інтерфейсу.
Але як в мові відмовились від класів, то довелося для цього вигадувати нову абстракцію.

А від класів ніхто не відмовився, вони є у trygve: 5. Class-Oriented Programming. Немаэ поліморфізму через успадкування.
Але ж знову — клас це більш низкорівнева абстракція відносно контекстів та ролей.

Так, за допомогою класів та інтерфейсів можна виразити DCI, можна ввести конвенції, так як у прикладах на C#. Але все одно на ріні компілятора не буде усіх необхідних перевірок, як з процедурами. Але це теж варіант, якщо наприклад переходити на trygve немає сенсу за відсутності під нього необхідних фреймворків та ліб.

Тобто DCI — це концепція, як MVC та інші, яка може будти представлена паттернами. Які можуть бути реалізовані на тий чи іншій мові за допомогою конвенцій.

Але є і мова trygve, яка включає абстракції DCI на рівні синтаксиса та семантики...

Що використовувати, та чи використовувати взагалі — це вибір архітектіа, або lead девелопера (якщо на проекті немає архітекта)...

Кто-то может обьяснить, как это работает ?
Вот вам простая задача вычесть деньги с одного счета, зачислить на другой:
Было — Простыня кода #1
Стало — Простыня кода #2
Обе простыни примерно одинакового размера.
Допустим, а где профит ?

Перечитайте статтю, в ній все сказано. Якщо, все ще не розумієте — скіпайте, значить це не для Вас.

Ще й з прикладами які йдуть в комплекті до стандартної видачі Гугла за запитом DCI.
Автор пиши ще!

Дякую за feedback.

Наступний свій web pet-проект планую робити на DCI. Тому думаю, напишу ще щось на цю тему.
Слідкуйте за анонсами на DOU.

Спроба відходити від процедурних стилів написання коду з засиллям анемічних моделей.

Цікаво, має право на життя.

Нажаль, незрозуміло, навіщо це на практиці.

Сам DCI, як Ви описали, виглядає як трьохшаровий підхід, котрий можна зробити на будь-якій мові програмування з ООП чи поліморфізмом інтерфейсів. Трохи схоже на розділення сценаріїв та доменів в DDD, якщо я вірно його пам’ятаю. В принципі, з цим ковирялась купа народу, кожен придумував свою назву herbertograca.com/...​ow-i-put-it-all-together

Хотілося б побачити приклади, в яких випадках чи чому воно буде працювати ліпше за звичайне класичне ООП, чи за ООП без класів. Який плюс, де фіча. Які мінуси. Чому не можна те ж зробити на С++. До речі, фічою, що продала С++ старим програмерам на С, були деструктори. Не ООП з наслідуванням, не виключення, а деструктори, котрі в С зробити неможливо, і котрі дозволяють автоматом звільняти ресурси при виході з функції.

котрий можна зробити на будь-якій мові програмування з ООП чи поліморфізмом

Звичайно можна.
Але це більше спроба відповзти від остогидлих %DomainName%Service та %EntityName%Service.

Плюсы та мінуси можна буде зрозуміти тіки на практиці якщо впилить це кудись в живий проект, з покриттям юніт тестами.

Як цікавий сайд-ефект — такий підхід може полегшити наприклад логування та збереження стану в бд.
Наприклад:
Account a1 = ...
Account a2 = ...
TransferFromTo t = new TransferFromTo(a1, a2)
transferRepo.save(t)

Ця парадигма має одну, дуже важливу з моє точки зору фічу. Вона дає можливість виражати дуже чітко Use Cases. Ні ООП, ні процедурна парадигма такого не дають.

На найвищому рівні абстракції у процедурній парадигмі знаходиться процедура, чи модуль з процедурами. У ООП це клас(хоча на справді об’єкт). Але це все деталі реалізації. У цих парадигмах відсутній уровень документації, опису Use Cases, акторів системи, їх поведінки. Є класи, сервіси, API, та інші девелоперські речі.

Але ж ви розумієте, що ми продаємо не класи, ні АПІ, ні патерни кінцевому юзеру? Ми продаємо функціонал, який описаний Use Caseами.

Тому основний плюс DCI, це наява уровня абстракції, котрий описує Use Cases! Ви відкриваєте код і зразу бачите всі Use Cases з усіма ролями, та імплементацією поведінки кожної ролі.

Зазвичай на ООП проекті є діаграми, та текстові файли з Use Cases, та окремо код який це реалізує. І це проблема. Треба постійно сінхронізувати доку з кодом, ти не бачиш явно в коді де реалізован той чи інший UseCase.

Також, як я писав у статті, відсутність поліморфізму, спрощує розуміння коду. Але з моєї точки зору поліморфізм має право на життя у інфраструктурному коді, у фреймворках, де перевантачуючи деякі методи, ми змінюємо дефолтну поведінку системи.

Ось шари в DDD:

User Interface
Responsible for drawing the screens the users use to interact with the application and translating the user’s inputs into application commands. It is important to note that the “users” can be human but can also be other applications connecting to our API, which corresponds entirely to the Boundary objects in the EBI architecture;
Application Layer
Orchestrates Domain objects to perform tasks required by the users: the Use Cases. It does not contain business logic. This relates to the Interactors in the EBI architecture, except that the Interactors were any object that was not related to the UI or an Entity and, in this case, the Application Layer only contains the objects relevant to a Use Case. This layer is where the Application Services belong, as they are the containers where the use case orchestration happens, using repositories, Domain Services, Entities, Value Objects or any other Domain object;
Domain Layer
This is the layer that contains all the business logic, the Domain Services, Entities, Events and any other object type that contains Business Logic. It obviously relates to the Entity object type of EBI. This is the heart of the system. The Domain Services will contain the Domain logic that does not quite fit in an Entity, usually orchestrating several entities in accomplishing some domain action;
Infrastructure
The technical capabilities that support the layers above, ie. persistence or messaging.
herbertograca.com/...​/07/domain-driven-design

Як бачите, the Use Cases виділені в окремий шар — відповідно, матимете клас чи процедуру для кожного юз кейса. Не бачу різниці. При тому, що це 2002 рік, здається (книжка DDD).

Окрім того, не у всіх доменах можна продавати Use Cases. Наприклад, наразі працюю з движком бази даних, на минулому проекті — була телефонія. Там юз кейс буде в кілька рядків, а інфраструктурного коду, щоб цей юз кейс забезпечити — десятки чи сотні тисяч рядків під капотом.

Так, а ніхто і не каже що це срібна куля, яка вирішує усі питання для всіх кейсів. Це просто ще одна парадигма, і використовувати її чи ні, залежить від конкретного випадку.

Ну і як на мене DCI парадигма більш проста для розуміння, та імплементації, та гнучка ніж highweight DDD.

Я наприклад зараз бачу, що DCI більш підходить для використання у Business Applications домені, ніж у системному.

Підписатись на коментарі