Интерфейсы
Фактически это те же самые абстрактные классы, НЕ СОДЕРЖАЩИЕ объявлений данных-членов и объявлений ОБЫЧНЫХ функций.
Все без исключения функции — члены интерфейса – абстрактные. Поэтому интерфейс объявляется с особым ключевым словом interface, а функции интерфейса, несмотря на свою "абстрактность", объявляются без ключевого слова abstract.
Основное отличие интерфейса от абстрактного класса заключается в том, что производный класс может наследовать одновременно несколько интерфейсов.
Объявление интерфейса
using System;
namespace Interface01
{
// Интерфейсы.
// Этот интерфейс характеризуется уникальными
// именами объявленных в нем методов.
interface Ix
{
void IxF0(int xKey);
void IxF1();
}
// Пара интерфейсов, содержащих объявления одноименных методов
// с одной и той же сигнатурой.
interface Iy
{
void F0(int xKey);
void F1();
}
interface Iz
{
void F0(int xKey);
void F1();
}
// А этому интерфейсу уделим особое внимание.
// Он содержит тот же набор методов, но в производном классе этот
// интерфейс будет реализован явным образом.
interface Iw
{
void F0(int xKey);
void F1();
}
// В классе TestClass наследуются интерфейсы...
class TestClass:Ix
,Iy
,Iz
,Iw
{
public int xVal;
// Конструкторы.
public TestClass()
{
xVal = 125;
}
public TestClass(int key)
{
xVal = key;
}
// Реализация функций интерфейса Ix.
// Этот интерфейс имеет специфические названия функций.
// В данном пространстве имен его реализация неявная и однозначная.
public void IxF0(int key)
{
xVal = key*5;
Console.WriteLine("IxF0({0})...", xVal);
}
public void IxF1()
{
xVal = xVal*5;
Console.WriteLine("IxF1({0})...", xVal);
}
// Реализация интерфейсов Iy и Iz в классе TestClass неразличима.
// Это неявная неоднозначная реализация интерфейсов.
// Однако неважно, чью конкретно функцию реализуем.
// Оба интерфейса довольны...
public void F0(int xKey)
{
xVal = (int)xKey/5;
Console.WriteLine("(Iy/Iz)F0({0})...", xVal);
}
public void F1()
{
xVal = xVal/5;
Console.WriteLine("(Iy/Iz)F1({0})...", xVal);
}
// А это явная непосредственная реализация интерфейса Iw.
// Таким образом, класс TestClass содержит ТРИ варианта
// реализации функций интерфейсов с одной и той же сигнатутой.
// Два варианта реализации неразличимы.
// Третий (фактически второй) вариант реализации
// отличается квалифицированными именами.
void Iw.F0(int xKey)
{
xVal = xKey+5;
Console.WriteLine("Iw.F0({0})...", xVal);
}
void Iw.F1()
{
xVal = xVal–5;
Console.WriteLine("Iw.F1({0})...", xVal);
}
public void bF0()
{
Console.WriteLine("bF0()...");
}
}
class Class1
{
static void Main(string[] args)
{
TestClass x0 = new TestClass();
TestClass x1 = new TestClass(5);
x0.bF0();
// Эти методы представляют собой неявную ОДНОЗНАЧНУЮ реализацию
// интерфейса Ix.
x0.IxF0(10);
x1.IxF1();
// Эти методы представляют собой неявную НЕОДНОЗНАЧНУЮ реализацию
// интерфейсов Iy и Iz.
x0.F0(5);
x1.F1();
// А вот вызов функций с явным приведением к типу интерфейса.
// Собственный метод класса bF0() при подобных преобразованиях
// не виден.
(x0 as Iy).F0(7);
(x1 as Iz).F1();
// А теперь настраиваем ссылки различных типов интерфейсов
// на ОДИН И ТОТ ЖЕ объект – представитель класса TestClass.
// И через "призму" интерфейса всякий раз объект будет
// выглядеть по-разному.
Console.WriteLine("==========Prism test==========");
Console.WriteLine("==========Ix==========");
Ix ix = x1;
ix.IxF0(5);
ix.IxF1();
Console.WriteLine("==========Iy==========");
Iy iy = x1;
iy.F0(5);
iy.F1();
Console.WriteLine("==========Iz==========");
Iz iz = x1;
iz.F0(5);
iz.F1();
Console.WriteLine("==========Iw==========");
Iw iw = x1;
iw.F0(10);
iw.F1();
}
}
}
Листинг
8.1.
Преимущества программирования с использованием интерфейсов проявляются в том случае, когда ОДНИ И ТЕ ЖЕ ИНТЕРФЕЙСЫ наследуются РАЗНЫМИ классами. При этом имеет место еще один вариант полиморфности: объект, представленный ссылкой-интерфейсом, способен проявлять различную функциональность в зависимости от реализации функций наследуемого интерфейса в классе данного объекта. И здесь все определяется спецификой данной конкретной реализации:
using System;
namespace Interface02
{
// Объявляются два интерфейса, каждый из которых содержит объявление
// одноименного метода с единственным параметром соответствующего типа.
interface ICompare0
{
bool Eq(ICompare0 obj);
}
interface ICompare1
{
bool Eq(ICompare1 obj);
}
// Объявляются классы, наследующие оба интерфейса.
// В каждом из классов реализуются функции интерфейсов.
// В силу того, что объявленные в интерфейсах методы – одноименные,
// в классах применяется явная реализация методов интерфейсов.
// Для классов-наследников интерфейсы представляют собой всего лишь
// базовые классы. Поэтому в соответствующих функциях сравнения
// допускается использование операции as.
//____________________________________________________________.
class C0:ICompare0,ICompare1
{
public int commonVal;
int valC0;
public C0(int commonKey, int key)
{
commonVal = commonKey;
valC0 = key;
}
// Метод реализуется для обеспечения сравнения объектов
// СТРОГО одного типа – C0.
bool ICompare0.Eq(ICompare0 obj)
{
C0 test = obj as C0;
if (test == null) return false;
if (this.valC0 == test.valC0)
return true;
else
return false;
}
// Метод реализуется для обеспечения сравнения объектов разного типа.
bool ICompare1.Eq(ICompare1 obj)
{
C1 test = obj as C1;
if (test == null) return false;
if (this.commonVal == test.commonVal)
return true;
else
return false;
}
}
//____________________________________________________________.
class C1:ICompare0,ICompare1
{
public int commonVal;
string valC1;
public C1(int commonKey, string key)
{
commonVal = commonKey;
valC1 = string.Copy(key);
}
// В классе C1 при реализации функции интерфейса ICompare0 реализован
// метод сравнения, который обеспечивает сравнение как объектов типа C1,
// так и объектов типа C0.
bool ICompare0.Eq(ICompare0 obj)
{
C1 test;
// Попытка приведения аргумента к типу C1.
// В случае успеха – сравнение объектов по специфическому признаку,
// который для данного класса представлен строковой переменной valC1.
// В случае неуспеха приведения
// (очевидно, что сравниваются объекты разного типа)
// предпринимается попытка по второму сценарию
// (сравнение объектов разных типов).
// Таким образом, в рамках метода, реализующего один интерфейс,
// используется метод второго интерфейса. Разумеется, при явном
// приведении аргумента к типу второго интерфейса.
test = obj as C1;
if (test == null)
return ((ICompare1)this).Eq((ICompare1)obj);
if (this.valC1.Equals(test.valC1))
return true;
else
return false;
}
// Метод реализуется для обеспечения сравнения объектов разного типа.
bool ICompare1.Eq(ICompare1 obj)
{
C0 test = obj as C0;
if (test == null) return false;
if (this.commonVal == test.commonVal)
return true;
else
return false;
}
}
//===============================================================
// Место, где порождаются и сравниваются объекты.
class Class1
{
static void Main(string[] args)
{
C0 x1 = new C0(0,1);
C0 x2 = new C0(1,1);
// В выражениях вызова функций - членов интерфейсов НЕ ТРЕБУЕТСЯ
// явного приведения значения параметра к типу интерфейса.
// Сравнение объектов - представителей одного класса (C0).
if ((x1 as ICompare0).Eq(x2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
C1 y1 = new C1(0,"1");
C1 y2 = new C1(1,"1");
// Сравнение объектов - представителей одного класса (C1).
if ((y1 as ICompare0).Eq(y2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
// Попытка сравнения объектов - представителей разных классов.
if (((ICompare0)x1).Eq(y2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
if ((x1 as ICompare1).Eq(y2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
if (((ICompare1)y2).Eq(x2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
// Здесь будет задействован метод сравнения, реализованный
// в классе C0 по интерфейсу ICompare0, который не является универсальным.
// Отрицательный результат может быть получен не только
// по причине неравенства значений сравниваемых величин,
// но и по причине несоответствия типов операндов.
Console.WriteLine("__________x2==y2__________");
if ((x2 as ICompare0).Eq(y2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
// А здесь вероятность положительного результата выше, поскольку в классе
// C1 метод интерфейса ICompare0 реализован как УНИВЕРСАЛЬНЫЙ.
// И это значит,
// что данный метод никогда не вернет отрицательного значения по причине
// несоответствия типов операндов.
Console.WriteLine("__________y2==x2__________");
if ((y2 as ICompare0).Eq(x2))
Console.WriteLine("Yes!");
else
Console.WriteLine("No!");
}
}
}
Листинг
8.2.
Реализация сортировки в массиве. Интерфейс IComparable
Независимо от типа образующих массив элементов, массив элементов данного типа – это особый класс, производный от базового класса Array.
Класс Array является основой для любого массива и для любого массива предоставляет стандартный набор методов для создания, манипулирования (преобразования), поиска и сортировки на множестве элементов массива.
В частности, варианты метода Sort() обеспечивают реализацию механизмов сортировки элементов ОДНОМЕРНОГО массива объектов (в смысле представителей класса Object ).
Сортировка элементов предполагает:
- ПЕРЕБОР всего множества (или его части) элементов массива;
- СРАВНЕНИЕ (значений) элементов массива в соответствии с определенным критерием сравнения.
При этом критерии для сравнения и алгоритмы сравнения могут задаваться либо с помощью массивов ключей, либо реализацией интерфейса IComparable, который специфицирует характеристики (тип возвращаемого значения, имя метода и список параметров) методов, обеспечивающих реализацию данного алгоритма. Далее будут рассмотрены различные варианты метода Sort.
- Вариант метода Sort сортирует полное множество элементов одноразмерного массива с использованием алгоритма, который реализуется методами, специфицированными стандартным интерфейсом сравнения:
public static void Sort(Array);
Реализованный на основе интерфейса алгоритм способен распознавать значения элементов массива и сравнивать эти элементы между собой, если это элементы предопределенных типов.
Таким образом, никаких проблем не существует, если надо сравнить и переупорядочить массивы элементов, для которых известно, КАК СРАВНИВАТЬ значения составляющих массив элементов. Относительно ..., System.Int16, System.Int32, System.Int64, ...,System.Double,... всегда можно сказать, какой из сравниваемых элементов больше, а какой меньше.
- Сортировка элементов массива (массива целевых элементов) также может быть произведена с помощью вспомогательного массива ключей:
public static void Sort(Array, Array);