Почему анемичная модель предметной области считается плохой в C # / OOP, но очень важной в F # / FP?

46

В сообщении в блоге на F # для развлечения и выгоды говорится:

В функциональном дизайне очень важно отделить поведение от данных. Типы данных просты и «тупы». И затем отдельно у вас есть ряд функций, которые действуют на эти типы данных.

Это полная противоположность объектно-ориентированному дизайну, где поведение и данные должны быть объединены. В конце концов, это именно то, что класс. На самом деле в действительно объектно-ориентированном дизайне у вас не должно быть ничего, кроме поведения - данные являются частными и могут быть доступны только через методы.

На самом деле, в OOD отсутствие поведения вокруг типа данных считается плохой вещью и даже имеет название: « модель анемичной области ».

Учитывая, что в C # мы, кажется, продолжаем заимствовать у F # и пытаемся писать более функциональный код; почему мы не заимствуем идею разделения данных / поведения и даже не считаем ее плохой? Это просто то, что определение не подходит для ООП, или есть конкретная причина, по которой это плохо в C #, которая по какой-то причине не применяется в F # (и фактически перевернута)?

(Примечание: меня особенно интересуют различия в C # / F #, которые могут изменить мнение о том, что хорошо / плохо, а не люди, которые могут не согласиться с любым мнением в сообщении в блоге).

Дэнни Таппени
источник
1
Привет, Дэн! Ваше восхищение вдохновляет. Помимо платформы .NET (и haskell), я рекомендую вам взглянуть на scala. Debashish ghosh написал пару блогов о моделировании предметной области с помощью функциональных инструментов, для меня это было полезно, надеюсь, и для вас, вот вам: debasishg.blogspot.com/2012/01/…
AndreasScheinert
2
Сегодня один коллега прислал мне интересное сообщение в блоге: blog.inf.ed.ac.uk/sapm/2014/02/04/… Кажется, что люди начинают оспаривать идею о том, что модели анемичных доменов совершенно плохи; Я думаю, что это может быть хорошо!
Дэнни Таппени
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-компилятором Infer-типа-аргументы-в-этом-случае

Джимми Хоффа
источник
Какие функции в F # облегчают написание такого кода для повторного использования? Почему код в C # не может быть повторно используемым? (Я думаю, я видел способность иметь методы, принимающие аргументы с определенными свойствами, без необходимости в интерфейсе; который, я думаю, был бы ключевым?)
Дэнни Таппени
7
@DannyTuppeny, честно говоря, F # - плохой пример для сравнения, это просто слегка наряженный C #; это изменчивый императивный язык, такой же, как C #, он имеет несколько возможностей FP, которых нет в C #, но не так много. Посмотрите на haskell, чтобы увидеть, где FP действительно выделяется, и подобные вещи становятся намного более возможными благодаря классам типов, а также универсальным ADT
Джимми Хоффа
@MattFenwick Я явно ссылаюсь на C #, потому что именно об этом спрашивал автор. Где я ссылаюсь на ООП на протяжении всего моего ответа здесь, я имею в виду C # ООП, я буду редактировать, чтобы уточнить.
Джимми Хоффа
2
Общим признаком среди функциональных языков, допускающих такой тип повторного использования, является динамическая или предполагаемая типизация. В то время как сам язык может использовать четко определенные типы данных, типичная функция не заботится о том, что это за данные, если действительные операции (другие функции или арифметика) над ними выполняются. Это доступно и в парадигмах ОО (например, в Go имеется неявная реализация интерфейса, позволяющая объекту быть уткой, поскольку он может летать, плавать и крякать, без явного объявления объекта как утки), но это в значительной степени реквизит функционального программирования.
KeithS
4
@KeithS. В Haskell по перегрузке на основе значений я думаю, что вы имеете в виду сопоставление с образцом Способность Haskell иметь несколько функций верхнего уровня с одним и тем же именем с различными шаблонами сразу приводит к 1 функции верхнего уровня + совпадению с шаблоном.
Jozefg
6

Почему анемичная модель предметной области считается плохой в C # / OOP, но очень важной в F # / FP?

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

На самом деле, я не мог начать рассказывать вам, как доменные модели выражаются в FP. Другой ответ здесь очень конкретно говорит о том, как это делается в Haskell, и совсем не применим к Lisp (прародителю всех языков FP), семейству языков ML или любым другим функциональным языкам.

почему мы не заимствуем идею разделения данных / поведения и даже не считаем ее плохой?

Обобщения могут рассматриваться как способ разделения данных и поведения. Обобщения происходят от семейства функциональных языков программирования ML, которые не являются частью ООП. У C # есть дженерики, конечно. Таким образом, можно утверждать, что C # постепенно заимствует идею разделения данных и поведения.

Это просто, что определение не соответствует ООП,

Я считаю, что ООП основывается на принципиально иной предпосылке и, следовательно, не дает вам инструментов, необходимых для разделения данных и поведения. Для всех практических целей вам нужен продукт и сумма типов данных и отправка по ним. В ML это означает типы объединения и записи и сопоставление с образцом.

Посмотрите на пример, который я привел здесь .

или есть конкретная причина того, что это плохо в C #, которая по какой-то причине не применима в F # (и на самом деле, наоборот)?

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

(Примечание: меня особенно интересуют различия в C # / F #, которые могут изменить мнение о том, что хорошо / плохо, а не люди, которые могут не согласиться с любым мнением в сообщении в блоге).

Отсутствие типов объединения и сопоставления с образцом в C # делает это практически невозможным. Когда у тебя есть только молоток, все выглядит как гвоздь ...

Джон Харроп
источник
2
... когда все, что у вас есть, это молоток, люди из ООП будут создавать фабрики молотков, P +1 для того, чтобы действительно определить суть того, чего не хватает в C #, чтобы позволить данным и поведению полностью абстрагироваться друг от друга: типы объединения и сопоставление с образцом.
Джимми Хоффа
-4

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

Джакомо Сити
источник
4
Не могли бы вы объяснить немного больше о том, как это относится к объектно-ориентированному программированию по сравнению с функциональным программированием?