Нарушает ли метод Array.Add LSP?

Array класс реализует IList интерфейс, который имеет Add член. Array.Add вызов выдает NotSupportedException. Является ли это нарушением принципа замещения Лисков или принципа разделения интерфейса или обоих?


person Alex Zaitsev    schedule 29.09.2017    source источник
comment
Включение метода в интерфейс, который разработчик может не захотеть реализовывать, является хорошим признаком того, что вы нарушаете правила интернет-провайдера, да. Однако не вижу, какое это имеет отношение к LSP.   -  person thisextendsthat    schedule 29.09.2017
comment
Мне кажется, что это также воля LSP, поскольку, если код ожидает ICollection и получает Array, он, похоже, терпит неудачу на Add (заявлено OP)   -  person Stefan    schedule 29.09.2017
comment
Это должно быть IList.Add вместо ICollection.Add, в то время как NotSupportedException является ожидаемым исключением IList.Add согласно msdn.microsoft.com/en-us/library/. Я не думаю, что нарушают LSP.   -  person qxg    schedule 29.09.2017
comment
@qxg, спасибо, я изменил описание   -  person Alex Zaitsev    schedule 29.09.2017
comment
В BCL есть много классов, нарушающих как LSP, так и ISP. Это сложная часть разработки повторно используемой библиотеки классов, которая должна использоваться для стольких различных вариантов использования, при этом пытаясь сохранить интуитивно понятный API для работы. Здесь задействовано больше факторов, чем просто принципы SOLID. Руководство по проектированию фреймворка дает несколько полезных советов по этому поводу. .   -  person Steven    schedule 29.09.2017
comment
Это нарушает LSP, потому что вы не можете свободно передать массив методу, принимающему IList, и вызывать IList.Add(). Из определения в Википедии: Liskov's notion of a behavioral subtype defines a notion of substitutability for objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g. correctness). Очевидно, что если вы передадите массив и метод попытается вызвать .Add(), это нарушит корректность программы. Документирование исключения не делает его ОК.   -  person Matthew Watson    schedule 29.09.2017
comment
Массивы слабо типизированы в .NET, многие считают поддержку ковариантности ошибкой проектирования. К счастью, в этом виновата Java. С такой отметиной массивы, реализующие IList и не имеющие подходящего для массивов типа интерфейса, являются лишь второстепенным следующим шагом. Юзабилити иногда превосходит чистоту.   -  person Hans Passant    schedule 29.09.2017


Ответы (2)


Это определенно нарушение принципа разделения интерфейса. Интернет-провайдер утверждает, что компоненты должны зависеть только от тех интерфейсов, которые им действительно требуются. Array реализует IList, но явно не нуждается во всех методах, определенных IList, потому что он выбирает исключение, объясняющее, что он не поддерживает Add. Кроме того, все, что нуждается в Array, теперь имеет IList, несмотря на то, что не нуждается во всех его методах (потому что все, что требует массив, явно не беспокоится о Add, так как он все равно не работает). Отсутствие поддержки операции, которую ваш интерфейс предлагает вам реализовать, явно является нарушением интернет-провайдера; принуждение потребляющего кода к зависимости от интерфейса, несмотря на то, что он не нужен полностью, также является нарушением ISP.

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

Является ли это нарушением LSP, зависит от того, как вы видите исключения в отношении LSP. Если подойти к этому с точки зрения того, что исключения являются частью ожидаемого контракта, то все в порядке. Метод соответствует своему контракту, и любой код, который вызывает Add без учета возможности исключения, виноват в том, что этого не происходит.

Если вы больше придерживаетесь мнения, что NotSupportedException считается ошибкой или сбоем, то это явное нарушение LSP. Лично я считаю, что принципы SOLID поддерживают друг друга и их сложнее решать в вакууме, поэтому, используя принцип сегрегации интерфейса в качестве резервной копии, я бы сказал, что интерфейс говорит: «Я могу делать эти вещи», поэтому говоря: «На самом деле, Я не могу сделать это" является ошибкой, и поэтому, вероятно, не должно рассматриваться как часть действительного контракта. Если исключение не является частью контракта, то, выбрасывая его, Array нарушает контракт Add и, следовательно, нарушает LSP.

person anaximander    schedule 29.09.2017
comment
AFAIK IList<T> реализован явно в массивах, поэтому вы не получаете методы по умолчанию, если не выполняете приведение к интерфейсу. В остальном массив действует аналогично неизменяемому списку. Возможно, это компромисс из-за отсутствия интерфейса IReadonlyList, который просто разрешает индексированный доступ для чтения (хотя массивы также разрешают индексированный доступ для записи), сохраняя при этом приятный язык в использовании. - person Joey; 29.09.2017
comment
В документации IList.Add указано, что NotSupportedException является ожидаемым исключением, а также перечислены условия для его создания. Вот почему, по моему скромному мнению, это не лисков-нарушение. LSP заявляет, что он должен работать правильно при передаче любой реализации IList — здесь создание исключения NotSupportedException включено в функцию корректно. - person Fildor; 29.09.2017
comment
@Fildor: я не согласен. Заявление о том, что что-то не поддерживается, хотя и оправдано, приведет к другому поведению с точки зрения программы. Это приведет к отсутствующей функциональности Add, в чем, если я правильно понял, и заключается LSP. - person Stefan; 29.09.2017
comment
@Stefan Тогда у нас может быть другое понимание Лискова. Насколько я знаю, это зависит от контракта Адда. Если ожидается, что он не будет поддерживаться, то импл. то что не поддерживает это лисков-ок, ИМХО. Если бы контракт Адда не включал это, то, конечно, это было бы нарушением. Однако я могу ошибаться. - person Fildor; 29.09.2017
comment
Это НЕ нормально. Принцип подстановки Лисков представлен в терминах математической логики, которая не допускает исключений из-за плохо определенной документации. - person Matthew Watson; 29.09.2017
comment
Я добавил немного больше к своему ответу, чтобы прояснить свое мнение по вопросам, поднятым в этих комментариях. - person anaximander; 29.09.2017
comment
@MatthewWatson Насколько я знаю, речь идет о семантике. Если неподдерживаемость является частью семантики, то реализации подтипа, которые ее не поддерживают, являются семантически правильными. Реализации, делающие что-то отличное от добавления или не поддерживающие добавление, будут нарушением. Таким образом, запрещение исключения было бы нарушением Лискова в этом случае (потому что это усилило бы предварительное условие, имхо). - person Fildor; 29.09.2017
comment
Исключения Исключение Условие NotSupportedException Список IList доступен только для чтения. -или- IList имеет фиксированный размер. Тем не менее, ваша точка зрения на документацию верна. Я просто рассмотрел документацию, чтобы отразить фактический контракт. О последнем, собственно, и следует говорить. - person Fildor; 29.09.2017
comment
@Fildor О да, не заметил этого! Но из-за этого это означает, что никогда не гарантируется безопасность вызова .Add(), что фактически означает, что любой метод, который вызывает .Add() через IList, является несчастным случаем, ожидающим своего часа. Таким образом, передача массива методу, который принимает IList(), который явно не документирует, что он не вызывает .Add(), будет неправильным — вот почему такое нарушение — это так плохо. - person Matthew Watson; 29.09.2017
comment
@MatthewWatson Я согласен. Это совершенно неудобно, и я всегда задавался вопросом, почему на самом деле массивы реализуют IList ... но, я думаю, это другая история. - person Fildor; 29.09.2017
comment
Это еще хуже, потому что вы можете подумать, что можете использовать хотя бы list.IsReadOnly, чтобы определить, не может ли список вызываться .Add(), но, к сожалению, это list.IsReadOnly ложно для типов массивов. Если вы вызовете следующий метод и передадите ему массив, он вернет false: public static bool IsreadOnly(IList list) { return list.IsReadOnly; } - person Matthew Watson; 29.09.2017
comment
@MatthewWatson Абсолютно. Я думаю, мы могли бы написать целую книгу о таких ловушках. - person Fildor; 29.09.2017

Принцип подстановки Лисков

Программу, использующую интерфейс, не следует путать с реализацией этого интерфейса.

Массивы составляют исключение в случае реализации IList‹› — что запутано.

void Main() 
{ 
    var arr = new int[]{1,2,3}; 

    Test(arr);  
    
    void Test(IList<int> list)
    {
        list.Add(4);
    }
}
person robert    schedule 25.03.2021