Кратко съдържание



страница29/73
Дата21.07.2018
Размер9.03 Mb.
#76887
1   ...   25   26   27   28   29   30   31   32   ...   73

Интерфейси


Интерфейсите описват функционалност (група методи, свойства, индекса­тори и съби­тия), която се поддър­жа от множество обек­ти. Подобно на класовете и структурите те се състоят от членове, но се различават от тях по това, че дефинират само прототипите на чле­но­ве­те си, без конкретната им реали­зацията.

От интерфейсите не могат да се създават обекти чрез инстанциране. Интерфейсите се реализират от класове или структури, които имплемен­тират всички дефинирани в тях членове. Конкретните имплементации на даден интерфейс вече могат да се инстанцират и да се присвояват на про­мен­ливи от тип интерфейс.


Членове на интерфейс


Интерфейсите могат да съдържат методи, свойства, индексатори и събития. В интерфейс не могат да се дефинират конструктори, деструк­то­ри, полета и вложени типове и не могат да се предефинират оператори.

Интерфейсите в 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




Сподели с приятели:
1   ...   25   26   27   28   29   30   31   32   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница