Здесь, следуя тому, что говорит LSP, «производный объект» должен использоваться в качестве замены «базового объекта».
Допустим, у вашего базового объекта есть метод:
class BasicAdder
{
Anything Add(Number x, Number y);
}
// example of usage
adder = new BasicAdder
// elsewhere
Anything res = adder.Add( integer1, float2 );
Здесь «Число» — это идея базового типа для числовых типов данных, целых чисел, чисел с плавающей запятой, двойных чисел и т. д. Такой вещи не существует, например, в C++, но мы не обсуждаем здесь конкретный язык. Точно так же, просто для примера, "Anything" отображает неограниченное значение любого типа.
Давайте рассмотрим производный объект, который «специализирован» на использовании комплекса:
class ComplexAdder
{
Complex Add(Complex x, Complex y);
}
// example of usage
adder = new ComplexAdder
// elsewhere
Anything res = adder.Add( integer1, float2 ); // FAIL
следовательно, мы только что сломали LSP: его НЕ можно использовать в качестве замены исходного объекта, потому что он не может принимать integer1, float2
параметров, потому что на самом деле он требует сложных параметров.
С другой стороны, обратите внимание, что ковариантный тип возвращаемого значения подходит: комплексный тип возвращаемого значения будет соответствовать Anything
.
Теперь рассмотрим другой случай:
class SupersetComplexAdder
{
Anything Add(ComplexOrNumberOrShoes x, ComplexOrNumberOrShoes y);
}
// example of usage
adder = new SupersetComplexAdder
// elsewhere
Anything res = adder.Add( integer1, float2 ); // WIN
теперь все в порядке, потому что тот, кто использовал старый объект, теперь также может использовать и новый объект, без каких-либо изменений в точке использования.
Конечно, не всегда возможно создать такой тип "объединения" или "надмножества", особенно с точки зрения чисел или с точки зрения некоторых автоматических преобразований типов. Но тогда мы не говорим о конкретном языке программирования. Общая идея имеет значение.
Также стоит отметить, что вы можете придерживаться или нарушать LSP на разных «уровнях».
class SmartAdder
{
Anything Add(Anything x, Anything y)
{
if(x is not really Complex) throw error;
if(y is not really Complex) throw error;
return complex-add(x,y)
}
}
Это, безусловно, похоже на соответствие LSP на уровне сигнатуры класса/метода. Но так ли это? Часто нет, но это зависит от многих вещей.
Как правило контравариантности помогает в достижении абстракции данных/процедур?
это хорошо.. очевидно для меня. Если вы создаете, скажем, компоненты, которые предназначены для замены/замены/замены:
- БАЗА: вычислить сумму счетов наивно
- DER-1: вычислить сумму счетов-фактур на нескольких ядрах параллельно
- DER-2: вычислить сумму счетов с подробным ведением журнала
а затем добавить новый:
- вычислить сумму счетов в другой валюте
и скажем, он обрабатывает входные значения EUR и GBP. Как насчет ввода в старой валюте, скажем, в долларах США? Если вы опустите это, то новый компонент не будет заменой старых. Вы не можете просто вынуть старый компонент и подключить новый и надеяться, что все в порядке. Все остальные вещи в системе могут по-прежнему отправлять значения в долларах США в качестве входных данных.
Если мы создадим новый компонент как производный от BASE, то каждый должен быть уверен, что может использовать его везде, где ранее требовалась BASE. Если в каком-то месте требовалось БАЗОВОЕ, а использовалось ДЕР-2, то мы должны иметь возможность подключить туда новый компонент. Это ЛСП. Если не можем, значит что-то сломалось:
- любое место использования не требовало только BASE, но на самом деле требовало большего
- или наш компонент действительно не является БАЗОЙ (обратите внимание на формулировку is-a)
Теперь, если ничего не сломано, мы можем взять одно и заменить другим, независимо от того, есть ли в наличии доллары США или фунты стерлингов, одноядерные или многоядерные. Теперь, глядя на общую картину на один уровень выше, если больше не нужно заботиться о конкретных типах валюты, то мы успешно абстрагируем ее, общая картина будет проще, в то время как, конечно, компоненты должны будут внутренне обрабатывать это. как-то.
Если это не похоже на помощь в абстракции данных/процедур, посмотрите на противоположный случай:
Если компонент, полученный из BASE, не соответствует LSP, то он может вызывать ошибки при поступлении допустимых значений в долларах США. Или, что еще хуже, он не заметит и обработает их как GBP. У нас есть проблемы. Чтобы исправить это, нам нужно либо исправить новый компонент (чтобы соответствовать всем требованиям BASE), либо изменить другие соседние компоненты, чтобы они следовали новым правилам, таким как «теперь используйте EUR, а не USD, или сумматор будет генерировать исключения», или нам нужно добавьте элементы в общую картину, чтобы обойти ее, т. е. добавьте несколько ветвей, которые будут обнаруживать данные в старом стиле и перенаправлять их на старые компоненты. Мы просто «слили» сложность соседям (и, возможно, заставили их сломать SRP) или усложнили «общую картину» (больше адаптеров, условий, веток, ..).
31.10.2016
Anything res = adder.Add( integer1, float2 ); // WIN
это может быть правдой, если метод Add имеетNumber
в качестве аргумента в классеSupersetComplexAdder
. Принимая во внимание тот факт, чтоBasicAdder
ясно указывает, что он не ожидает ничего, кроме типа Number или его подтипа, в качестве аргумента в методеAdd
, предоставление супертипа в качестве аргумента в производном классе не дает никаких дополнительных возможностей вызывающей стороне. 31.10.2016BasicAdder
, поскольку теперь код более специфичен дляSupersetComplexAdder
. И это само по себе нарушает LSP дляBasicAdder
. Хотя я согласен с тем, что LSP по-прежнему верен дляSupersetComplexAdder
. 31.10.2016