2016-10-31 12 views
3

Одно из правил, Liskov Substitution Principle навязывает сигнатуры метода в производном классе:Почему Принципу замены Лискова необходимо, чтобы аргумент был контравариантным?

контрвариация метода аргументы в подтипе.

Если я правильно понял, это говорит о том, что функция переопределения производного класса должна допускать контравариантные аргументы (аргументы супертипа). Но я не мог понять причину этого правила. Поскольку LSP говорит в основном о динамической привязке типов с такими подтипами (а не супертипами), чтобы достичь абстракции, поэтому включение супертипов в качестве аргументов метода в производном классе довольно сбивает меня с толку. Мои вопросы:

  • Почему LSP разрешает/требует Контравариантной аргументы в производном классе overridding Funtion?
  • Как правило контравариантности помогает в достижении абстракции данных/процедур?
  • Есть ли какой-нибудь реальный пример, где нам нужно передать контравариантный параметр переопределенному методу производного класса?

ответ

2

Здесь, следуя указаниям LSP, «производный объект» должен использоваться в качестве замены «базового объекта».

Допустим, ваш базовый объект имеет метод:

class BasicAdder 
{ 
    Anything Add(Number x, Number y); 
} 

// example of usage 
adder = new BasicAdder 

// elsewhere 
Anything res = adder.Add(integer1, float2); 

Здесь, «Номер» является идея базового типа для целого ряда подобных типов данных, целые числа, поплавки, удваивается и т.д. Нет такой вещи существует в ie 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 

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

Конечно, не всегда возможно создать такой тип «union» или «superset», особенно в терминах чисел или в терминах некоторых преобразований автоматического типа. Но тогда мы говорим не о конкретном языке программирования. Общая идея имеет значение.

Стоит также отметить, что вы можете придерживаться или разорвать 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: вычислить сумму счетов-фактуры с подробной регистрацией

, а затем добавить новую один:

  • вычислительной сумму invoi ces в другой валюте

и позволяет сказать, что он управляет входными значениями EUR и GBP. Что относительно вкладов в старой валюте, скажем, в долларах США? Если вы опустите это, то новый компонент не является заменой старых. Вы не можете просто вынуть старый компонент и подключить новый, и надеяться, что все в порядке. Все остальные вещи в системе могут по-прежнему отправлять значения доллара в качестве входных данных.

Если мы создадим новый компонент как производный от BASE, то все должны быть в безопасности, чтобы предположить, что они могут использовать его везде, где раньше требовалось BASE. Если в каком-то месте потребовалась база, но использовался DER-2, тогда мы должны были бы подключить новый componeent. Это LSP. Если мы не можем, то что-то нарушается:

  • либо место использования did't требуют только базы, но на самом деле требуется более
  • или наш компонент не не БАЗА (пожалуйста, обратите внимание, что есть-)

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

Если не упали, как помогает в данном/процедуре абстракция, то посмотрите на противоположном случае:

Если компонент, полученный из БАЗЫ не прилипать к LSP, то это может вызывать ошибки, когда значения в законно прибывают доллары США. Или, что еще хуже, он не заметит и будет обрабатывать их как GBP. У нас есть проблемы.Чтобы исправить это, нам нужно либо исправить новый компонент (придерживаться всех требований с BASE), либо изменить другие соседние компоненты, чтобы следовать новым правилам, например «теперь используйте EUR не USD, или Adder будет генерировать исключения», или нам нужно добавьте вещи в общую картину, чтобы работать над ней, например, добавьте некоторые ветви, которые будут обнаруживать старые данные и перенаправлять их на старые компоненты. Мы просто «утекли» сложность для соседей (и, возможно, мы заставили их сломать SRP), или мы сделали «большую картинку» более сложной (больше адаптеров, условий, ветвей, ..).

+0

Благодарим за подробное описание. Однако 'Anything res = adder.Add (integer1, float2); // WIN' это может быть правдой, если метод Add имеет 'Number' в качестве аргумента внутри класса SupersetComplexAdder. Учитывая тот факт, что «BasicAdder» ясно указывает, что он не ожидает ничего, кроме типа Number или его подтипа, в качестве аргумента в методе «Добавить», предоставление супертипа в качестве аргумента в производном классе не дает дополнительного средства для вызывающего. – Mac

+0

Даже если мы допустим, чтобы подтип имел ковариантные аргументы, в этом случае код вызывающего абонента (Клиент) теряет условие замены объекта «SupersetComplexAdder» любым другим подтипом «BasicAdder», поскольку теперь код более специфичен для «SupersetComplexAdder» '. И это само портит LSP для 'BasicAdder'. Хотя я бы согласился с тем, что LSP по-прежнему справедлив для SupersetComplexAdder. – Mac

+0

После повторного чтения ваших ответов у меня есть довольно четкое представление о важности поддержки ковариации в LSP :). Еще раз спасибо. – Mac

3

Фраза «контравариантность аргументов метода» может быть кратким, но она неоднозначна. Давайте использовать это в качестве примера:

class Base { 
    abstract void add(Banana b); 
} 

class Derived { 
    abstract void add(Xxx? x); 
} 

Теперь «контрвариантность метода аргумента» может означать, что Derived.add должен принять любой объект, который имеет тип Banana или супертип, что-то вроде ? super Banana. Это неправильная толкование правила LSP.

Фактическая интерпретация: «Derived.add должна быть объявлена ​​либо с типом Banana, так же, как в Base или каким-либо надтипа Banana, таких как Fruit». Какой супертип вы выбираете, зависит от вас.

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

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

+0

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

+0

Например, вызывающий вызов всегда будет вызывать 'base.callMe (Number num);' или 'base.callMe (Integer int);' но никогда 'base.callMe (Object obj);' – Mac

+0

Так же, как ковариантные типы возврата, это только обслуживает клиентов подтипа. –

 Смежные вопросы

  • Нет связанных вопросов^_^