Интерфейсите описват функционалност (група методи, свойства, индексатори и събития), която се поддържа от множество обекти. Подобно на класовете и структурите те се състоят от членове, но се различават от тях по това, че дефинират само прототипите на членовете си, без конкретната им реализацията.
От интерфейсите не могат да се създават обекти чрез инстанциране. Интерфейсите се реализират от класове или структури, които имплементират всички дефинирани в тях членове. Конкретните имплементации на даден интерфейс вече могат да се инстанцират и да се присвояват на променливи от тип интерфейс.
Членове на интерфейс
Интерфейсите могат да съдържат методи, свойства, индексатори и събития. В интерфейс не могат да се дефинират конструктори, деструктори, полета и вложени типове и не могат да се предефинират оператори.
Интерфейсите в C# не могат и да съдържат и константи, за разлика от други обектно-ориентирани езици, като Java, където това е допустимо.
Към членовете на интерфейс не може да се прилагат модификатори на достъпа – по подразбиране всички членове са с глобална видимост, все едно е указан модификатор public. Интерфейс може да наследи един или повече други интерфейса, като е възможно да предефинира или скрива техните членове. За пример да разгледаме няколко дефиниции на интерфейси:
GeometryInterfaces.cs
|
interface IMovable
{
void Move(int aDeltaX, int aDeltaY);
}
interface IShape
{
void SetPosition(int aX, int aY);
double CalculateSurface();
}
interface IPerimeterShape : IShape
{
double CalculatePerimeter();
}
interface IResizable
{
void Resize(int aWeight);
void Resize(int aWeightX, int aWeightY);
void ResizeByX(int aWeightX);
void ResizeByY(int aWeightY);
}
interface IDrawableShape : IShape, IResizable, IMovable
{
void Delete();
Color Color
{
get;
set;
}
}
|
Дефинирахме следните интерфейси: IMovable, IShape, IPerimeterShape, IResizable и IDrawableShape. Те илюстрират дефинирането на методи и свойства в интерфейс, както и наследяването между интерфейси (което може да бъде и множествено, както е например при IDrawableShape).
Реализиране на интерфейс
Тъй като не съдържат данни и описана функционалност, интерфейсите не могат да се инстанцират, а само да се реализират (имплементират) от класове и структури, от които вече могат да се създават инстанции.
Реализирането на интерфейс е операция, подобна на наследяването, с тази особеност, че реализиращият интерфейса тип в общия случай трябва да предостави реализации за всички членове на интерфейса. Ето примерна реализация на някои от дефинираните в горния пример интерфейси:
GeomertyImplementation.cs
|
public class Square : IShape
{
private int mX, mY, mSize;
public Square(int aX, int aY, int aSize)
{
mX = aX;
mY = aY;
mSize = aSize;
}
public void SetPosition(int aX, int aY) // From IShape
{
mX = aX;
mY = aY;
}
public double CalculateSurface() // Derived from IShape
{
return mSize * mSize;
}
}
public struct Rectangle : IShape, IMovable, IResizable
{
private int mX, mY, mWidth, mHeight;
public Rectangle(int aX, int aY, int aWidth, int aHeight)
{
mX = aX;
mY = aY;
mWidth = aWidth;
mHeight = aHeight;
}
public void SetPosition(int aX, int aY) // From IShape
{
mX = aX;
mY = aY;
}
public double CalculateSurface() // Derived from IShape
{
return mWidth * mHeight;
}
public void Move(int aDeltaX, int aDeltaY) // From IMovable
{
mX += aDeltaX;
mY += aDeltaY;
}
public void Resize(int aWeight) // Derived from IResizable
{
mWidth = mWidth * aWeight;
mHeight = mHeight * aWeight;
}
public void Resize(int aWeightX, int aWeightY) // IResizable
{
mWidth = mWidth * aWeightX;
mHeight = mHeight * aWeightY;
}
public void ResizeByX(int aWeightX) // From IResizable
{
mWidth = mWidth * aWeightX;
}
public void ResizeByY(int aWeightY) // From IResizable
{
mHeight = mHeight * aWeightY;
}
}
public class Circle : IPerimeterShape
{
private int mX, mY, mRadius;
public Circle(int aX, int aY, int aRadius)
{
mX = aX;
mY = aY;
mRadius = aRadius;
}
public void SetPosition(int aX, int aY) // From IShape
{
mX = aX;
mY = aY;
}
public double CalculateSurface() // From IShape
{
return Math.PI * mRadius * mRadius;
}
public double CalculatePerimeter() // From IPerimeterShape
{
return 2 * Math.PI * mRadius;
}
}
|
В този пример виждаме как класът Square реализира интерфейса IShape и как класът Rectangle реализира едновременно няколко интерфейса: IShape, IMovable и IResizable. Класът Circle реализира интерфейса IPerimeterShape, но понеже този интерфейс е наследник на IShape, това означава, че Circle на практика имплементира едновременно интерфейсите IShape и IPerimeterShape. Забележете, че всички методи от интерфейсите са декларирани като публични. Това се изисква по спецификация, защото всички методи в даден интерфейс са публични (въпреки, че нямат модификатор public). Няма да дискутираме как работят самите имплементации, защото това е извън целите на примера.
Имплементирането на интерфейс много прилича на наследяване. Можем да считаме, че то действително е особен вид наследяване, защото също задава "is-a" релация между интерфейса и типа, който го реализира. Например, в сила са твърденията че квадратът и правоъгълникът са форми, а кръгът също е форма, и освен това има периметър.
След като реализирането на интерфейс създава "is-a" релация, можем да говорим и за множество от обекти от тип интерфейс – това са инстанциите на всички класове, които реализират интерфейса пряко или косвено (реализирайки интерфейс, който го наследява), както и техните наследници.
Реализиране на интерфейс от структура
Интересно в горния пример е, че типът Rectangle не е клас, а структура. Това илюстрира една разлика между наследяването на клас и реализирането на интерфейс – второто може да се извърши и от структура.
|
Въпреки, че е възможно, не е препоръчителна практика структурите да реализират функционалност и да имплементират интерфейси. Структурите трябва да се използват за съхранение на проста съвкупност от полета. Ако случаят не е такъв, трябва да се използва клас.
| Обекти от тип интерфейс
Чрез следващия пример ще демонстрираме създаването на обекти от тип интерфейс. Реално ще създаваме обекти от типове, които наследяват даден интерфейс:
GeometryTest.cs
|
class GeomertyTest
{
public static void Main()
{
Square square = new Square(0, 0, 10);
Rectangle rect = new Rectangle(0, 0, 10, 12);
Circle circle = new Circle(0, 0, 5);
if (square is IShape)
{
Console.WriteLine("{0} is IShape", square.GetType());
}
if (rect is IResizable)
{
Console.WriteLine("{0} is IResizable", rect.GetType());
}
IShape[] shapes = {square, rect, circle};
foreach (IShape shape in shapes)
{
shape.SetPosition(5, 5);
if (shape is IPerimeterShape)
{
Console.WriteLine("{0} is IPerimeterShape", shape);
}
}
}
}
|
В горния пример създадохме масив от обекти от тип IShape и към всички приложихме действието SetPosition(…) полиморфно, т. е. без да се интересуваме от точния им тип – единствено знаем, че обектите поддържат методите от интерфейса. Кодът от примера се компилира и изпълнява без грешка и отпечатва следния резултат:
Square is IShape
Rectangle is IResizable
Circle is IPerimeterShape
|
Виждаме, че макар и да не можем директно (с конструктор) да създадем обект от тип интерфейс, можем през променлива от този тип да достъпваме обекти от класовете, които го реализират.
Друго интересно явление, което наблюдаваме в горния пример, е че можем да използваме интерфейс, за да приложим полиморфизъм, като полиморфното действие се извършва от типовете, реализиращи интерфейса, независимо дали са класове или структури.
Запазената дума is
Отново ще обърнем внимание на запазената дума is, която представихме при разглеждането на предаването на произволен брой параметри. Обръщението <обект> is <тип> връща true ако обектът е от дадения тип и false в противен случай. Трябва да имаме предвид, че обектите от тип-наследник са обекти и от базовия тип, за това <обект> is <базов_тип> винаги връща true.
В горния пример това обръщение се среща три пъти, като при първите два от тях по време на компилация получаваме предупреждение "The given expression is always of the provided type" – съобщение, с което сме напълно съгласни. Действително, типът на обектите circle и rect се определя по време на компилация и още тогава е известно, че проверяваното условие е винаги истина.
За обръщението в тялото на цикъла не получаваме предупреждение и в този случай на употреба виждаме истинската мощ на оператора is – проверка за типа на обект, който не е известен в момента на компилация.
Явна имплементация на интерфейс
Както споменахме по-рано в тази тема, класовете и структурите могат да имплементират по повече от един интерфейс. Това би могло да създаде конфликт, ако един тип имплементира няколко интерфейса, съдържащи методи с еднакви сигнатури. Да разгледаме следния пример:
public interface I1
{
void Test();
}
public interface I2
{
void Test();
void AnotherTest();
}
public class TestImplementation : I1, I2
{
public void Test()
{
Console.WriteLine("Test() called");
}
}
|
Горният код е допустим в C#, но използването му не се препоръчва. То създава затруднения, от една страна, защото не е ясно в кой интерфейс е дефиниран методът Test() в класа TestImplementation, и от друга, защото няма възможност да предостави различни имплементации за метода от различните интерфейси.
За да се справим с описания проблем можем да използваме явната имплементация на интерфейси (explicit interface implementation). В C# можем да дефинираме в един тип два метода с еднаква сигнатура, стига поне единият от тях да е явна имплементация на метод от интерфейс. Явна имплементация се задава, като изрично се укаже на кой интерфейс принадлежи имплементираният член, както в примера:
public class TestExplicit : I1, I2
{
void I1.Test()
{
Console.WriteLine("I1.Test() called");
}
void I2.Test()
{
Console.WriteLine("I2.Test called");
}
void I2.AnotherTest()
{
Console.WriteLine("I2.AnotherTest called");
}
public void Test()
{
Console.WriteLine("TestExplicit.Test() called");
}
public static void Main()
{
TestExplicit t = new TestExplicit();
t.Test();
// Prints: TestExplicit.Test() called
I1 i1 = (I1) t;
i1.Test();
// Prints: I1.Test() called
I2 i2 = (I2) t;
i2.Test();
// Prints: I2.Test() called
}
}
|
Виждаме как при явна имплементация на интерфейс трябва да укажем името на интерфейса в дефиницията на реализирания член, а за да го достъпим трябва да преобразуваме обекта към интерфейса. Методите, принадлежащи на явно имплементирани интерфейс, не могат да бъдат публични или да имат друг модификатор за достъп. Те винаги private.
|
Не е позволено да имплементираме явно само някои членове от един интерфейс. В горния пример ако променим дефиницията на метода I2.AnotherTest() на public void AnotherTest(), компилаторът ще съобщи за грешка.
|
При изпълнение на примерния код се получава следният резултат:
TestExplicit.Test() called
I1.Test() called
I2.Test called
|
Сподели с приятели: |