«Используйте карту вместо класса для представления данных» - Рич Хикки

19

В этом видео Рич Хикки , создатель Clojure, он советует использовать карту для представления данных вместо использования класса для их представления, как это сделано в Java. Я не понимаю, как это может быть лучше, так как как пользователь API может узнать, что такое ключи ввода, если они просто представлены в виде карт.

Пример :

PersonAPI {
    Person addPerson(Person obj);
    Map<String, Object> addPerson(Map<String, Object> personMap);
}

Во второй функции, как пользователь API может узнать, что является входом для создания человека?

Эмиль
источник
Я также хотел бы знать это, и я чувствую, что пример вопроса не совсем отвечает на него.
Сидан
Я знаю, что видел эту дискуссию раньше где-то на SE. Я считаю, что это было в контексте JavaScript, но аргументы были одинаковыми. Не могу найти это все же.
Себастьян Редл
2
Ну, поскольку Clojure - это Лисп, вы должны делать что-то подходящее для Лиспа. когда вы используете Java, код в ... ну Java.
AK_

Ответы:

12

Преувеличенное резюме (ТМ)

Вы получаете несколько вещей.

  • Прототип наследования и клонирования
  • Динамическое добавление новых свойств
  • Сосуществование объектов разных версий (уровней спецификации) одного и того же класса.
    • Объекты, принадлежащие к более поздним версиям (уровням спецификации), будут иметь дополнительные «необязательные» свойства.
  • Самоанализ свойств, старых и новых
  • Самоанализ правил проверки (обсуждается ниже)

Есть один фатальный недостаток.

  • Компилятор не проверяет строки с ошибками для вас.
  • Инструменты автоматического рефакторинга не будут переименовывать имена ключей свойств, если вы не заплатите за причудливые.

Дело в том, что вы можете получить самоанализ, используя ... самоанализ. Это то, что обычно происходит:

  • Включить отражение.
  • Добавьте большую библиотеку самоанализа в свой проект.
  • Отметьте различные методы и свойства объекта с помощью атрибутов или аннотаций.
  • Пусть библиотека самоанализа сделает волшебство.

Другими словами, если вам никогда не нужно взаимодействовать с FP, вам не нужно принимать советы Rich Hickey.

И последнее, но не по значимости (и не самое красивое), хотя использование в Stringкачестве ключа свойства имеет самый простой смысл, вам не нужно использовать Strings. Многие унаследованные системы, в том числе Android ™, широко используют целочисленные идентификаторы во всей структуре для ссылки на классы, свойства, ресурсы и т. Д.

Android является товарным знаком Google Inc.


Вы также можете сделать оба мира счастливыми.

Для мира Java реализуйте методы получения и установки как обычно.

Для мира FP реализуйте

  • Object getPropertyByName(String name)
  • void setPropertyByName(String name, Object value) throws IllegalPropertyChangeException
  • List<String> getPropertyNames()
  • Class<?> getPropertyValueClass(String name)

Внутри этих функций, да, некрасивый код, но есть плагины IDE, которые заполнят это для вас, используя ... умный плагин, который читает ваш код.

С Java стороны все будет так же производительно, как обычно. Они никогда не будут использовать эту уродливую часть кода. Возможно, вы даже захотите скрыть это от Javadoc.

FP сторона мира может написать любой «легкий» код, который они хотят, и они обычно не кричат ​​на вас, что код медленный.


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

В частности, сериализация / десериализация часто требует аналогичного метода.

Просто некоторые общие мысли относительно "карты как объекта".

  1. Вы все еще должны предоставить функцию для проверки такой «карта как объект». Разница в том, что «карта как объект» позволяет использовать более гибкие (менее ограничительные) критерии проверки.
  2. Вы можете легко добавить дополнительные поля к «карте как объекту».
  3. Чтобы указать минимальные требования к действительному объекту, вам необходимо:
    • Перечислите «минимально необходимый» набор ключей, ожидаемых на карте
    • Для каждого ключа, значение которого необходимо проверить, укажите функцию проверки значения
    • Если существуют правила проверки, которые должны проверять несколько значений ключей, укажите это также.
    • В чем выгода? Предоставление спецификации таким способом является интроспективным: вы можете написать программу для запроса минимально необходимого набора ключей и для получения функции проверки для каждого ключа.
    • В ООП все они свернуты в черный ящик под названием «инкапсуляция». Вместо машиночитаемой логики проверки вызывающая сторона может читать только читаемую человеком «документацию по API» (если, к счастью, она существует).
rwong
источник
commonplaceкажется немного сильным для меня. Я имею в виду, что он используется, как вы описываете, но это также одна из тех печально известных / хрупких вещей (таких как массивы байтов или голые указатели), которые библиотеки стараются изо всех сил скрывать.
Теластин
@Telastyn Эта «уродливая голова тысячи змей» обычно возникает на границе связи между двумя системами, где по какой-то причине канал связи или межпроцессный канал не позволяют телепортировать объекты в целости и сохранности. Я предполагаю, что новые методы, такие как Protocol Buffers, почти исключили этот архаичный вариант использования карты как объекта. Могут быть и другие допустимые варианты использования, но я мало что знаю об этом.
rwong
2
Что касается фатальных недостатков, согласен. Но если имена ключей свойств «легко ошибиться» и «трудно реорганизовать» будут храниться, насколько это возможно, в константах или перечислениях , эта проблема исчезнет. Конечно, это ограничивает некоторые возможности расширения :-(.
user949300
Если «один фатальный недостаток» действительно фатален, почему некоторые люди могут использовать его эффективно. Кроме того, классы и статическая типизация являются ортогональными - вы можете определять классы в Clojure, даже если он динамически типизирован.
Натан Дэвис,
@NathanDavis (1) Я признаю, что мой ответ написан с точки зрения статической типизации (C #), и я написал этот ответ, потому что я разделяю ту же точку зрения автора. Я признаю, что мне не хватает FP-ориентированной точки зрения. (2) Добро пожаловать в SE.SE, и, поскольку вы являетесь уважаемой фигурой в Clojure, пожалуйста, уделите время, чтобы написать собственный ответ, если существующие не являются удовлетворительными. Прогнозы снижают репутацию, а новые ответы привлекают негативы, которые быстро складывают репутацию. (3) Я вижу, как «неполные объекты» могут быть полезны - вы можете запросить 2 свойства для данного объекта (имя, аватар) и опустить остальные.
rwong
9

Это отличный разговор того, кто действительно знает, о чем говорит. Я рекомендую читателям посмотреть все это. Это всего 36 минут.

Одним из его основных моментов является то, что простота открывает возможности для изменений в будущем. Выбор класса для представления Personобеспечивает немедленную выгоду от создания статически проверяемого API, как вы указали, но это связано со стоимостью ограничения возможностей или увеличения затрат на изменения и повторное использование в дальнейшем.

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

Ниже приведены некоторые изменения кода (одно или два из которых были упомянуты в докладе), которые потенциально проще использовать список карт по сравнению с использованием списка Personобъектов:

  • Отправка человека на REST-сервер. (Функция, созданная для помещения Mapпримитивов в передаваемый формат, может использоваться многократно и может даже предоставляться в библиотеке. Для Personвыполнения той же задачи объекту, вероятно, потребуется собственный код).
  • Автоматически составить список людей из запроса реляционной базы данных. (Опять же, одна универсальная и многократно используемая функция).
  • Автоматически создавать форму для отображения и редактирования человека.
  • Используйте общие функции для работы с личными данными, которые сильно неоднородны, например, студент или сотрудник.
  • Получить список всех лиц, которые проживают в определенном почтовом индексе.
  • Повторно используйте этот код, чтобы получить список всех предприятий в определенном почтовом индексе.
  • Добавьте специфическое для клиента поле к человеку, не затрагивая других клиентов.

Мы решаем подобные проблемы постоянно, и у нас есть шаблоны и инструменты для них, но редко задумываемся о том, не упростит ли наша работа более простое и гибкое представление данных в начале.

Карл Билефельдт
источник
Есть ли имя для этого? Скажем, сопоставление свойства объекта или сопоставление атрибута объекта (вдоль той же линии, что и ORM)?
Руон
4
Choosing a class to represent a Person provides the immediate benefit of creating a statically-verifiable API... but that comes with the cost of limiting opportunities or increasing costs for change and reuse later on.Неправильно и невероятно неискренне. Это улучшает ваши возможности для внесения изменений позднее, потому что, когда вы вносите критические изменения, компилятор автоматически найдет и укажет для вас все места, которые необходимо обновить, чтобы ускорить работу всей кодовой базы. Именно в динамическом коде, где вы не можете этого сделать, вы действительно привязаны к предыдущему выбору!
Мейсон Уилер
4
@MasonWheeler: Что вы на самом деле говорите, так это то, что вы цените безопасность типов во время компиляции над более динамическими (и более свободно типизированными) структурами данных.
Роберт Харви
1
Полиморфизм не является концепцией, ограниченной ООП. В случае карт у вас может быть инклюзивный полиморфизм (если элементы являются подтипами некоторого типа, который карта может обрабатывать) или специальный полиморфизм (если элементы являются теговыми объединениями). Это внутренние органы. Операции, которые могут быть выполнены на карте, также могут быть полиморфными. Параметрический полиморфизм, когда мы используем функцию более высокого порядка для элементов или ad-hoc при диспетчеризации. Инкапсуляция может быть достигнута с помощью пространств имен или других форм управления видимостью. По сути, изоляция объектов не равна назначению операций типам данных.
Siefca
1
@GillBates, почему ты так говоришь? Вы просто теряете возможность поместить эти виртуальные методы «в карту» - но это именно то, о чем говорит Рич Хикки, «ActiveObjects» на самом деле является анти-паттерном. Вы должны относиться к данным как к тому, что они (данные), а не переплетать их с поведением. Есть огромные преимущества простоты, которые должны быть достигнуты, разделяя проблемы.
Вирджил
4
  • Если у данных мало или нет поведения с гибким содержимым, которое может измениться, используйте карту. IMO, типичный «javabean» или «объект данных», который состоит из анемичной доменной модели с N полями, N установщиками и N получателями, является пустой тратой времени. Не пытайтесь поразить окружающих своей прославленной структурой, обернув ее в причудливый умный класс. Будьте честны, проясните свои намерения и используйте карту. (Или, если это имеет какой-то смысл для вашего домена, объект JSON или XML)

  • Если данные имеют существенное реальное поведение, или методы ( скажи, не спрашивай ), используйте класс. И похлопайте себя по спине за использование реального объектно-ориентированного программирования :-).

  • Если у данных много важных проверочных действий и обязательных полей, используйте класс.

  • Если данные имеют умеренную степень проверки, это граница.

  • Если данные запускают события изменения свойств, это на самом деле проще и гораздо менее утомительно с картой. Просто напишите маленький подкласс.

  • Одним из основных недостатков использования Map является то, что пользователь должен преобразовывать значения в Strings, Ints, Foos и т. Д. Если это очень раздражает и подвержено ошибкам, рассмотрите класс. Или рассмотрите вспомогательный класс, который оборачивает карту соответствующими получателями.

user949300
источник
1
На самом деле Рич Хики утверждает, что если данные имеют существенное реальное поведение ... вы, вероятно, неправильно делаете всю "конструкцию". Данные «информация». Информация в реальном мире НЕ является «местом, где хранятся данные». Информация не имеет «операций, которые контролируют, как информация меняется». Мы не передаем информацию, сообщая людям, где она хранится. Объектно-ориентированные метафоры ИНОГДА являются подходящей моделью мира ... но чаще всего это не так. Вот что он говорит - «думай о своей проблеме». Не все является объектом - мало что есть.
Вирджил
0

API для mapимеет два уровня.

  1. API для карт.
  2. Условные обозначения заявки.

API можно описать на карте условно. Например, пара :api api-validateможет быть размещена на карте или :api-foo validate-fooможет быть условной. Карта может даже хранить api api-documentation-link.

Использование соглашений позволяет программисту создавать предметно-ориентированный язык, который стандартизирует доступ к «типам», реализованным в виде карт. Использование (keys map)позволяет определять свойства во время выполнения.

В картах нет ничего волшебного, в объектах нет ничего волшебного. Это все отправка.

Бен Руджерс
источник