Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка для КР по ООП.doc
Скачиваний:
8
Добавлен:
18.04.2019
Размер:
2.47 Mб
Скачать

Виртуальные функции. Принцип полиморфизма

Основа реализации принципа полиморфизма – наследование. Ссылка на объект базового класса, настроенная на объект производного может обеспечить выполнение методов ПРОИЗВОДНОГО класса, которые НЕ БЫЛИ ОБЪЯВЛЕНЫ В БАЗОВОМ КЛАССЕ. При реализации принципа полиморфизма происходят вещи, которые не укладываются в ранее описанную схему.

Одноимённые функции с одинаковой сигнатурой в базовом и производном классах: между ними может быть установлено отношение замещения. Замещаемая функция базового класса должна при этом дополнительно специфицироваться спецификатором virtual.

Отношение замещения между функциями базового и производного класса устанавливается, если соответствующая (одноименная функция с соответствующей сигнатурой) функция производного класса специфицируется дополнительным спецификатором override.

Введём следующие обозначения и построим схему наследования.

Спецификация и заголовок функции- члена

Уровень наследования

Обозначение

public void F(){ }

0

public virtual void F() { }

0

new public virtual void F() { }

1..N

new public void F() { }

1..N

public override void F() { }

1..N

Схема возможных вариантов объявления методов в иерархии наследования трёх уровней.

Наконец, замечательный пример,

using System;

namespace Implementing

{

class A

{

public virtual void F() { Console.WriteLine(“A”);}

}

class B:A

{

public override void F() { Console.WriteLine(“B”); }

}

class C:B

{

new public virtual void F() { Console.WriteLine(“C”); }

}

class D:C

{

public override void F() { Console.WriteLine(“D”); }

}

class Starter

{

static void Main(string[] args)

{

D d = new D();

C c = d;

B b = c;

A a = b;

d.F(); /* D */

c.F(); /* D */

b.F(); /* B */

a.F(); /* B */

}

}

}

в котором становится ВСЁ понятно, если ПОДРОБНО нарисовать схемы классов, структуру объекта-представителя класса D и посмотреть, КАКАЯ ссылка ЗА КАКОЕ место этот самый объект удерживает.

Интерфейсы

Фактически это те же самые абстрактные классы, НЕ СОДЕРЖАЩИЕ объявлений данных – членов и объявлений ОБЫЧНЫХ функций.

Все без исключения функции-члены интерфейса – абстрактные. Поэтому интерфейс объявляется с особым ключевым словом interface, а функции интерфейса, несмотря на свою “абстрактность” объявляются без ключевого слова abstract.

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

using System;

namespace Interface01

{

// Интерфейсы.

// Этот интерфейс характеризуется уникальными

// именами объявленных в нём методов.

interface Ix

{

void IxF0(int xKey);

void IxF1();

}

// Пара интерфейсов, содержащих объявления одноимённых методов

// с одной и той же сигнатурой.

interface Iy

{

void F0(int xKey);

void F1();

}

interface Iz

{

void F0(int xKey);

void F1();

}

// А этому интерфейсу уделим особое внимание.

// Он содержит тот же набор методов, но в производном классе этот интерфейс

// будет реализован явным образом.

interface Iw

{

void F0(int xKey);

void F1();

}

// В классе TestClass наследуются интерфейсы...

class TestClass:Ix

,Iy

,Iz

,Iw

{

public int xVal;

// Конструкторы.

public TestClass()

{

xVal = 125;

}

public TestClass(int key)

{

xVal = key;

}

// Реализация функций интерфейса Ix.

// Этот интерфейс имеет специфические названия функций.

// В данном пространстве имён его реализация неявная и однозначная.

public void IxF0(int key)

{

xVal = key*5;

Console.WriteLine("IxF0({0})...", xVal);

}

public void IxF1()

{

xVal = xVal*5;

Console.WriteLine("IxF1({0})...", xVal);

}

// Реализация интерфейсов Iy и Iz в классе TestClass неразличима.

// Это неявная неоднозначная реализация интерфейсов.

// Однако, неважно, чью конкретно функцию реализуем. Оба интерфейса довольны...

public void F0(int xKey)

{

xVal = (int)xKey/5;

Console.WriteLine("(Iy/Iz)F0({0})...", xVal);

}

public void F1()

{

xVal = xVal/5;

Console.WriteLine("(Iy/Iz)F1({0})...", xVal);

}

// А это явная непосредственная реализация интерфейса Iw.

// Таким образом, класс TestClass содержит ТРИ варианта реализации функций интерфейсов

// с одной и той же сигнатутой. Два варианта реализации неразличимы. Третий

// (фактически второй) вариант реализации отличается квалифицированными именами.

void Iw.F0(int xKey)

{

xVal = xKey+5;

Console.WriteLine("Iw.F0({0})...", xVal);

}

void Iw.F1()

{

xVal = xVal-5;

Console.WriteLine("Iw.F1({0})...", xVal);

}

public void bF0()

{

Console.WriteLine("bF0()...");

}

}

class Class1

{

static void Main(string[] args)

{

TestClass x0 = new TestClass();

TestClass x1 = new TestClass(5);

x0.bF0();

// Эти методы представляют собой неявную ОДНОЗНАЧНУЮ реализацию

// интерфейса Ix.

x0.IxF0(10);

x1.IxF1();

// Эти методы представляют собой неявную НЕОДНОЗНАЧНУЮ реализацию

// интерфейсов Iy и Iz.

x0.F0(5);

x1.F1();

// А вот вызов функций с явным приведением к типу интерфейса.

// Собственный метод класса bF0() при подобных преобразованиях

// не виден.

(x0 as Iy).F0(7);

(x1 as Iz).F1();

// А теперь настраиваем ссылки различных типов интерфейсов

// на ОДИН И ТОТ ЖЕ объект-представитель класса TestClass.

// И через "призму" интерфейса всякий раз объект будет

// выглядеть по-разному.

Console.WriteLine("==========Prism test==========");

Console.WriteLine("==========Ix==========");

Ix ix = x1;

ix.IxF0(5);

ix.IxF1();

Console.WriteLine("==========Iy==========");

Iy iy = x1;

iy.F0(5);

iy.F1();

Console.WriteLine("==========Iz==========");

Iz iz = x1;

iz.F0(5);

iz.F1();

Console.WriteLine("==========Iw==========");

Iw iw = x1;

iw.F0(10);

iw.F1();

}

}

}

Преимущества программирования с использованием интерфейсов проявляются в том случае, когда ОДНИ И ТЕ ЖЕ ИНТЕРФЕЙСЫ наследуются РАЗНЫМИ классами. И здесь всё определяется спецификой данной конкретной реализации.

using System;

namespace Interface02

{

// Объявляются два интерфейса, каждый из которых содержит объявление

// одноименного метода с единственным параметром соответствующего типа.

interface ICompare0

{

bool Eq(ICompare0 obj);

}

interface ICompare1

{

bool Eq(ICompare1 obj);

}

// Объявляются классы, наследующие оба интерфейса.

// В каждом из классов реализуются функции интерфейсов.

// В силу того, что объявленные в интерфейсах методы одноименные,

// в классах применяется явная реализация методов интерфейсов.

//_______________________________________________________________________.

class C0:ICompare0,ICompare1

{

public int commonVal;

int valC0;

public C0(int commonKey, int key)

{

commonVal = commonKey;

valC0 = key;

}

// Метод реализуется для обеспечения сравнения объектов СТРОГО одного типа - C0.

bool ICompare0.Eq(ICompare0 obj)

{

C0 test = obj as C0;

if (test == null) return false;

if (this.valC0 == test.valC0)

return true;

else

return false;

}

// Метод реализуется для обеспечения сравнения объектов разного типа.

bool ICompare1.Eq(ICompare1 obj)

{

C1 test = obj as C1;

if (test == null) return false;

if (this.commonVal == test.commonVal)

return true;

else

return false;

}

}

//_______________________________________________________________________.

class C1:ICompare0,ICompare1

{

public int commonVal;

string valC1;

public C1(int commonKey, string key)

{

commonVal = commonKey;

valC1 = string.Copy(key);

}

// В классе C1 при реализации функции интерфейса ICompare0 реализован

// метод сравнения, который обеспечивает сравнение как объектов типа C1,

// так и объектов типа C0.

bool ICompare0.Eq(ICompare0 obj)

{

C1 test;

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

// В случае успеха - сравнение объектов по специфическому признаку,

// который для данного класса представлен строковой переменной valC1.

// В случае неуспеха приведения (очевидно, что сравниваются объекты разного типа)

// предпринимается попытка по второму сценарию (сравнение объектов разных типов).

// Таким образом, в рамках метода, реализующего один интерфейс,

// используется метод второго интерфейса. Разумеется, при явном приведении

// аргумента к типу второго интерфейса.

test = obj as C1;

if (test == null)

return ((ICompare1)this).Eq((ICompare1)obj);

if (this.valC1.Equals(test.valC1))

return true;

else

return false;

}

// Метод реализуется для обеспечения сравнения объектов разного типа.

bool ICompare1.Eq(ICompare1 obj)

{

C0 test = obj as C0;

if (test == null) return false;

if (this.commonVal == test.commonVal)

return true;

else

return false;

}

}

//===================================================================

// Место, где порождаются и сравниваются объекты.

class Class1

{

static void Main(string[] args)

{

C0 x1 = new C0(0,1);

C0 x2 = new C0(1,1);

// В выражениях вызова функций-членов интерфейсов НЕ ТРЕБУЕТСЯ явного

// приведения значения параметра к типу интерфейса.

// Сравнение объектов-представителей одного класса (C0).

if ((x1 as ICompare0).Eq(x2))

Console.WriteLine("Yes!");

else

Console.WriteLine("No!");

C1 y1 = new C1(0,"1");

C1 y2 = new C1(1,"1");

// Сравнение объектов-представителей одного класса (C1).

if ((y1 as ICompare0).Eq(y2))

Console.WriteLine("Yes!");

else

Console.WriteLine("No!");

// Попытка сравнения объектов-представителей разных классов.

if (((ICompare0)x1).Eq(y2))

Console.WriteLine("Yes!");

else

Console.WriteLine("No!");

if ((x1 as ICompare1).Eq(y2))

Console.WriteLine("Yes!");

else

Console.WriteLine("No!");

if (((ICompare1)y2).Eq(x2))

Console.WriteLine("Yes!");

else

Console.WriteLine("No!");

// Здесь будут задействован метод сравнения, реализованный

// в классе C0 по интерфейсу ICompare0, который не является универсальным.

// Отрицательный результат может быть получен не только по причине неравенства

// значений сравниваемых величин, но и по причине несоответствия типов операндов.

Console.WriteLine("__________x2==y2__________");

if ((x2 as ICompare0).Eq(y2))

Console.WriteLine("Yes!");

else

Console.WriteLine("No!");

// А здесь вероятность положительного результата выше, поскольку в классе

// C1 метод интерфейса ICompare0 реализован как УНИВЕРСАЛЬНЫЙ. И это значит,

// что данный метод никогда не вернёт отрицательного значения по причине

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

Console.WriteLine("__________y2==x2__________");

if ((y2 as ICompare0).Eq(x2))

Console.WriteLine("Yes!");

else

Console.WriteLine("No!");

}

}

}