Rozhraní

7. díl - Rozhraní

Tomáš Herceg       19.07.2010       C#, VB.NET, .NET       20330 zobrazení

V tomto díle si ukážeme, jak deklarovat a implementovat rozhraní a k čemu to vlastně je. Krátce si též představíme základní vestavěná rozhraní v .NET Frameworku.

V minulém díle jsme si poměrně podrobně povykládali o datových typech, třídách, dědičnosti, metodách a o všem možné. Měli byste být schopni zodpovědět následující otázky:

  • Jaký je rozdíl mezi hodnotovým a referenčním typem? Jaký je rozdíl mezi třídou a strukturou?
  • Co je předávání parametrů hodnotou a referencí?
  • Co je přetěžování?
  • Co je zapečetěná a virtuální metoda?
  • K čemu slouží abstraktní třídy?
  • Co znamená, když je metoda statická?

Rozhraní

Jak již název tohoto dílu říká, dnes se budeme věnovat rozhraním. K čemu vlastně slouží?

.NET Framework, na rozdíl třeba od C++, nepodporuje vícenásobnou dědičnost. Každá třída může dědit nejvýše z jedné třídy. V .NETu existuje právě jedna třída, která nemá žádného předka, a tou je třída System.Object. Každý datový typ dědí právě z této třídy.

Vícenásobná dědičnost podporována tedy není, což znamená, že jedna třída nemůže dědit z více dalších tříd. Velmi často ale můžeme chtít s jednou třídou (anebo hierarchií tříd) pracovat na různých místech a využívat různé části jejich funkcionality. Nebo můžeme chtít stejným způsobem pracovat s několika třídami, které od sebe navzájem nedědí.

Proto máme k dispozici rozhraní. Rozhraní (anglicky interface) si představte jako seznam metod, vlastností, událostí atd., který musí obsahovat každá třída, která toto rozhraní implementuje.

Uveďme praktický příklad – v .NETu existuje rozhraní IComparable (rozhraní se typicky pojmenovávají s velkým I na začátku), které mohou implementovat všechny typy, jejichž instance mezi sebou můžeme porovnávat. Toto rozhraní předepisuje jednu jedinou metodu, a to CompareTo, která porovná předanou hodnotu s hodnotou, na níž tuto metodu voláme, a vrátí číslo –1, 0 nebo 1 podle toho, jestli je předaná hodnota menší, rovna nebo větší hodnotě instance, na níž je metoda volána.

Každé rozhraní samozřejmě může implementovat libovolné množství metod, vlastností atd.

Deklarace rozhraní

V jazycích VB.NET a C# deklarujeme rozhraní velmi podobně, jako bychom deklarovali abstraktní třídu s abstraktními metodami. Deklarovaným členům nedáváme žádné modifikátory viditelnosti (Public, Private atd.) ani žádná jiná klíčová slova (abstract, virtual resp. MustOverride a Overridable). Rozhraní též nemůže deklarovat statické členy, figuruje pouze na instancích tříd.

Kód v jazyce Visual Basic .NET
 Public Interface IVector

Function GetLength() As Double


Sub
AddVector(ByVal vector As IVector)

ReadOnly Property X() As Double

ReadOnly
Property Y() As Double

End
Interface
Kód v jazyce C#
 public interface IVector
{

double GetLength();

void AddVector(IVector vector);

double X { get; }

double Y { get; }

}

Na výše uvedené ukázce jsme deklarovali rozhraní IVector, které předepisuje vlastnosti X a Y typu Double, které jsou jen pro čtení, metodu GetLength, jež vrátí délku vektoru, a metodu AddVector, která do aktuální instance přičte zadaný vektor.

Je nutné si uvědomit, že rozhraní je jenom seznam členů. Neobsahuje žádný kód, jen deklarace. Rozhraní neumožňuje předepisovat proměnné.

Implementace rozhraní

Samotné rozhraní je nám naprosto k ničemu. Potřebujeme třídu, která by jej implementovala. Napíšeme dvě různé implementace vektoru ve 2D prostoru, jedna bude zadána přímo souřadnicemi X a Y, druhá bude zadána délkou a úhlem. Ukážeme si, že díky našemu rozhraní můžeme s oběma těmito třídami, které od sebe nedědí ani nemají mimo System.Object společného předka, pracovat jednotně.

Kód v jazyce Visual Basic .NET
 Public Class Vector1
Implements IVector

Private _x, _y As Double

Public Sub AddVector(ByVal vector As IVector) Implements IVector.AddVector
_x += vector.X
_y += vector.Y
End Sub

Public Function GetLength() As Double Implements IVector.GetLength
Return Math.Sqrt(_x * _x + _y * _y)
End Function

Public ReadOnly Property X() As Double Implements IVector.X
Get
Return _x
End Get
End Property

Public ReadOnly Property Y() As Double Implements IVector.Y
Get
Return _y
End Get
End Property
End
Class

Public
Class Vector2
Implements IVector

Private _length, _angle As Double

Public Sub AddVector(ByVal vector As IVector) Implements IVector.AddVector
Dim _x, _y As Double
_x = X + vector.X
_y = Y + vector.Y
_length += vector.GetLength
_angle = Math.Atan2(_x, _y)
End Sub

Public Function GetLength() As Double Implements IVector.GetLength
Return _length
End Function

Public ReadOnly Property X() As Double Implements IVector.X
Get
Return Math.Sin(_angle) * _length
End Get
End Property

Public ReadOnly Property Y() As Double Implements IVector.Y
Get
Return Math.Cos(_angle) * _length
End Get
End Property
End
Class
Kód v jazyce C#
 public class Vector1 : IVector
{
private double x, y;

public double GetLength()
{
return Math.Sqrt(x * x + y * y);
}

public void AddVector(IVector vector)
{
x += vector.X;
y += vector.Y;
}

public double X
{
get { return x; }
}

public double Y
{
get { return y; }
}

}

public class Vector2 : IVector
{
private double length, angle;

public double GetLength()
{
return length;
}

public void AddVector(IVector vector)
{
double x = X + vector.X, y = Y + vector.Y;
length += vector.GetLength();
angle = Math.Atan2(x, y);
}

public double X
{
get { return Math.Sin(angle) * length; }
}

public double Y
{
get { return Math.Cos(angle) * length; }
}

}

Na výše uvedené ukázce máme třídy Vector1 a Vector2. Všimněte si nejprve syntaxe, jakou rozhraní impementujeme. Ve VB.NET je použito klíčové slovo Implements, v C# to vypadá jako klasická dědičnost. Pokud by třída implementovala víc rozhraní (což klidně může, zároveň může dědit z jedné třídy), uvádíme v C# nejprve třídu a pak seznam rozhraní, přičemž tyto názvy oddělujeme čárkami. Ve VB.NET bychom použili klíčové slovo Inherits pro třídu a Implements pro všechna rozhraní, opět je oddělujeme čárkou.

Ve VB.NET každý člen, který pochází z rozhraní, má na konci Implements IVector.něco, čímž jasně říkáme, že následující metoda implementuje tuto metodu z rozhraní. Nemusí se vůbec jmenovat stejně. V C# se naproti tomu u jednotlivých metod a vlastností rozhraní většinou neuvádí, je nutné dodržet názvy a datové typy argumentů, pokud jde o metody.

V případě, že třída má dědit více rozhraní, může se stát, že dvě rozhraní definují položku stejného názvu a například i stejného počtu argumentů. Ve VB.NET je to vyřešeno automaticky klauzulí Implements, prostě uděláme dvě metody s různými názvy a přes Implements je namapujeme na konkrétní metody rozhraní.

V C# se ale metody mapují podle stejných názvů a může vzniknout problém. Proto v C# rozlišujeme implicitní a explicitní implementaci rozhraní. Implicitní jsme již viděli – rozhraní a příslušná metoda či vlastnost je určeno podle názvu automaticky. V případě, kdy by měla nastat pochybnost, to můžeme vyřešit takto:

Kód v jazyce C#
     double IVector.GetLength()
{
return length;
}

V tomto případě jsme napsali implementaci metody GetLength. Nelze zde použít modifikátor viditelnosti, takže tato metoda bude vidět jen, pokud je výraz, na němž jej voláme, typu IVector. Pokud není, metodu neuvidíme, museli bychom nadeklarovat druhou s modifikátorem třeba public, která by tuto zavolala, abychom docílili stejného efektu, jako ve VB.NET.

V praxi bychom tedy, v případě explicitní implementace metody GetLength v C# museli proměnnou typu Vector1 nebo Vector2 přetypovat na IVector, anebo místo konkrétního typu používat proměnnou typu IVector, abychom mohli zavolat GetLength. Anebo do tříd Vector1 i Vector2 uvést kromě výše uvedené ještě veřejnou metodu GetLength:

Kód v jazyce C#
     public double GetLength()
{
return ((IVector)this).GetLength();
}

To už se ale dostáváme k tomu, jak rozhraní používat.

Používáme rozhraní

K čemu nám tedy rozhraní jsou, jsme ukázali v předešlé kapitole. Díky rozhraní IVector můžeme do jedné proměnné uložit jak instance tříd Vector1, tak i tříd Vector2. A to i přesto, že tyto třídy nedědí jedna od druhé, ani nemají společného předka (tedy resp. mají, System.Object, ale ten zase nemá metody, které by s nimi mohly nějak rozumně pracovat, například zjistit délku).

Vnitřní implementace metod našich dvou vektorů je jiná, jeden si pamatuje X a Y souřadnice a délku počítá, druhý si zase pamatuje délku a úhel a počítá z nich souřadnice X a Y.

Kód v jazyce Visual Basic .NET
         Dim v1, v2 As IVector
v1 = New Vector1()
v2 = New Vector2()

v1.AddVector(v2)
Console.WriteLine(v2.X)
Kód v jazyce C#
         IVector v1, v2;
v1 = new Vector1();
v2 = new Vector2();

v1.AddVector(v2);
Console.WriteLine(v2.X);

Oba vektory ale mají navenek stejné rozhraní – vlastnosti X a Y, metody GetLength a AddVector. V případě, že budeme mít kód, který bude využívat rozhraní IVector, kdokoliv může napsat svoji implementaci vektoru implementující toto rozhraní, a ať už si bude informace o vektoru ukládat jak a kam chce, kód bude fungovat (pokud tedy dodržíme stejnou funkčnost metod a vlastností).

Další vymoženosti

Rozhraní nemusí implementovat jen třídy a referenční typy obecně, právě naopak. I hodnotové typy mohou implementovat rozhraní, a také to dělají, například již zmiňované IComparable s metodou CompareTo. Schválně se podívejte, co můžete zavolat na Integeru.

Rozhraní mohou od sebe dědit (a dost se to používá). Vícenásobná dědičnost rozhraní je povolena, takže jedno rozhraní může dědit z více různých rozhraní. To už se v praxi vídá méně často, já jsem to nepoužil pokud vím nikdy, ale jde to. Dědičnost rozhraní se chová naprosto intuitivně, pokud víte, principy jsou stejné jako u tříd.

Vestavěná rozhraní

V .NET Frameworku je mnoho různých rozhraní, která si nyní představíme. Nebudeme se zaobírat detaily, na většinu z nich podrobněji narazíme v následujících dílech, ale ta základní je jistě dobré znát.

IEnumerable je rozhraní, které deklaruje metodu GetEnumerator, jež vrací objekt implementující IEnumerator, který má metody MoveNext, Reset a vlastnost Current. Vlastnost Current vrací aktuální prvek, metoda MoveNext přepne na následující prvek (a vlastnost Current bude pak vracet ten) a ještě vrátí, zda-li sada obsahuje další prvky. Reset se posune na začátek. Celá tato maškaráda slouží pro procházení jakýchkoliv kolekcí údajů a spoléhá na ni i vestavěná konstrukce v jazycích VB.NET a C#, a sice cyklus For Each (resp. foreach), který si představíme příště, až se budeme bavit o kolekcích. Víceméně vše, co obsahuje sadu nějakých objektů či hodnot, implementuje IEnumerable, aby bylo možné je procházet cyklem.

IComparable jsme již zmínili, slouží pro definici funkce pro porovnávání objektů. Výhodou je, že s ním počítají různé funkce Sort a umí podle této hodnoty řadit.

IEquatable je něco podobného, obsahuje metodu Equals vracející true nebo false podle toho, jestli se dva objekty rovnají nebo ne.

ICustomFormatter je rozhraní, které podporuje konverzi na řetězec s možností definice formátu. Implementováno je třeba typem DateTime, který reprezentuje datum a čas, aby bylo možné datum formátovat s použitím různých stylů a národních nastavení.

ICollection reprezentuje kolekci dat a dědí z IEnumerable. Kromě možnosti sekvenčního procházení od začátku do konce obsahuje metody pro přidávání a odebírání prvků z kolekce.

O dalších vestavěných rozhraních si povíme něco v dalších dílech tohoto seriálu.

Závěrem

V tomto díle jsme si krátce představili rozhraní. Neprobíral jsem všechny detaily a neukazoval úplně všechny možnosti, co s rozhraními jdou, nerozepisoval jsem všechna omezení, která nám stejně řekne kompilátor, když je porušíme. Není důležité si je pamatovat a umět z hlavy.

Popovídali jsme si i o tom, k čemu se rozhraní dají použít. Můžeme je využít jako náhražku za absenci vícenásobné dědičnosti, dále pro definici společných metod ve třídách, které spolu jinak “nekamarádí”. Dále se rozhraní hojně používají pro implementaci různých návrhových vzorů, které se běžně používají a je velmi příhodné je znát. Také samotný .NET Framework používá mnoho mechanismů, která staví právě na použití rozhraní. Je proto třeba znát základní rozhraní, protože na ně narážíme téměř na každém kroku.

 

hodnocení článku

2 bodů / 4 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Všechny díly tohoto seriálu

7. Rozhraní 19.07.2010
6. Dědičnost - dokončení 01.01.2010
5. Dědičnost 09.09.2009
4. Třídy 09.06.2009
3. Datové typy 05.05.2009
2. Základní elementy VB.NET a C# 18.04.2009
1. Úvod do .NET Frameworku 03.04.2009

 

 

 

Nový příspěvek

 

Diskuse: Rozhraní

Dobrý deň,

V triede Vector2 v implementácií vlastnosti X a Y máte prehodený

sínus a kosínus.Pre X ste použil funkciu sínus, lenže sin(0) je 0, pričom X súradnica pri uhle 0 je predsa 1. To zodpovedá hodnote cos(0).Pre Y je použitý kosínus, ale cos(0) je ako som povedal 1 a správna Y súradnica pri uhle 0 je 0.To presne opačne zodpovedá sin(0).

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

To záleží, jak ten úhel definujete, u některých geometrických interpretací bývá úhel 0° směrem nahoru.

Já to mám tak, aby mi vycházelo zpětné poskládání pomocí funkce Atan2 a nemusel jsem to řešit nějak složitěji.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

S tými interpretáciami je to pravda, lenže keby ste neprehodil parametre pre funkciu Atan2, tak Vám pre vektor [1;0] aj vráti uhol 0°. Ako pozerám do Object Browseru, prvý parameter sa volá y zatialčo Vy tam dávate hodnotu x.To vysvetľuje, prečo je potrebné mať vymenené aj sin a cos aby to vyšlo.A nepripadá mi to zložitejšie.

Váš článok, ale ja by som to všetko usporiadal do správneho poradia. Nemôže sa stať, že by niekto potom prehodil sin a cos podľa toho, ako to tu máte, ale neprehodil x a y pre atan2, ale išiel by podľa názvu parametru ? Potom bude mať tie súradnice úplne popletené.1

nahlásit spamnahlásit spam 2 / 2 odpovědětodpovědět

Aha, že Atan2 chce souřadnice pozpátku, jsem si nevšimnul. A divil jsem se, že pak ty úhly vychází špatně. Tak to bude tím.

Každopádně už to tak nechám, tohle je článek o rozhraních a ne o sinech a cosinech.

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback