Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Другие элементы меню 18 страница




Рассмотрим, как устроен метод GetObjectData, управляющий сохранением данных. У этого метода два аргумента:

GetObjectData(SerializedInfo info, StreamingContext context)

Поскольку самому вызывать этот метод не приходится – он вызывается автоматически методом Serialize, то можно не особенно задумываться о том, как создавать аргументы метода. Более важно понимать, как их следует использовать. Чаще всего используется только аргумент info и его метод AddValue(key, field). Данные сохраняются вместе с ключом, используемым позже при чтении данных. Аргумент key, который может быть произвольной строкой, задает ключ, а аргумент field – поле объекта. Например, для сохранения полей name и age можно задать следующие операторы:

info.AddValue("name",name); info.AddValue("age", age);

Поскольку имена полей уникальны, то их разумно использовать в качестве ключей.

Если поле son класса Father является объектом класса Child и этот класс сериализуем, то для сохранения объекта son, следует вызвать метод:

son.GetObjectData(info, context)

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

Перейдем теперь к рассмотрению специального конструктора класса. Он может быть объявлен с атрибутом доступа private, но лучше, как и во многих других случаях, использовать атрибут protected, что позволит использовать этот конструктор потомками класса, осуществляющими собственную сериализацию. У конструктора те же аргументы, что и у метода GetObjectData. Опять-таки, в основном используется аргумент info и его метод GetValue(key, type), выполняющий операцию, обратную к операции метода AddValue. По ключу key находится хранимое значение, а аргумент type позволяет привести его к нужному типу. У метода GetValue имеется множество типизированных версий, позволяющих не задавать тип. Так что восстановление полей name и age можно выполнить следующими операторами:

name = info.GetString("name"); age = info.GetInt32("age");

Восстановление поля son, являющегося ссылочным типом, выполняется вызовом его специального конструктора:

son = new Child(info, context);

А теперь вернемся к нашему примеру со стариком, старухой и золотой рыбкой. Заменим стандартную сериализацию собственной. Для этого оставив атрибут сериализации у класса Personage, сделаем класс наследником интерфейса ISerializable:

[Serializable]

public class Personage:ISerializable

{…}

Добавим в наш класс специальный метод, вызываемый при сериализации – сохранении данных:

//Специальный метод сериализации

public void GetObjectData(SerializationInfo info, StreamingContext context)

{

info.AddValue("name",name); info.AddValue("age", age);

info.AddValue("status",status);

info.AddValue("wealth", wealth);

info.AddValue("couplename",couple.name);

info.AddValue("coupleage", couple.age);

info.AddValue("couplestatus",couple.status);

info.AddValue("couplewealth", couple.wealth);

}

В трех первых строках сохраняются значимые поля объекта и тут все ясно. Но вот запомнить поле, хранящее объект couple класса Personage, напрямую не удается. Попытка рекурсивного вызова

couple.GetObjectData(info,context);

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

Добавим в наш класс специальный конструктор, вызываемый при десериализации – восстановления состояния:

//Специальный конструктор сериализации

protected Personage(SerializationInfo info, StreamingContext context)

{

name = info.GetString("name"); age = info.GetInt32("age");

status = info.GetString("status");

wealth = info.GetString("wealth");

couple = new Personage(info.GetString("couplename"),

info.GetInt32("coupleage"));

couple.status = info.GetString("couplestatus");

couple.wealth = info.GetString("couplewealth");

this. couple = couple; couple.couple = this;

}

Опять-таки первые строки восстановления значимых полей объекта прозрачно ясны. А с полем couple приходится повозиться. Вначале создается новый объект обычным конструктором, аргументы которого читаются из сохраняемой памяти. Затем восстанавливаются значения других полей этого объекта, а затем уже происходит взаимное связывание двух объектов.

Помимо введения конструктора класса и метода GetObjectData никаких других изменений в проекте не понадобилось – ни в методах класса, ни на стороне клиента. Внешне проект работал совершенно идентично ситуации. когда не вводилось наследование интерфейса сериализации. Но с внутренних позиций изменения произошли – методы форматеров Serialize и Deserialize в процессе своей работы теперь вызывали созданный нами метод и конструктор класса. Небольшие изменения произошли и в файлах, хранящих данные.

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

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

Таблица 19.1 Размеры файлов при различных случаях сериализации

формат сериализация Размер файла
Бинарный поток стандартная 355 байтов
Бинарный поток управляемая 355 байтов
XML документ стандартная 1, 14 Кб.
XML документ управляемая 974 байта

Преимуществом XML документа является его читабельность и хорошо развитые средства разбора, но зато бинарное представление выигрывает в объеме и скорости передачи тех же данных.

 

Вариант 1

52. Ключевое слово interface в языке C# задает описание:

q пользовательского интерфейса класса;

q открытой части класса;

q частного случая класса;

q абстрактного класса.

53. Отметьте истинные высказывания:

q слово «интерфейс» имеет разный смысл в зависимости от контекста;

q множественное наследование интерфейсов дает те же возможности, что и множественное наследование классов;

q при наследовании интерфейса ICloneable необходимо реализовать метод MemberwiseClone;

q при наследовании двух интерфейсов имена их методов должны быть различными;

q несколько интерфейсов могут быть наследниками одного и того же интерфейса.

54. Интерфейс ISerializable:

q автоматически реализует глубокую сериализацию;

q позволяет управлять процессом сериализации;

q имеет два метода, которые должен реализовать класс, наследующий интерфейс;

q конфликтует с атрибутом класса Serializable.

Вариант 2

56. Пусть задано описание интерфейсов: interface IN{string M(string s);} interface IP{string M(string s); string M1(int s);} interface IQ{int M(int s);}. Какие из объявлений классов содержат ошибки:

q public class C1:IP{string IP.M(string s){return (s+s);}
string IP.M1(int x){return x.ToString();}public int M (int s) { return s++;}}

q public class C1:IP,IN{string IP.M(string s){return (s+s);}
string IP.M1(int x){return x.ToString();}}

q public class C1:IP,IN{public string M(string s){return (s+s);}
public string M1(int x){return x.ToString();}}

q public class C1:IP,IN,IQ{public string M(string s){return (s+s);}
public string M1(int x){return x.ToString();}}

57. Отметьте истинные высказывания:

q для того чтобы объекты собственного класса сравнивать на «больше» и «меньше», необходимо сделать класс наследником интерфейса IComparable;

q для того чтобы объекты собственного класса можно было клонировать, необходимо сделать класс наследником интерфейса ICloneable;

q для того чтобы объекты собственного класса можно было сериализовать, необходимо сделать класс наследником интерфейса ISerializable;

q методы разных интерфейсов с одинаковой сигнатурой можно «склеивать» в классе наследнике, назначая им одну реализацию.

q реализация метода Clone позволяет организовать глубокое клонирование.

58. Класс с атрибутом Serialize:

q должен быть наследником интерфейса ISerializable;

q при вызове форматером метода Serialize выполняет глубокую сериализацию, если класс не является наследником интерфейса ISerializable;

q допускает два формата сериализации данных;

q облегчает организацию обмена данными с удаленным приложением;

q позволяет сохранять данные в текстовом формате.

Вариант 3

53. Пусть задано описание интерфейса и класса: interface IP{string M(string s); string M1(int s);} public class C1:IP{string IP.M(string s){return (s+s);}
string IP.M1(int x){return x.ToString();}public int M (int s) { return (s++);}}
Какие из объявлений в клиентском классе выполнимы:

q C1 it1 = new C1(); it1.M(7777);

q C1 it2 = new C1(); string s ="ss"; s =it2.IP.M(s);

q C1 it3 = new C1(); string s ="ss"; s =((IP)it3).M(s);

q IP it4 = new IP(); string s= "ss"; s = it4.M(s);

q IP it5 = (IP) new C1(); string s= "ss"; s = it5.M(s);

54. Отметьте истинные высказывания:

q один класс может наследовать несколько интерфейсов;

q один интерфейс может наследоваться несколькими классами;

q из-за коллизии имен дублируемое наследование интерфейсов запрещено;

q интерфейс может быть наследником нескольких интерфейсов;

q класс с атрибутом ISerializable должен реализовать специальный защищенный конструктор.

55. При наследовании интерфейсов:

q класс наследник должен реализовать хотя бы один из его методов;

q может выполнить переименование методов интерфейса;

q может выполнить склейку методов с одинаковой сигнатурой;

q может выполнить склейку методов с разной сигнатурой;

q объекту интерфейсного типа доступны закрытые методы интерфейса, реализованные в классе;

q объекты интерфейсного типа создаются стандартным путем с помощью контсруктора.

 

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

Новое слово для старого понятия. Функциональный тип. Функции высших порядков. Вычисление интеграла и сортировка. Два способа взаимодействия частей при построении сложных систем. Функции обратного вызова. Наследование и функциональные типы. Сравнение двух подходов. Класс Delegate. Методы и свойства класса. Операции над делегатами. Комбинирование делегатов. Список вызовов.

Как определяется функциональный тип, и как появляются его экземпляры

Слово делегат (delegate) используется в C# для обозначения хорошо известного понятия. Делегат задает определение функционального типа (класса) данных. Экземплярами класса являются функции. Описание делегата в языке C# представляет собой описание еще одного частного случая класса. Каждый делегат описывает множество функций с заданной сигнатурой. Каждая функция (метод), сигнатура которого совпадает с сигнатурой делегата, может рассматриваться как экземпляр класса, заданного делегатом. Синтаксис объявления делегата имеет следующий вид:

[<спецификатор доступа>] delegate <тип результата > <имя класса> (<список аргументов>);

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

Спецификатор доступа может быть, как обычно, опущен. Где следует размещать объявление делегата? Как и у всякого класса есть две возможности:

непосредственно в пространстве имен, наряду с объявлениями других классов, структур, интерфейсов

внутри другого класса наряду с объявлениями методов и свойств. В этом случае такое объявление рассматривается как объявление вложенного класса.

Также как и интерфейсы C# делегаты не задают реализации. Фактически между некоторыми классами и делегатом заключается контракт на реализацию делегата. Классы, согласные с контрактом, должны объявить у себя статические или динамические функции, сигнатура которых совпадает с сигнатурой делегата. Если контракт выполняется, то можно создать экземпляры делегата, присвоив им в качестве значений функции, удовлетворяющие контракту. Заметьте, контракт является жестким, – не допускается ситуация, при которой у делегата тип параметра – object, а у экземпляра соответствующий параметр имеет тип, согласованный с object, например, int.

Начнем примеры этой лекции с объявления трех делегатов, поместив два из них в пространство имен, третий вложим непосредственно в создаваемый нами класс:

namespace Delegates

{

//объявление классов - делегатов

delegate void Proc(ref int x);

delegate void MesToPers(string s);

class OwnDel

{

public delegate int Fun1(int x);

int Plus1(int x){ return (x+100);}// Plus1

int Minus1(int x){ return (x-100);}// Minus1

void Plus(ref int x){x+= 100;}

void Minus(ref int x){x-=100;}

//поля класса

public Proc p1;

public Fun1 f1;

char sign;

//конструктор

public OwnDel(char sign)

{

this. sign = sign;

if (sign == '+')

{p1 = new Proc(Plus);f1 = new Fun1(Plus1);}

{p1 = new Proc(Minus);f1 = new Fun1(Minus1);}

}

}// class OwnDel

}

Прокомментирую этот текст:

Первым делом объявлены три функциональных класса – три делегата: Proc, MesToPers, Fun1. Каждый из них описывает множество функций фиксированной сигнатуры.

В классе OwnDel описаны четыре метода:Plus, Minus, Plus1, Minus1, сигнатуры которых соответствуют сигнатурам, задаваемых классами Proc и Fun1.

Поля p1 и f1 класса OwnDel являются экземплярами классов Proc и Fun1.

В конструкторе класса поля p1 и f1 связываются с конкретными методами Plus или Minus, Plus1 или Minus1. Связывание с той или иной функцией в данном случае определяется значением поля sign.

Заметьте, экземпляры делегатов можно рассматривать как ссылки (указатели на функции), методы тех или иных классов с соответствующей сигнатурой можно рассматривать как объекты, хранимые в динамической памяти. В определенный момент происходит связывание ссылки и объекта (в этой роли выступают не совсем обычные объекты, имеющие поля, а методы, задающие код). Взгляд на делегата, как на указатель функции, характерен для программистов, привыкших к С++.

Приведу теперь процедуру, тестирующую работу созданного класса:

public void TestOwnDel()

{

int account = 1000, account1=0;

OwnDel oda = new OwnDel('+');

Console.WriteLine("account = {0}, account1 = {1}",

account, account1);

oda.p1(ref account); account1=oda.f1(account);

Console.WriteLine("account = {0}, account1 = {1}",

account, account1);

}

Клиент класса OwnDel создает экземпляр класса, передавая конструктору знак той операции, которую он хотел бы выполнить над своими счетами – account и account1. Вызов p1 и f1, связанных к моменту вызова с закрытыми методами класса, приводит к выполнению нужных функций.

В нашем примере объявление экземпляров делегатов и связывание их с внутренними методами класса происходило в самом классе. Клиенту оставалось лишь вызывать уже созданные экземпляры, но эту работу можно выполнять и на стороне клиентского класса, чем мы сейчас и займемся. Рассмотрим многократно встречавшийся класс Person, слегка изменив его определение:

class Person

{

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

public Person(){name =""; id=0; salary=0.0;}

public Person(string name){ this. name = name;}

public Person (string name, int id, double salary)

{ this. name = name; this. id=id; this. salary = salary;}

public Person (Person pers)

{ this. name = pers.name; this. id = pers.id;

this. salary = pers.salary;}

//методы

public void ToPerson(string mes)

{

this. message = mes;

Console.WriteLine("{0}, {1}",name, message);

}

//свойства

private string name;

private int id;

private double salary;

private string message;

//доступ к свойствам

public string Name

{ get { return (name);} set {name = value;}}

public double Salary

{ get { return (salary);} set {salary = value;}}

public int Id

{ get { return (id);} set {id = value;}}

}// class Person

Класс Person устроен обычным способом: у него несколько перегруженных конструкторов, закрытые поля и процедуры-свойства для доступа к ним. Обратить внимание прошу на метод класса ToPerson, сигнатура которого совпадает с сигнатурой класса, определенной введенным ранее делегатом MesToPers. Посмотрите, как клиент класса может связать этот метод с экземпляром делегата, определенного самим клиентом:

Person man1 = new Person("Владимир");

MesToPers mestopers = new MesToPers(man1.ToPerson);

mestopers("пора работать!");

Обратите внимание, поскольку метод ToPerson не является статическим методом, то при связывании необходимо передать и объект, вызывающий метод. Более того, переданный объект становится доступным экземпляру делегата. Отсюда сразу же становится ясным, что экземпляры делегата – это не просто указатели на функцию, а более сложно организованные структуры. Они, по крайней мере, содержат пару указателей на метод и на объект, вызвавший метод. Вызываемый метод в своей работе использует как информацию, передаваемую ему через аргументы метода, так и информацию, хранящуюся в полях объекта. В данном примере переданное сообщение «пора работать» присоединится к имени объекта, и результирующая строка будет выдана на печать. В тех случаях, когда метод, связываемый с экземпляром делегата, не использует информацию объекта, этот метод может быть объявлен как статический метод класса. Таким образом инициализировать экземпляры делегата можно как статическими методами, так и динамическими методами, связанными с конкретными объектами.

Последние три строки были добавлены в выше приведенную тестирующую процедуру. Взгляните на результаты ее работы:

Рис. 20.1 Объявление делегатов и создание их экземпляров

Функции высших порядков

Одно из наиболее важных применений делегатов связано с функциями высших порядков. Функцией высшего порядка называется такая функция (метод) класса, у которой один или несколько аргументов принадлежат к функциональному типу. Без таких функций в программировании обойтись довольно трудно. Классическим примером является функция вычисления интеграла, у которой один из аргументов задает подынтегральную функцию. Другим примером может служить функция, сортирующая объекты. Аргументом этой функции является функция Compare, сравнивающая два объекта. В зависимости от того, какая функция сравнения будет передана на вход функции сортировки, объекты будут сортироваться по-разному, например, по имени или по ключу, или по нескольким полям. Вариантов может быть много, и они определяются классом, описывающим сортируемые объекты.

Вычисление интеграла

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

public class HighOrderIntegral

{

//delegate

public delegate double SubIntegralFun(double x);

public double EvalIntegral(double a, double b,

double eps,SubIntegralFun sif)

{

int n=4;

double I0=0, I1 = I(a, b, n,sif);

for (n=8; n < Math.Pow(2.0,15.0); n*=2)

{

I0 =I1; I1=I(a,b,n,sif);

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);

Console.WriteLine("Требуемая точность не достигнута! "+

" eps = {0}, достигнутая точность ={1}, n= {2}",

eps,Math.Abs(I1-I0),n);

return (I1);

}

private double I(double a, double b, int n, SubIntegralFun sif)

{

//Вычисляет частную сумму по методу трапеций

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);

}

}// class HighOrderIntegral

Прокомментирую этот текст:

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

Метод EvalIntegral – основной метод класса позволяет вычислять определенный интеграл. Этот метод является функцией высшего порядка, поскольку одним из его аргументов является подынтегральная функция, принадлежащая классу SubIntegralFun.

Для вычисления интеграла применяется классическая схема. Интервал интегрирования разбивается на n частей и вычисляется частичная сумма по методу трапеций, представляющая приближенное значение интеграла. Затем n удваивается и вычисляется новая сумма. Если разность двух приближений по модулю меньше заданной точности eps, то вычисление интеграла заканчивается, иначе процесс повторяется в цикле. Цикл завершается либо по достижении заданной точности, либо когда n достигнет некоторого предельного значения (в нашем случае – 215).

Вычисление частичной суммы интеграла по методу трапеций реализовано закрытой процедурой I.

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

Чтобы продемонстрировать работу с классом HighOrderIntegral, приведу еще класс Functions, в котором описано несколько функций, удовлетворяющих контракту, заданному классом SubIntegralFun:

class functions

{

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

static public double sif1(double x)

{

int k = 1; int b = 2;

return (double)(k*x +b);

}

static public double sif2(double x)

{

double a = 1.0; double b = 2.0; double c= 3.0;

return (double)(a*x*x +b*x +c);

}

}// class functions

А теперь рассмотрим метод класса клиента, выполняющий создание нужных объектов и тестирующий их работу:

public void TestEvalIntegrals()

{

double myint1=0.0;

HighOrderIntegral.SubIntegralFun hoisif1 =

new HighOrderIntegral.SubIntegralFun(functions.sif1);

HighOrderIntegral hoi = new HighOrderIntegral();

myint1 = hoi.EvalIntegral(2,3,0.1e-5,hoisif1);

Console.WriteLine("myintegral1 = {0}",myint1);

HighOrderIntegral.SubIntegralFun hoisif2 =

new HighOrderIntegral.SubIntegralFun(functions.sif2);

myint1= hoi.EvalIntegral(2,3,0.1e-5,hoisif2);

Console.WriteLine("myintegral2 = {0}",myint1);

}// EvalIntegrals

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

Рис. 20.2 Вычисление интеграла с использованием функций высших порядков

Построение программных систем методом «раскрутки». Функции обратного вызова

Метод «раскрутки» является одним из основных методов функционально-ориентированного построения сложных программных систем. Суть его состоит в том, что программная система создается слоями. Вначале пишется ядро системы – нулевой слой, реализующий базовый набор функций. Затем пишется первый слой с новыми функциями, которые интенсивно вызывают в процессе своей работы функции ядра. Теперь система обладает большим набором функций. Каждый новый слой расширяет функциональность системы. Процесс продолжается, пока не будет достигнута заданная функциональность. На рисунке, изображающем схему построения системы методом раскрутки, стрелками показано, как функции внешних слоев вызывают функции внутренних слоев:

Рис. 20.3 Построение системы методом «раскрутки»

Успех языка С и операционной системы Unix, во многом, объясняется тем, что в свое время они были созданы методом раскрутки. Это позволило написать на 95% на языке С транслятор с языка С и операционную систему. Благодаря этому обеспечивался легкий перенос транслятора и операционной системы на компьютеры с разной системой команд. Замечу, что в те времена мир компьютеров отличался куда большим разнообразием, чем в нынешнее время. Для переноса системы на новый тип компьютера достаточно было написать ядро системы в соответствии с машинным кодом данного компьютера, далее работала раскрутка.

При построении систем методом раскрутки возникает одна проблема. Понятно, что функциям внешнего слоя известно все о внутренних слоях, и они без труда могут вызывать функции внутренних слоев. Но как быть, если функции внутреннего слоя необходимо вызывать функции внешних, еще не написанных и возможно еще не спроектированных слоев? Возможна ли симметрия вызовов. На первый взгляд, это кажется невозможным. Но программисты придумали, по крайней мере, два способа этой проблемы. Оба из них используют контракты. Один из них основан на функциях обратного вызова, другой – на наследовании и полиморфизме. Мы разберем оба эти способа, но начнем с функций обратного вызова.

Пусть F – функция высшего порядка с параметром G функционального типа. Тогда функцию G, задающую параметр, (а иногда и саму функцию F), называют функцией обратного вызова (callback функцией). Термин вполне понятен. Если в некотором внешнем слое функция Q вызывает функцию внутреннего слоя F, то, предварительно во внешнем слое следует позаботиться о создании функции G, которая и будет передана F. Таким образом, функция Q внешнего слоя вызывает функцию F внутреннего слоя, которая, в свою очередь (обратный вызов) вызывает функцию G внешнего слоя. Чтобы эта техника работала, должен быть задан контракт. Функция высших порядков, написанная во внутреннем слое, задает следующий контракт: «всякая функция, которая собирается меня вызвать, должна передать мне функцию обратного вызова, принадлежащую определенному мной функциональному классу, следовательно иметь известную мне сигнатуру».




Поделиться с друзьями:


Дата добавления: 2014-12-25; Просмотров: 516; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.153 сек.