Опубликован: 19.08.2004 | Уровень: для всех | Доступ: платный | ВУЗ: Национальный исследовательский ядерный университет «МИФИ»

Лекция 11: Расширенные возможности полиморфизма в языке C#

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

Предыдущая лекция была посвящена базовым аспектам полиморфизма. Обсудим более тонкие механизмы, предусмотренные в языке C# для реализации сложных полиморфных объектов.

Проиллюстрируем особенности использования абстрактных свойств и методов следующим фрагментом программы на языке C#:

abstract class Sequence {
	public abstract void Add(object x);
	// метод
	public abstract string Name{
		get;
	}
	// свойство
	public abstract object this [int i]
	{
		get;
		set;
	}
	// индексатор
}

class List : Sequence {
	public override void Add(object x)
	{
		...
	}

	public override string Name 
	{
		get {
			...
		}
	}
	public override object this [int i]
	{
		get {
		...
		}
		set{
			...
		}
	}
}

Как видно из приведенного примера, фрагмент программы на языке C# представляет собой описание абстрактных классов Sequence и List, реализующих потоковое чтение и запись данных ( get и set ).

Заметим, что описание абстрактного класса Sequence реализовано явно посредством зарезервированного слова abstract .

Необходимо обратить внимание на то обстоятельство, что поскольку в производных классах требуется замещение методов, методы Add и Name класса List оснащены описателем override .

Еще одним средством реализации расширенного полиморфизма в языке программирования C# является механизм, известный под названием "запечатанных" (sealed) классов.

Под "запечатанными" классами мы будем понимать нерасширяемые классы, которые могут наследовать свойства других классов. Решение об использовании механизма "запечатывания" при описании приоритетных или, иначе, замещенных ( override ) методов принимается в индивидуальном порядке.

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

Проиллюстрируем особенности использования механизма "запечатанных" классов следующим фрагментом программы на языке C#:

sealed class Account : Asset {
    long val;
    public void Deposit (long x)
    {
        ...
    }
    public void Withdraw (long x)
    {
    ...
    }
    ...
}

Как видно из приведенного примера, фрагмент программы на языке C# представляет собой описание "запечатанного" класса Account, реализующего абстрактные методы Deposit и Withdraw для занесения средств на счет и их снятия со счета.

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

Проиллюстрируем особенности реализации динамического связывания в языке программирования C# следующим фрагментом программы:

class A {
    public virtual void WhoAreYou()
    {
    Console.WriteLine("I am an A");
    }
}

class B : A {
    public override void WhoAreYou()
    {
    Console.WriteLine("I am a B");
    }
}

Как видно из приведенного примера, данный фрагмент программы содержит описания класса A с виртуальным методом WhoAreYou, который выводит на стандартное устройство вывода сообщение о принадлежности к классу A, а также замещенного класса B с виртуальным методом WhoAreYou, который выводит на стандартное устройство вывода сообщение о принадлежности к классу B. При таком подходе сообщение запускает метод, который динамически определяет принадлежность к классу (в приведенном ниже примере создается и идентифицируется объект класса B ):

A a = new B();
a.WhoAreYou();
// "I am a B"

Заметим, что существенной особенностью рассматриваемого примера является то обстоятельство, что B - это замещенный метод.

В этой связи, каждый из методов, который способен обрабатывать A, может также обрабатывать B. Проиллюстрируем этот факт следующим фрагментом программы на языке C#:

void Use (A x) {
    x.WhoAreYou();
}
Use(new A());
// "I am an A"
Use(new B());
// "I am a B"

Как видно из приведенного фрагмента программы, применение метода Use, реализующего идентификацию объекта класса A, приводит к результату для объекта "I am an A" класса A и к результату "I am a B" для объекта класса B. Корректность получаемого результата для класса B (несмотря на детерминированное описание класса A как параметра метода Use ) объясняется тем обстоятельством, что класс B является замещенным.