Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Domnin_Lab_9-12 / ДОКУМЕНТЫ_9-12 / ЛАБ_10_C# / Виртуальные_методы_и_абстрактные классы

.doc
Скачиваний:
15
Добавлен:
02.02.2015
Размер:
181.76 Кб
Скачать

Лаб. раб. №10

ВИРТУАЛЬНЫЕ МЕТОДЫ И АБСТРАКТНЫЕ КЛАССЫ

Виртуальный метод (VM) – метод, объявленный в базовом классе с применением ключегого слова virtual и переопределенный во множестве производных классов, каждый из которых имеет свою версию виртуального метода. Вызов соответствующего виртуального метода выполняется ссылкой на базовый класс и определяется типом объекта, на который указывает эта ссылка. Обращение к функции выполняется во время выпошнения программы, динамически. Тип объекта ( а не тип ссылки ), на который указывает ссылка, определяет выбор выполняемого виртуального метода. Процесс переопределения виртуального метода в производном классе (замещенного метода) сопровождается применением модификатора override. При этом:

- сигнатуры VM и метода-заменителя должны совпадать;

- не допускется определять VM как static или abstract.

Переопределение VM организует динамическую диспетчеризацию методов (динамический полиморфизм) – вызова переопределенного метода во время выполнения программы, а не при компиляции. Ниже приводится пример программы динамического полиморфизма.

1

// Виртуальные методы и их переопределение

using System;

class Base {

// Создание VM в базовом классе

public virtual void dfa()

{

Console.WriteLine("\n Метод dfa() в классе Base ");

}

}

// Первый производный класс D1

class D1 : Base {

// Переопределение метода dfa()в классе D1.

public override void dfa()

{

Console.WriteLine(" Метод dfa() в классе D1 ");

}

}

// Второй производный класс D2

class D2 : Base {

// Переопределение метода dfa()в классе D2.

public override void dfa()

{

Console.WriteLine(" Метод dfa() в классе D2 ");

}

}

// Третий производный класс D3

class D3 : Base {

// Переопределение метода dfa()в классе D3.

public override void dfa()

{

Console.WriteLine(" Метод dfa() в классе D3 \n\n\n ");

}

}

class Overridedfa

{

public static void Main()

{

Base baseOb = new Base();

D1 dOb1 = new D1();

D2 dOb2 = new D2();

D3 dOb3 = new D3();

Base baseRef; // Ссылка на базовый класс

baseRef = baseOb;

baseRef.dfa();

baseRef = dOb1;

baseRef.dfa();

baseRef = dOb2;

baseRef.dfa();

baseRef = dOb3;

baseRef.dfa();

}

}

Рис 1 Работа с виртуальными методами и их переопределение

Результаты работы программы

1 Создаются базовый класс Base и три производных.

2 В классе Base объявлен метод dfa(), который переопределяется производными классами.

3 В методе Main() объявлены объекты типа Base, D1, D2, D3 и ссылка baseRef типа Base.

4 Поочередно присваиваются ссылке baseRef ссылки на объект каждого типа и по ссылке baseRef вызывается соответствующий метод dfa(). Как следует нужная версия dfa() определяется типом объекта, адресуемого в момент вызова.

Рассмотрим ситуацию, когда виртуальный метод можно не переопределять – используется версия, определеная в базовом классе

№ 2

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

using System;

class Base

{ // Создание VM в базовом классе

public virtual void dfa()

{ Console.WriteLine("\n Метод dfa() в классе Base ");

}

}

// Первый производный класс D1

class D1 : Base

{ // Переопределение метода dfa()в классе D1.

public override void dfa()

{ Console.WriteLine(" Метод dfa() в классе D1 ");

}

}

// Второй производный класс D2

class D2 : Base

{

// Этот производный класс D2 не переопределяет метод dfa().

}

// Третий производный класс D3

class D3 : Base

{

// Переопределение метода dfa()в классе D3.

public override void dfa()

{

Console.WriteLine(" Метод dfa() в классе D3 \n\n\n ");

}

}

class NoOverridedfa1

{

public static void Main()

{

Base baseOb = new Base();

D1 dOb1 = new D1();

D2 dOb2 = new D2();

D3 dOb3 = new D3();

Base baseRef; // Ссылка на базовый класс

baseRef = baseOb;

baseRef.dfa();

baseRef = dOb1;

baseRef.dfa();

baseRef = dOb2;

baseRef.dfa(); // Вызывает метод класса Base.

baseRef = dOb3;

baseRef.dfa();

}

}

Рис 2 Виртуальный метод не переопределеи, используется метод

базового класса

Из текста программы и приведенных результатов (рис 2) видно, что класс D2 не переопределяет метод dfa() и требуемый метод dfa() вызывается из базового класса Base.

К особому случаю относится отсутствие переопределения при многоуровневой иерархии. Ниже рассматривается такой пример.

3

// Производный класс не переопределяет виртуальный метод, при многоуровневой иерархии

using System;

class Base

{

// Создание VM в базовом классе

public virtual void dfa()

{

Console.WriteLine("\n Метод dfa() в классе Base ");

}

}

// Первый производный класс D1

class D1 : Base

{

// Переопределение метода dfa()в классе D1.

public override void dfa()

{

Console.WriteLine("\n Метод dfa() в классе D1 \n\n\n ");

}

}

// Второй производный класс D2

class D2 : D1

{

// Этот производный класс D2 не переопределяет метод dfa().

}

// Третий производный класс D3

class D3 : D2

{

// Этот производный класс D3 также не переопределяет метод dfa()

}

class NoOverridedfa2

{

public static void Main()

{

D3 dOb = new D3();

Base baseRef; // Ссылка на базовый класс

baseRef = dOb;

baseRef.dfa(); // Вызывает метод dfa() из класса D1

}

}

Рис 3 Отсутствие переопределения при многоуровневой иерархии

Как видно из текста программы и приведенных результатов (рис 3) при отсутствии переопределений для ряда классов в многоуровневой иерархии выполняется метод dfa() ближайшего класса, который переопределяет виртуальный метод.

Также можно модифицировать свойства с помощью ключевого слова virtual, а затем переопределять с помощью ключевого слова override.

Ниже рассматривается пример программы, которая содержит несколько классов, каждый из которых выполняет по своему однотипные действия (в данном случае определяется площадь геометрической фигуры). Эти однотипные действия поручены одной функции area(), которая в базовом классе объявлена как виртуальная (virtual double area()), а во всех производных классах она объявлена как переопределенная (override double area()). Тогда общую структуру текста программы можно представить так:

class B // базовый класс

{ // Набор полей данных

// Набор конструкторов по умолчанию и с параметрами

// Набор объектов

// Набор свойств

// Виртуальная фукция f()

public virtual тип f() { return ! }

}

class C : B // производный класс C

{ // Набор методов с форматом расширенного объявления

// Переопределение метода f() для класса С

public override тип f() { return !! }

// Методы вывода

}

class D : B // производный класс D

{ см. структуру класса С }

// класс с функцией Main()

class BCD

{ public static void Main()

{ B [] s = new B (данные);

s[i] = new C ( );

s[ i+k] = new D ( );

……………….

}

}

4

// Применение виртуальных методов и полиморфизма

using System;

class TwoDShape

{ double priib; // Это private-член

double prijb; // Это private-член

string priname; // Это private-член

// Добавление конструктора по умолчанию в класс В

public TwoDShape()

{ ib = jb = 0.0;

name = "null";

}

// Конструктор класса В c параметрами(добавлено в сравнении с № 6)

public TwoDShape (double i, double j, string n)

{ ib = i;

jb = j;

name = n;

}

// Создание дополнительного объекта класса В с ib = jb, name =n

public TwoDShape(double x, string n)

{

ib = jb = x;

name = n;

}

// Создается объект из объекта

public TwoDShape(TwoDShape ob)

{

ib = ob.ib;

jb = ob.jb;

name = ob.name;

}

//

public double ib

{

get { return priib; } // get-аксессор

set { priib = value; } // set-аксессор

}

// Свойство jb

public double jb

{

get { return prijb; } // get-аксессор

set { prijb = value; } // set-аксессор

}

// Свойство name

public string name

{

get { return priname; } // get-аксессор

set { priname = value; } // set-аксессор

}

public void showb()

{

Console.WriteLine(" ib = " + ib + " и jb = " + jb );

}

//

public virtual double area()

{

Console.WriteLine("i= " + ib + "метод area() необходимо переопределить.");

return 0.0;

}

}

// Класс С производный от класса В

class Triangle : TwoDShape

{

string style; // Закрытое поле

// Конструктор по умолчанию для класса С, который

// автоматически вызывает конструктор по умолчанию класса В

public Triangle()

{ style = "null";

}

// Вызывается конструктор c параметрами базового класса В (согласно с форматом расширенного объявления)

public Triangle(double i, double j, string s)

: base(i, j, "треугольник")

{ style = s; // Инициализация поля производного класса (своего класса)

Console.WriteLine("\n style = " + style);

}

// Создаем равнобедренный треугольник

public Triangle(double x) : base(x,"треугольник")

{ style = "равнобедренный";

}

// Создаем объект из объекта

public Triangle(Triangle ob) : base(ob)

{ style = ob.style;

}

// Переопределяем метод area() для класса Triangle

public override double area()

{

return ib * jb / 2;

}

// Метод отображает showc() тип треугольника

public void showc()

{

Console.WriteLine(" Треугольник = " + style);

}

}

// Класс прямоугольников, производный от класса B

class Rectangle : TwoDShape {

// Конструктор с параметрами

public Rectangle(double i, double j) : base(i, j, "прямоугольник") {}

// Создаем квадрат.

public Rectangle(double x) : base(x, "прямоугольник") { }

// Создаем объект из объекта

public Rectangle(Rectangle ob) : base(ob) { }

// Метод возвращает true, если прямоугольник - квадрат

public bool isSquare() {

if(ib == jb) return true;

return false;

}

// Переопределяем метод area() для класса Rectangle

public override double area() {

return ib * jb;

}

}

class DShapes {

public static void Main() {

TwoDShape[] shapes = new TwoDShape[5];

shapes [ 0 ] = new Triangle( 8.0, 12.0,"прямоугольный");

shapes [ 1 ] = new Rectangle(10);

shapes [ 2 ] = new Rectangle(10, 4);

shapes [ 3 ] = new Triangle(7.0);

shapes [ 4 ] = new TwoDShape(10,20, "заготовка для фигуры");

for(int i=0; i < shapes.Length; i++) {

Console.WriteLine(" i=" + i + " Объектом является " + shapes[i].name);

Console.WriteLine(" i=" + i + " Площадь равна " + shapes[i].area());

Console.WriteLine();

}

Console.WriteLine("\n\n\n");

}

}

Рис 4 Применение виртуальных методов

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

РАБОТА С АБСТРАКТНЫМ КЛАССОМ

Под классом мы понимаем структуру, в которой, в программировании, размещаются реально используемые ресурсы: рабочие функции (методы), поля данных, практически все то, чем пользуются программисты. Именно классам присвоено определение “ типы данных пользователя ”. При разработке сложных программ используется множество классов, каждый из которых имеет свои методы, ориентированные на выполнение определенных задач. По сути эти классы являются производными классами. Возникла необходимость иметь дополнительный класс в качестве базового класса, ссылка на который может указывать на объект производного класса, где выполняются переопределения методов. Таким классом является абстрактный класс, который кроме общепринятых методов содержит абстрактный метод. Абстрактный метод не реализуется базовым классом, не содержит тела и автоматически является виртуальным (не использует слово virtual).Формат записи абстрактного метода имеет вид:

abstract тип имя( список параметров );

Особенности реализации абстрактного механизма:

- абстрактыми могут быть обычные методы, свойства;

- класс, имеющий хотя бы один абстрактный метод должен быть абстрактным (меть модификатор abstract перед словом class);

- для абстрактного класса экземпляры или объекты не существуют;

- попытка создать объект абстрактного класса вызывает ошибку компиляции;

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

Общая структура текста программы может иметь следующий вид.

abstract class B // абстрактный класс базовый

{ // Поля данных

// Набор конструкторов

// Набор объектов

// Набор свойств

// Абстрактный метод f()

public abstract тип f() { return ! }

}

class C : B // производный класс С

{ // Набор конструкторов

// Методы с расширенными форматами объявления

// Набор объектов

// Вспомогательные методы

// Переопределение метода f() для класса С

public override тип f() { return ! }

}

// Производный класс D : B

class D : B

{ // Конструкторы с параметрами

// Создание объектов

// Переопределение метода f() для класса D

public override тип f() { return ! }

}

// Класс BCD

class BCD

{ public static void Main()

{ B [] x = new B[n];

x [0] = C();

…………..

x [n-1] = D(); //

Вывод результатов

}

}

5

// Работа с абстрактными классами

using System;

abstract class TwoDShape

{ double priib; // Это private-член

double prijb; // Это private-член

string priname; // Это private-член

// Добавление конструктора по умолчанию в класс В

public TwoDShape()

{ ib = jb = 0.0;

name = "null";

}

// Конструктор класса В c параметрами(добавлено в сравнении с № 6)

public TwoDShape (double i, double j, string n)

{ ib = i;

jb = j;

name = n;

}

// Создание дополнительного объекта класса В с ib = jb, name =n

public TwoDShape(double x, string n)

{

ib = jb = x;

name = n;

}

// Создается объект из объекта

public TwoDShape(TwoDShape ob)

{

ib = ob.ib;

jb = ob.jb;

name = ob.name;

}

// Свойство ib

public double ib

{

get { return priib; } // get-аксессор

set { priib = value; } // set-аксессор

}

// Свойство jb

public double jb

{

get { return prijb; } // get-аксессор

set { prijb = value; } // set-аксессор

}

// Свойство name

public string name

{

get { return priname; } // get-аксессор

set { priname = value; } // set-аксессор

}

public void showb()

{

Console.WriteLine(" ib = " + ib + " и jb = " + jb );

}

// Tеперь метод area() абстрактный.

public abstract double area();

}

// Класс С производный от класса В

class Triangle : TwoDShape

{

string style; // Закрытое поле

// Конструктор по умолчанию для класса С, который

// автоматически вызывает конструктор по умолчанию класса В

public Triangle()

{

style = "null";

}

// Вызывается конструктор c параметрами базового класса В (согласно с форматом расширенного объявления)

public Triangle(double i, double j, string s)

: base(i, j, "треугольник")

{

style = s; // Инициализация поля производного класса (своего класса)

Console.WriteLine("\n style = " + style);

}

// Создаем равнобедренный треугольник

public Triangle(double x) : base(x,"треугольник")

{ style = "равнобедренный";

}

// Создаем объект из объекта

public Triangle(Triangle ob) : base(ob)

{

style = ob.style;

}

// Переопределяем метод area() для класса Triangle

public override double area()

{

return ib * jb / 2;

}

// Метод отображает showc() тип треугольника

public void showc()

{

Console.WriteLine(" Треугольник = " + style);

}

}

// Класс прямоугольников, производный от класса B

class Rectangle : TwoDShape {

// Конструктор с параметрами

public Rectangle(double i, double j) : base(i, j, "прямоугольник") {}

// Создаем квадрат.

public Rectangle(double x) : base(x, "прямоугольник") { }

// Создаем объект из объекта

public Rectangle(Rectangle ob) : base(ob) { }

// Метод возвращает true, если прямоугольник - квадрат

public bool isSquare() {

if(ib == jb) return true;

return false;

}

// Переопределяем метод area() для класса Rectangle

public override double area() {

return ib * jb;

}

}

class AbsShapes {

public static void Main() {

TwoDShape[] shapes = new TwoDShape[4];

shapes [ 0 ] = new Triangle( 8.0, 12.0,"прямоугольный");

shapes [ 1 ] = new Rectangle(10);

shapes [ 2 ] = new Rectangle(10, 4);

shapes [ 3 ] = new Triangle(7.0);

for(int i=0; i < shapes.Length; i++) {

Console.WriteLine(" i=" + i + " Объектом является " + shapes[i].name);

Console.WriteLine(" i=" + i + " Площадь равна " + shapes[i].area());

Console.WriteLine();

}

Console.WriteLine("\n\n\n");

}

}

Рис 5 Работа с абстрактным классом

Некоторые выводы:

- Рузультаты рис 5 и рис 4 полностью совпадают при i = 0 ÷3. Это означает, что при работе с базовым абстрактным классом все производные классы должны или переопределить свои виртуальные функции или обявить себя абстрактными.

- На рис 4 при i = 4 использовалась объектная ссылка на базовый класс, но при этом нельзя объявлять объект типа базового класса (в задаче № 5 i = 4 не использовалось).

- Абстрактные классы могут сохранять ряд методов без модификации в абстрактный режим и использовать их в производных классах без изменения.

- Методы, объявленные как абстрактными, должны обязательно переопределяться в производных классах.

Дополнительные возможности наследования

Сюда относятся:

- механизм предотвращения наследования;

- применение класса object, из которого выводятся все остальные С# _типы;

- применение типа object как обобщенного типа данных.

Наследование предполагает совместную работу базового и поизводных классов. Прекращение наследования выполняется применением ключевого слова sealed (изолированный). Базовый класс может иметь несколько производных (наследных) классов. Отметим особенности применения модификатора sealed.

1 Эффект прекращения наследования проявляется только в производных классах.

2 Два модификатора abstract и sealed совместно не применяются, они проявляются только после создания производного класса и поэтому исключают друг друга.

Формат реализации запрета наследования имеет вид:

sealed class A {

………..

}

// Производный класс В

class B : A { ………

}

Ниже рассматривается пример программы на предотвращение наследования.

6

// Наследование, скрытие имени, использование слова base и запрещение наследования

using System;

/*sealed*/

class A{

public int x;

}

class B : A{

new int x; // Переменная х скрывает х в классе А

public B(int a, int b)

{ base.x = a; // переменная х в классе А

x = b; // переменная х в классе В

}

public void show()

{ Console.WriteLine("\n x in class A : " + base.x); // x in class A

Console.WriteLine(" x in class B : " + x); // x in class B

Console.WriteLine("\n z = " + (base.x + x) + "\n\n\n ");

}

}

class First {

public static void Main() {

B ob = new B(5, 55);

ob.show();

}

}

Рис 6 Наследование разрешено, доступ к скрытому имени через base.

// Эффект запрещения наследования

using System;

sealed

class A

{ public int x;

}

class B : A {

// ......

}

Рис 6.1 Наследование запрещено. Сообщение о ошибке а классе В

Рис 6.1 – сокращенный фрагмент программы примера № 6. В ошибке указано, что класс 'B': cannot derive from sealed type 'A’

Класс object

15