Добавить в цитаты Настройки чтения

Страница 324 из 371

Наш пример с вычислением интеграла хорошо демонстрирует функции обратного вызова и технику "раскрутки". Можно считать, что класс HighOrderIntegral — это внутренний слой нашей системы. В нем задан делегат, определяющий контракт, и функция EvalIntegral, требующая задания функции обратного вызова в качестве ее параметра. Функция EvalIntegral вызывается из внешнего слоя, где и определяются callback функции из класса Functions.

Многие из функций операционной системы Windows, входящие в состав Win Api 32, требуют при своем вызове задания callback-функций. Примером может служить работа с объектом операционной системы Timer. Конструктор этого объекта является функцией высшего порядка, и ей в момент создания объекта необходимо в качестве параметра передать callback-функцию, вызываемую для обработки событий, которые поступают от таймера.

Пример работы с таймером приводить сейчас не буду, ограничусь лишь сообщением синтаксиса объявления конструктора объекта Timer;

public Timer(TimerCallback callback,object state, int dueTime, int period);

Первым параметром конструктора является функция обратного вызова callback, которая принадлежит функциональному классу TimerCallback, заданному делегатом:

public delegate void TimerCallback(object state);

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

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

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

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

class FIntegral

{

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

     //интеграла и виртуальный метод, задающий базовую

     //подынтегральную функцию

     public double Evaluatelntegral(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=1(a,b,n);

        if(Math.Abs(I1-I0)<eps)break;

     }

     if(Math.Abs(I1–10)< 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 х = a, sum = sif(x)/2, dx = (b-a)/п;

       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, здесь нет делегата, у функции Evaluate integral нет параметра функционального типа. Вместо этого тут же в классе определен защищенный виртуальный метод, задающий конкретную подынтегральную функцию. В качестве таковой выбрана самая простая функция, тождественно равная единице.

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

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.Evaluatelntegral(2.0,3.0,0.le-5);

    double res2 = integral2.Evaluatelntegral(2.0,3.0,0.le-5);

    Console.WriteLine("Father = {0}, Son = {1}", resl,res2);

}//PolymorphIntegral

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

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

Делегаты как свойства

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