Да, я считаю, что вы правильно поняли. По существу, чтобы выполнить LSP, вы должны иметь возможность делать с подтипом все, что вы могли бы делать с супертипом. Именно поэтому проблема эллипса/окружности возникает в LSP. Если у Ellipse есть метод setEccentricity
, а Circle является подклассом Ellipse, а объекты должны быть изменяемыми, то Circle никак не может реализовать метод setEccentricity
. Таким образом, с Эллипсом можно сделать что-то, чего нельзя сделать с Окружностью, так что LSP нарушается. Точно так же есть кое-что, что вы можете сделать с обычным List
, чего нельзя сделать с обернутым Collections.unmodifiableList
, так что это нарушение LSP.
Проблема в том, что здесь есть что-то, что нам нужно (неизменяемый, немодифицируемый, доступный только для чтения список), что не захвачено системой типов. В C# вы можете использовать IEnumerable
, который отражает идею последовательности, которую вы можете повторять и читать, но не записывать. Но в Java есть только List
, который часто используется для изменяемого списка, но который мы иногда хотели бы использовать для неизменяемого списка.
Некоторые могут сказать, что Circle может реализовать setEccentricity
и просто выдать исключение, и аналогичным образом неизменяемый список (или неизменяемый список из Guava) выдает исключение, когда вы пытаетесь его изменить. Но на самом деле это не означает, что это является списком с точки зрения LSP. Во-первых, это как минимум нарушает принцип наименьшей неожиданности. Если вызывающий объект получает неожиданное исключение при попытке добавить элемент в список, это весьма удивительно. И если вызывающему коду необходимо предпринять шаги, чтобы отличить список, который он может изменить, от списка, который он не может (или форму, эксцентриситет которой он может установить, и одну, которую он не может), то одно на самом деле не заменяет другое. .
Было бы лучше, если бы в системе типов Java был тип для последовательности или коллекции, который допускал бы только итерацию, а другой тип допускал бы модификацию. Возможно, для этого можно использовать Iterable, но я подозреваю, что в нем отсутствуют некоторые функции (например, size()
), которые действительно нужны. К сожалению, я думаю, что это ограничение текущего API коллекций Java.
Несколько человек отметили, что документация для Collection
позволяет реализации генерировать исключение из метода add
. Я предполагаю, что это означает, что Список, который не может быть изменен, подчиняется букве закона, когда речь идет о контракте для add
, но я думаю, что нужно изучить свой код и посмотреть, сколько мест есть, которые защищают вызовы мутирующих методов. списка (add
, addAll
, remove
, clear
) с блоками try/catch, прежде чем утверждать, что LSP не нарушен. Возможно, это не так, но это означает, что весь код, вызывающий List.add
в списке, полученном в качестве параметра, неисправен.
Это, конечно, о многом бы сказало.
(Аналогичные аргументы могут показать, что идея о том, что null
является членом каждого типа, также является нарушением принципа подстановки Лискова.)
Я знаю, что есть и другие способы решения проблемы Ellipse/Circle, например сделать их неизменяемыми или удалить метод setEccentricity. Я говорю здесь только о самом распространенном случае, в качестве аналогии.
person
David Conrad
schedule
26.02.2014