Студопедия

КАТЕГОРИИ:

АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция

Правила наследования типов. Принцип «Liskov Substitution Principle».




Принцип замещения Лисков

Формулировка №1: eсли для каждого объекта o1 типа S существует объект o2 типа T, который для всех программ P определен в терминах T, то поведение P не измениться, если o1 заменить на o2 при условии, что S является подтипом T.

Формулировка №2: подтипы должны быть заменяемы базовыми типами.

Примеры

Проверка абстракции на тип

Я уже приводил код проверки абстракции на тип на примере нарушения принципа открытости/закрытости. Теперь мы видим, что класс Repository нарушает еще и принцип замещения Лисков. Дело в том, что внутри класса Repository мы оперируем не только абстрактной сущностью AbstractEntity, но и унаследованными типами. А это значит, что в данном случае подтипы AccountEntity и RoleEntity не могут быть заменены типом, от которого они унаследованы. По определению имеем нарушение.

Надо заметить, что принципы проектирования взаимосвязаны. Нарушение одного из принципов скорее всего приведет к нарушению одного или нескольких других принципов.

Ошибочное наследование

Проблема

Мы хотим реализовать свой список с интерфейсом IList. Его особенностью будет то, что все записи в нем дублируются.

1: public class DoubleList<T> : IList<T>

2: {

3:     private readonly IList<T> innerList = new List<T>();

4:  

5:     public void Add(T item)

6:     {

7:         innerList.Add(item);

8:         innerList.Add(item);

9:     }

10:  

11:     ... 

Данная реализация не представляет никакой опасности, если рассматривать ее изолированно. Взглянем на использование этого класса с точки зрения клиента. Клиент, абстрагируясь от реализаций, пытается работать со всеми объектами типа IList одинаково:

1: [Fact]

2: public void CheckBehaviourForRegularList()

3: {

4:     IList<int> list = new List<int>();

5:  

6:     list.Add(1);

7:  

8:     Assert.Equal(1, list.Count);

9: }

10:  

11: [Fact]

12: public void CheckBehaviourForDoubleList()

13: {

14:     IList<int> list = new DoubleList<int>();

15:  

16:     list.Add(1);

17:  

18:     Assert.Equal(1, list.Count); // fail

19: }

Поведение списка DoubleList отличается от типичных реализаций IList. Получается, что наш DoubleList не может быть заменен базовым типом. Это и есть нарушение принципа замещения Лисков.

Проблема заключается в том, что теперь клиенту необходимо знать о конкретном типе объекта, реализующем интерфейс IList. В качестве такого объекта могут передать и DoubleList, а для него придется выполнять дополнительную логику и проверки.

Решение

Правильным решением будет использовать свой собственный интерфейс, например, IDoubleList. Этот интерфейс будет объявлять для пользователей поведение, при котором добавляемые элементы удваиваются.

Проектирование по контракту

Есть формальный способ понять, что наследование является ошибочным. Это можно сделать с помощью проектирования по контракту. Бернард Мейер, его автор, сформулировал следующий принцип:

Наследуемый объект может заменить родительское пред-условие на такое же или более слабое и родительское пост-условие на такое же или более сильное. (перефразировано)

Рассмотрим пред- и пост-условия для интерфейса IList. Для функции Add:

пред-условие: item != null

пост-условие: count = oldCount + 1

Для нашего DoubleList и его функции Add:

пред-условие: item != null

пост-условие: count = oldCount + 2

Теперь стало видно, что по контракту пост-условие базового класса не выполняется.

Другими словами, когда мы используем интерфейс IList, то как пользователи этого базового класса знаем только его пред- и пост-условия. Нарушая принцип проектирования по контракту мы меняем поведение унаследованного объекта.

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

 


15. C#. Приведение типов. Операторы as и is.

 

Динамическая идентификация типов (runtime type identification — RTTI) позволяет определить тип объекта во время выполнения программы, что необходимо во многих ситуациях. Например, можно совершенно точно узнать, на объект какого типа в дей-ствительности указывает ссылка на базовый класс. Еще одно применение RTTI — за-ранее проверить, удачно ли будет выполнена операция приведения типа, не допустив возникновения исключения, связанного с некорректно заданной операцией приведе-ния типа. Динамическая идентификация типов также является ключевым компонен-

том средства отражения (информации о типе).

В С# предусмотрено три ключевых слова, которые поддерживают динамическую идентификацию типов: i s , as и typeof. Рассмотрим назначение каждого из них в отдельности.


Проверка типа с помощью ключевого слова is

С помощью оператора i s можно определить, имеет ли рассматриваемый объект заданный тип. Общая форма его записи имеет следующий вид:

выражение i s ТИП

Здесь тип элемента выражение сравнивается с элементом ТИП. ЕСЛИ ТИП элемента выражение совпадает (или совместим) с элементом ТИП, результат выполнения опера-ции принимается равным значению ИСТИНА. В противном случае — значению ЛОЖЬ. Следовательно, если результат истинен, выражение можно привести к типу,

заданному элементом ТИП.

Рассмотрим пример использования оператора i s .

// Демонстрация выполнения оператора i s .

using System;

class A {}

class В : A {}

class Usels {

public static void Main() {

A a = new A () ;

В b = new В();

if(a is A) Console.WriteLine("Объект а имеет тип A.");

if(b is A)

Console.WriteLine("Объект b совместим с типом А, " +

"поскольку его тип выведен из типа А.");

if(a is В)

Console.WriteLine("Этот текст не будет отображен, " +

"поскольку объект а не выведен из класса В.")

if(b is В) Console.WriteLine("Объект b имеет тип В.");

if(a is object) Console.WriteLine("а — это объект.");

Результаты выполнения этой программы таковы:

Объект а имеет тип А.

Объект b совместим с типом А, поскольку его тип выведен из типа А.

Объект b имеет тип В.

а -- это объект.

Несмотря на то что все сообщения в этой программе говорят сами за себя, некото- fbie из них все же требуют пояснений. Обратите внимание на следующую инструкцию:

i f ( b i s A)

Console.WriteLine("Объект b совместим с типом А, " +

"поскольку его тип выведен из типа А.");

В данном случае i f-инструкция выполнилась успешно, поскольку переменная b является ссылкой типа в, который выведен из типа А. Следовательно, объект b со-вместим с типом А. Однако обратное утверждение не является справедливым. При выполнении строки кода I i f ( a is В)

Console.WriteLine("Этот текст не будет отображен, " + "поскольку объект а не выведен из класса В.");

if-инструкция успехом не увенчается, поскольку объект а имеет тип А, который не выведен из типа в. Следовательно, объект а и класс в несовместимы по типу.

Использование оператора as

Иногда во время работы программы требуется выполнить операцию приведения типов, не генерируя исключение в случае, если попытка окажется неудачной. Для этого предусмотрен оператор as, формат которого таков:

выражение as тип

Нетрудно догадаться, что используемый здесь элемент выражение участвует в по-пытке приведения его к типу, заданному элементом тип. В случае успешного выпол-нения этой операции возвращается ссылка на тип. В противном случае возвращаетсянулевая ссылка.

Оператор as в некоторых случаях предлагает удобную альтернативу оператору i s .

Рассмотрим, например, следующую программу, в которой оператор i s позволяет пре-дотвратить неверное приведение типов:

// Использование оператора i s для предотвращения

// неверной операции приведения типов.

using System;

class A {}

class В : А {}

class CheckCast {

public static void Main() {

A a = new A();

В b = new В();

// Проверяем, можно ли объект а привести к типу В.

if(a is В) // При положительном результате выполняем

// операцию приведения типов,

b = (В) а;

else // В противном случае операция приведения

// типов опускается,

b = null;

if(b==null)

Console.WriteLine(

"Операция приведения типов b = (В) а НЕ РАЗРЕШЕНА.");

else

Console.WriteLine(

"Операция приведения типов b = (В) а разрешена.")

Результаты выполнения этой программы таковы:

Операция приведения b = (В) а НЕ РАЗРЕШЕНА.

Как подтверждают эти результаты, поскольку тип объекта а не совместим с типом в, операция приведения объекта а к типу в недопустима, и ее выполнение предот-вращается с помощью инструкции if. Как видите, реализация такого подхода требует выполнения двух этапов. Первый состоит в подтверждении обоснованности операции приведения типов, а второй — в самом ее выполнении. С помощью оператора as эти два этапа можно объединить в один, как показано в следующей программе.

// Демонстрация использования оператора as.

using System;

class A {}

class В : A {}

class CheckCast {

public static void Main() {

A a = new A();

В b = new В() ;

b = a as В; // Выполняем операцию приведения типов,

// если она возможна.

if(b==null)

Console.WriteLine("Операция приведения типов " +

"b = (В) а НЕ РАЗРЕШЕНА.");

else

Console.WriteLine(

"Операция приведения типов b = (В) а разрешена.");

Вот результаты выполнения этой программы:

Операция приведения типов b = (В) а НЕ РАЗРЕШЕНА.

В этой версии оператор as проверяет допустимость операции приведения типов, а затем, если она законна, выполняет ее, причем все это реализуется в одной инструкции.










Последнее изменение этой страницы: 2018-05-27; просмотров: 186.

stydopedya.ru не претендует на авторское право материалов, которые вылажены, но предоставляет бесплатный доступ к ним. В случае нарушения авторского права или персональных данных напишите сюда...