Как написать ImmutableMap, который следует подстановке Лискова и другим принципам SOLID без запаха кода?

Я ответил на вопрос относительно ImmutableMap. Я предложил использовать шаблон Proxy.

Проблема в том, что Map содержит метод put, который выдает UnsupportedOperationException. Замена других экземпляров Map на ImmutableMap нарушит принцип замены Лискова. Мало того, необходимость объявлять put и putAll [нарушает принцип разделения интерфейсов]

Технически нет возможности заменить экземпляр Map экземпляром ImmutableMap, поскольку Map — это просто интерфейс. Итак, мой вопрос:

Будет ли создание ImmutableMap с использованием интерфейса Map считаться нарушением LSP, поскольку Map содержит методы put и putAll? Не будет ли реализация Map считаться запахом кода «Альтернативные классы с разными интерфейсами»? Как можно создать ImmutableMap, который соблюдает LSP, но не содержит запахов кода?


person Dioxin    schedule 25.04.2015    source источник
comment
Map.put и Map.putAll (а также remove и clear) определены как необязательные операции. Также в документации по интерфейсу указана возможность выбрасывания UnsupportedOperationException из реализующих классов.   -  person Mick Mnemonic    schedule 26.04.2015
comment
@MickMnemonic Но это нарушает принцип разделения интерфейса, который гласит, что ни один клиент не должен быть вынужденным зависеть от методов, которые он не использует. Я отредактирую вопрос, указав, что он также должен следовать другим принципам SOLID.   -  person Dioxin    schedule 26.04.2015
comment
Я хотел сказать, что контракты для этих мутирующих методов, по крайней мере, хорошо документированы в интерфейсе Map. Но у вас есть очень веская точка зрения; интерфейс Map толстый. Возможно, изначально его следовало разделить на Map и ModifiableMap extends Map.   -  person Mick Mnemonic    schedule 26.04.2015
comment
Иногда нужно делать то, что нужно. Поскольку Map используется повсеместно, вы просто добавляете неиспользуемые методы. Вы ничего не можете сделать там, не изобретая свою собственную иерархию типов (что вы могли бы сделать, но никто не предоставил бы ее).   -  person eckes    schedule 26.04.2015
comment
См. stackoverflow.com/questions/22050848/ для обсуждения того, нарушают ли методы мутации коллекций LSP.   -  person jaco0646    schedule 03.08.2016
comment
@ jaco0646 Лично это не спорно. Контракт, указанный в javadoc, является дешевым способом обойти другой принцип, который они нарушили: разделение интерфейса (во-первых, List не должно иметь add, поскольку не все List объекты могут быть добавлены). Kotlin — прекрасный пример того, как это должно было быть обработано: разделение метода add на MutableList и сохранение неизменности List. Из-за этого Kotlin фактически сопоставляет коллекции Java со своими собственными коллекциями (при взаимодействии между ними) во время компиляции.   -  person Dioxin    schedule 03.08.2016


Ответы (1)


На мой взгляд, ImmutableMap должен реализовать Map. Было бы плохой идеей не реализовывать Map, поскольку существует множество методов, которые принимают Map в качестве аргумента и используют его только для чтения. Я не думаю, что это нарушает принцип замещения Лискова, потому что в контракте для Map ясно указано, что put — необязательная операция.

Не идеально, чтобы классы, реализующие Map, должны были реализовывать put, но в качестве альтернативы можно было бы иметь сложную иерархию интерфейсов, каждый из которых включал бы только подмножество возможных необязательных методов. Если есть n необязательных методов, то должно быть 2^n интерфейсов для охвата всех комбинаций. Я не знаю значение n, но есть некоторые неочевидные необязательные операции, например, поддерживает ли Iterator, возвращаемое map.entrySet().iterator(), операцию setValue. Если вы объедините эту иерархию с уже существующей иерархией интерфейсов и абстрактных классов (включая AbstractMap, SortedMap, NavigableMap, ConcurrentMap, ConcurrentNavigableMap...), вы получите полный беспорядок.

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

person Paul Boddington    schedule 26.04.2015
comment
Иерархия интерфейсов будет соответствовать принципу разделения интерфейсов; Я бы предпочел много интерфейсов одному громоздкому. Я могу вспомнить несколько случаев, когда Map мутировал, но вы правы; в большинстве случаев речь идет только о доступе (по крайней мере, в обычных для меня ситуациях). Не буду врать, я уже придумал такой ответ; Я действительно надеялся найти какой-то скрытый драгоценный камень, который мог бы помочь мне решить эту ситуацию. Я подожду еще немного ответов, прежде чем принять; Я надеюсь, вы понимаете. Если ничего не появится, вы получите мой голос за ваше успокаивающее заявление :) - person Dioxin; 26.04.2015