Architecture Net

Динамическое использование интерфейсов


Полезной особенностью интерфейсов является возможность их использования в динамических сценариях, что позволяет по ходу выполнения программы проверять, поддерживается ли интерфейс определенным классом. Если интерфейс поддерживается, мы можем воспользоваться предоставляемыми им возможностями; в противном же случае программа может проигнорировать интерфейс. Фактически такое динамическое поведение реализуется с помощью обработки возникающих исключений, как это уже было продемонстрировано выше. Хотя подобный подход вполне работоспособен, однако он отличается некоторой неуклюжестью и приводит к появлению трудночитаемых программ. C++ поддерживает использование операторов dynamic_cast и typeid, а в .NET Framework есть класс Туре (Тип), облегчающий динамическое использование интерфейсов.

В качестве примера рассмотрим интерфейс ICustomer2, имеющий, по сравнению с интерфейсом ICustomer 1, дополнительный метод ShowCustomer.

_gc _interface ICustomer2 : ICustomerl
// сборщик мусора - ICustomer2: ICustomerl
{
public:
void ShowCustomers(int id); // идентификатор
};

Предположим, что класс Customerl поддерживает интерфейс ICustomerl, a класс Customer2 — интерфейс !Customer2. Для консольной программы-клиента удобнее использовать исходный метод ShowCustomer, а не метод Get-Customer, так как последний создает список массивов и копирует данные в него. Поэтому программа-клиент предпочтет работать с интерфейсом ICus-tomer2, если это возможно. В папке TestlnterfaceBeforeCast содержится программа, рассмотренная в следующем разделе.

Проверка поддержки интерфейса перед приведением типов

Проверку поддержки интерфейса можно производить, выполняя динамическое приведение типа указателя и обрабатывая исключение, которое может при этом возникнуть. Однако более изящным решением будет выполнять проверку до приведения типа, избегая при этом возникновения исключений. Если объект поддерживает необходимый интерфейс, можно выполнять приведение типа для получения доступа к интерфейсу. С# поддерживает использование удобного оператора is для проверки того, поддерживает ли объект определенный интерфейс. К сожалению, в C++ с этой целью приходится использовать метод отражения, реализуемый посредством методов GetType и Getlnterf асе. В связи с тем, что это приводит к появлению несколько громоздкого выражения, в следующем примере с помощью директивы #define определяется макрос IS (THIS, THAT_INTERFACE), используемый далее в двух условных операторах if.


//TestlnterfaceBeforeCast.срр
//MACRO: pObj->GetType()->GetInterface("Somelnterface")!=0

// МАКРОС
#define IS(THIS, THAT_INTERFACE) (THIS->GetType()->GetInterface(

THAT_INTERFACE)!=0)
#using <mscorlib.dll>
using namespace System;
// использование пространства имен Система;
_gc _interface ICustomer1 {};
// сборщик мусора - ICustomer1;
_gc _interface ICustomer2 : ICustomer1
// сборщик мусора - ICustomer2: ICustomer1
{
public:
void ShowCustomers(int id); // идентификатор
};
_gc class Customer! : public ICustomer1 {};
// класс сборщика мусора Customerl: ICustomerl {};
_gc class Customer2 : public ICustomer2
// класс сборщика мусора Customer2: ICustomer2
{
public:
void ShowCustomers(int id) // идентификатор
{
Console::WriteLine("Customer2::ShowCustomers:
succeeded");
}
};
void main(void) // главный
{
Customerl *pCustl = new Customerl; // не к ICustomer2
Console::WriteLine(pCustl->GetType());
// проверить, относится ли к типу ICustomer2 перед приведением

if (IS(pCustl, "ICustomer2"))
{
ICustomer2 *plcust2 =
dynamic_cast<ICustomer2 *>(pCustl);
plcust2->ShowCustomers(-1);
}
else
Console::WriteLine
("pCustl does not support ICustomer2 interface");
// ("pCustl не поддерживает интерфейс ICustomer2");

Customer2 *pCust2 = new Customer2; // да, на ICustomer2
Console::WriteLine(pCust2->GetType());
// проверить, относится ли к типу ICustomer2 перед приведением

if (IS(pCust2, "ICustomer2"))
{
ICustomer2 *plcust2 =
dynamic_cast<ICustomer2 *>(pCust2);
pIcust2->ShowCustomers(-1);
}
else
Console::WriteLine
("pCust2 does not support ICustomer2 interface");
// ("pCust2 не поддерживает интерфейс ICustomer2");

}
В этом примере продемонстрировано далеко не оптимальное решение, так как проверка типа производится дважды. Первый раз — при использовании отражения для проверки поддержки интерфейса в макросе IS. А еще раз проверка производится автоматически — при выполнении динамического приведения типа, в этом случае, если интерфейс не поддерживается, возникает исключение. Результат работы программы приведен ниже. Обратите внимание, что при выполнении программы действительно не возникает исключения.



Customer1
pCustl does not support ICustomer2 interface
Customer2
Customer2::ShowCustomers: succeeded

А вот и перевод:

Customer1
pCustl не поддерживает интерфейс ICustomer2
Customer2
Customer2:: ShowCustomers: успешно

Оператор dynamic_cast

Результатом выполнения оператора dynamic_cast является непосредственно указатель на интерфейс. В случае, если интерфейс не поддерживается, значение указателя устанавливается равным нулю. Используем этот факт для создания программы, в которой проверка поддержки интерфейса производится один раз. Приведенный ниже фрагмент взят из примера CastThenTestForNull, отличающегося от предыдущего, Testlnter-f aceBef oreCast, тем, что в нем производится проверка равенства нулю результата динамического приведения типа.

void main(void) // главный
{
Customerl *pCustl = new Customer!; // нет ICustomer2
Console::WriteLine(pCustl->GetType() ) ;
// Использовать оператор С ++ dynamic_cast, чтобы проверить
// наличие ICustomer2
ICustomer2 *plcust2 =
dynamic_cast<ICustomer2 *>(pCustl);
if (plcust2 != 0)
p!cust2->ShowCustomers(-1) ;
else
Console::WriteLine
("pCustl does not support ICustomer2 interface");
// ("pCustl не поддерживает интерфейс ICustomer2");

Customer2 *pCust2 = new Customer2; // да, есть ICustomer2
Console::WriteLine(pCust2->GetType()) ;
// Использовать оператор С ++ dynamic_cast, чтобы проверить
// наличие ICustomer2
plcust2 =
dynamic_cast<ICustomer2 *>(pCust2);
if (plcust2 != 0)
p!cust2->ShowCustomers(-1) ;
else
Console::WriteLine
("pCust2 does not support ICustomer2 interface");
// ("pCust2 не поддерживает интерфейс ICustomer2");

}

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

Customer1
pCustl does not support ICustomer2 interface
Customer2
ICustomer2::ShowCustomers: succeeded

Вот перевод этой выдачи:

Customer1
pCustl не поддерживает интерфейс ICustomer2
Customer2
ICustomer2:: ShowCustomers: успешно

Если вы знакомы с моделью компонентных объектов Microsoft (COM), проверка поддержки классом интерфейса вам должна быть хорошо знакома.

CompEbook.ru Железо, дизайн, обучение и другие


Содержание раздела