Чому модель анемічного домену вважається поганою в C # / OOP, але дуже важливою для F # / FP?


46

У публікації блогу на F # для розваги та прибутку написано:

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

Це якраз протилежність об'єктно-орієнтованому дизайну, де маються на увазі поєднання поведінки та даних. Зрештою, саме такий клас є. В справді об'єктно-орієнтованому дизайні насправді у вас не повинно бути нічого, крім поведінки - дані приватні та доступ до них можна отримати лише методами.

Насправді в OOD недостатня поведінка навколо типу даних вважається поганою річчю і навіть має назву: " анемічна модель домену ".

Враховуючи, що в C # ми, здається, продовжуємо запозичувати F # і намагаємось написати код більш функціонального стилю; чому так, ми не запозичуємо ідею розділення даних / поведінки, і навіть вважаємо це поганим? Це просто те, що визначення не відповідає OOP, чи є конкретна причина, що це погано в C #, яка чомусь не застосовується у F # (а насправді є зворотною)?

.


1
Привіт, Ден! Ваше вдячність надихає. Окрім платформи .NET (і haskell), я заохочую вас переглянути масштаби. Debashish ghosh написав декілька блогів про моделювання домену за допомогою функціональних інструментів, для мене це було проникливим, сподіваємось і для вас, ось тут: debasishg.blogspot.com/2012/01/…
AndreasScheinert

2
Мені сьогодні колега надіслала цікавий допис у блозі: blog.inf.ed.ac.uk/sapm/2014/02/04/… Мабуть, люди починають оскаржувати думку про те, що анемічні моделі доменів відверто погані; що я думаю, що це може бути хорошою справою!
Danny Tuppeny

1
Повідомлення в блозі, на яке ви посилаєтесь, ґрунтується на помилковій ідеї: "Зазвичай нормально експонувати дані, не піддаючись інкапсуляції. Дані є непорушними, тому вони не можуть бути" пошкоджені "функцією неправильного поведінки". Навіть непорушні типи мають інваріанти, які потрібно зберегти, і для цього потрібно приховувати дані та контролювати, як вони можуть бути створені. Наприклад, ви не можете піддавати реалізації незмінного червоно-чорного дерева, оскільки тоді хтось може створити дерево, що складається лише з червоних вузлів.
Доваль

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

2
@JimmyHoffa Я згоден, але це не має нічого спільного з тим, що я критикував. Автор стверджує, що нормально піддавати експозиції дані нормально, тому що вони незмінні, і я кажу, що незмінність не магічно знімає необхідність приховувати деталі реалізації.
Доваль

Відповіді:


37

Основна причина, на яку прагне FP, а C # OOP не в тому, що у FP акцент робиться на еталонній прозорості; тобто дані переходять у функцію і дані виходять, але вихідні дані не змінюються.

У C # OOP існує концепція делегування відповідальності, де ви делегуєте управління об'єктом на нього, і тому ви хочете, щоб він змінив власну внутрішню.

У програмі FP ви ніколи не хочете змінювати значення в об'єкті, тому мати функції, вбудовані у ваш об'єкт, не має сенсу.

Далі у FP у вас вищий поліморфізм, що дозволяє вашим функціям бути набагато більш узагальненими, ніж дозволяє C # OOP. Таким чином ви можете написати функцію, яка працює для будь-якої a, і тому вбудовувати її в блок даних не має сенсу; що б щільно пара методу так , що він працює тільки з конкретним видом з a. Така поведінка в C # OOP все добре і поширена, тому що ви взагалі не маєте можливостей абстрактно функціонувати, але у FP це компроміс.

Найбільша проблема, яку я бачив у моделях анемічного домену в C # OOP, полягає в тому, що ви отримуєте дублікат коду, оскільки у вас є DTO x і 4 різні функції, які здійснюють активність f до DTO x, оскільки 4 різних людей не бачили іншої реалізації . Коли ви кладете метод прямо на DTO x, то ці 4 людини бачать реалізацію f і використовують її повторно.

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


Як зазначається в коментарях , висновок типу є однією з переваг, на які покладається FP, щоб дозволити такий значний поліморфізм, і конкретно ви можете простежити це до системи типу Хіндлі Мілнера з алгоритмом виводу типу W; такого виводу типу в системі типів C # OOP було уникнено, оскільки час компіляції, коли додається висновок на основі обмежень, стає надзвичайно довгим завдяки необхідному вичерпному пошуку, деталі тут: https://stackoverflow.com/questions/3968834/generics-why -cant-the-compiler-infer-the-type-argument-in-this-case


Які функції у F # полегшують написання такого коду для багаторазового використання? Чому код у C # не може бути такою ж багаторазовою? (Я думаю, я бачив можливість методів приймати аргументи з певними властивостями, не потребуючи інтерфейсу; який, мабуть, був би ключовим?)
Danny Tuppeny,

7
@DannyTuppeny, чесно кажучи, F # - поганий приклад для порівняння, це просто трохи одягнений C #; це змінна імперативна мова, як і C #, у неї є кілька засобів FP, яких C # не має, але не дуже. Подивіться на haskell, щоб побачити, де FP насправді виділяється, і подібні речі стають набагато більш можливими завдяки класам типів, а також загальним ADTs
Jimmy Hoffa

@MattFenwick я прямо посилаюся на C #, тому що про це запитував плакат. Де я посилаюся на OOP протягом усієї своєї відповіді тут, я маю на увазі C # OOP, я редагую, щоб уточнити.
Джиммі Хоффа

2
Загальною рисою функціональних мов, що дозволяють використовувати цей тип повторного використання, є динамічне або грудне введення тексту. Хоча сама мова може використовувати чітко визначені типи даних, типовій функції не важливо, які дані є, доки операції (інші функції або арифметичні), що виконуються на них, є дійсними. Це також доступно в парадигмах OO (наприклад, Go, має неявну інтерфейсну реалізацію, що дозволяє об'єкту бути качкою, оскільки він може Fly, Swim та Quack, без об'єкта явно оголошеного як Duck), але це в значній мірі необхідний функціональний програмування.
KeithS

4
@KeithS За перевантаженням на основі значення в Haskell, я думаю, ви маєте на увазі відповідність шаблону. Можливість Haskell мати декілька однойменних функцій верхнього рівня з різними візерунками desugars негайно до 1 функції рівного рівня + узгодження шаблону.
jozefg

6

Чому модель анемічного домену вважається поганою в C # / OOP, але дуже важливою для F # / FP?

У вашому запитання є велика проблема, яка обмежить корисність отриманих відповідей: ви маєте на увазі / припускаєте, що F # і FP схожі. FP - це величезне сімейство мов, включаючи символічне переписування термінів, динамічне та статичне. Навіть серед статично типових мов FP існує багато різних технологій для вираження доменних моделей, таких як модулі вищого порядку в OCaml та SML (які не існують у F #). F # є однією з цих функціональних мов, але особливо примітна тим, що вона є худорлявою і, зокрема, не забезпечує ні модулів вищого порядку, ні типів вищого типу.

Насправді я не міг почати розповідати вам про те, як виражаються доменні моделі в FP. Інша відповідь тут говорить конкретно про те, як це робиться в Haskell і зовсім не стосується Lisp (матері всіх мов FP), сім'ї мов ML або будь-яких інших функціональних мов.

чому так, ми не запозичуємо ідею розділення даних / поведінки, і навіть вважаємо це поганим?

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

Чи просто те, що визначення не відповідає OOP,

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

Ознайомтеся з прикладом, який я навів тут .

чи є конкретна причина, що це погано в C #, яка з якихось причин не застосовується у F # (а насправді є зворотною)?

Будьте уважні до переходу з OOP на C #. C # ніде не є пуританічним щодо OOP, як інші мови. .NET Framework тепер рясніє генеричними, статичними методами і навіть лямбдами.

.

Відсутність типів об'єднання та відповідності шаблонів у C # робить це майже неможливим. Коли у вас є молоток, все виглядає як цвях ...


2
... коли все, що у вас є, є молот, OOP фольклор створить фабрики молотів; P +1 для дійсного закріплення суті того, чого C # не вистачає, щоб дозволити дані та поведінку повністю абстрагуватися від інших: типів союзу та узгодження зразків.
Джиммі Хоффа

-4

Я думаю, що в бізнес-додатку ви часто не хочете приховувати дані, оскільки відповідність шаблонів на незмінні значення велика, щоб забезпечити охоплення всіх можливих випадків. Але якщо ви реалізуєте складні алгоритми чи структури даних, ви краще приховуєте деталі реалізації, перетворюючи ADT (алгебраїчні типи даних) в ADT (абстрактні типи даних).


4
Чи можете ви пояснити трохи більше про те, як це стосується об'єктно-орієнтованого програмування проти функціонального програмування?
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.