Тверской государственный университет
Опубликован: 22.11.2005 | Доступ: свободный | Студентов: 30274 / 1763 | Оценка: 4.31 / 3.69 | Длительность: 28:26:00
ISBN: 978-5-9556-0050-5
Лекция 20:

Функциональный тип в C#. Делегаты

< Лекция 19 || Лекция 20: 12345 || Лекция 21 >

Наследование и полиморфизм - альтернатива обратному вызову

Сегодня многие программные системы проектируются и разрабатываются не в функциональном, а в объектно-ориентированном стиле. Такая система представляет собой одно или несколько семейств интерфейсов и классов, связанных отношением наследования. Классы-потомки наследуют методы своих родителей, могут их переопределять и добавлять новые методы. Переопределив метод родителя, потомки без труда могут вызывать как собственный метод, так и метод родителя; все незакрытые методы родителя им известны и доступны. Но может ли родитель вызывать методы, определенные потомком, учитывая, что в момент создания родительского метода потомок не только не создан, но еще, скорее всего, и не спроектирован? Тем не менее, ответ на этот вопрос положителен. Достигается такая возможность опять-таки благодаря контрактам, заключаемым при реализации полиморфизма.

О полиморфизме говорилось достаточно много в предыдущих лекциях. Тем не менее, позволю напомнить суть дела. Родитель может объявить свой метод виртуальным, в этом случае в контракте на метод потомку разрешается переопределить реализацию, но он не имеет права изменять сигнатуру виртуального метода. Когда некоторый метод родителя Q вызывает виртуальный метод F, то, благодаря позднему связыванию, реализуется полиморфизм и реально будет вызван не метод родителя F, а метод F, который реализован потомком, вызвавшим родительский метод Q. Ситуация в точности напоминает раскрутку и вызов обратных функций. Родительский метод Q находится во внутреннем слое, а потомок с его методом F определен во внешнем слое. Когда потомок вызывает метод Q из внутреннего слоя, тот, в свою очередь, вызывает метод F из внешнего слоя. Сигнатура вызываемого метода F в данном случае задается не делегатом, а сигнатурой виртуального метода, которую, согласно контракту, потомок не может изменить. Давайте вернемся к задаче вычисления интеграла и создадим реализацию, основанную на наследовании и полиморфизме.

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

class FIntegral
{
	//базовый класс, в котором определен метод вычисления 
	//интеграла и виртуальный метод, задающий базовую
	//подынтегральную функцию
	public double EvaluateIntegral(double a, double b, 
		double eps)
	{
		int n=4;
		double I0=0, I1 = I( a, b, n);
		for( n=8; n < Math.Pow(2.0,15.0); n*=2)
		{
			I0 =I1; I1=I(a,b,n);
			if(Math.Abs(I1-I0)<eps)break;
		}
		if(Math.Abs(I1-I0)< eps)
			Console.WriteLine("Требуемая точность достигнута! "+
				" eps = {0}, достигнутая точность ={1}, n= {2}",
				eps,Math.Abs(I1-I0),n);
		else
			Console.WriteLine("Требуемая точность не достигнута! "+
				" eps = {0}, достигнутая точность ={1}, n= {2}",
				eps,Math.Abs(I1-I0),n);
		return(I1);			
	}
	private double I(double a, double b, int n)
	{
		//Вычисляет частную сумму по методу трапеций
		double x = a, sum = sif(x)/2, dx = (b-a)/n;
		for (int i= 2; i <= n; i++)
		{
			x += dx; sum += sif(x);
		}
		x = b; sum += sif(x)/2;
		return(sum*dx);
	}
	protected virtual double sif(double x)
	{return(1.0);}
	
}//FIntegral

Этот код большей частью знаком. В отличие от класса HighOrderIntegral, здесь нет делегата, у функции EvaluateIntegral нет параметра функционального типа. Вместо этого тут же в классе определен защищенный виртуальный метод, задающий конкретную подынтегральную функцию. В качестве таковой выбрана самая простая функция, тождественно равная единице.

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

class FIntegralSon:FIntegral
{
	protected override double sif(double x)
	{
		double a = 1.0; double b = 2.0; double c= 3.0;
		return (double)(a*x*x +b*x +c);
	}
}//FIntegralSon

Принципиально задача решена. Осталось только написать фрагмент кода, запускающий вычисления. Он оформлен в виде следующей процедуры:

public void TestPolymorphIntegral()
{
	FIntegral integral1 = new FIntegral();
	FIntegralSon integral2 = new FIntegralSon();
	double res1 = integral1.EvaluateIntegral(2.0,3.0,0.1e-5);
	double res2 = integral2.EvaluateIntegral(2.0,3.0,0.1e-5);
	Console.WriteLine("Father = {0}, Son = {1}", res1,res2);
}//PolymorphIntegral

Взгляните на результаты вычислений.

Вычисление интеграла, использующее полиморфизм

Рис. 20.4. Вычисление интеграла, использующее полиморфизм
< Лекция 19 || Лекция 20: 12345 || Лекция 21 >
Александр Галабудник
Александр Галабудник

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

Александра Гусева
Александра Гусева