Dědičnost, rozhraní (interface) Obsah kapitoly DĚDIČNOST... 1 CHYBNÉ POUŽITÍ DĚDIČNOSTI... 8 VÍCENÁSOBNÁ DĚDIČNOST... 10 VÍCEÚROVŇOVÁ HIERARCHIÍ TŘÍD... 11 ROZHRANÍ (INTERFACE)... 18 SEZNAM OBRÁZKŮ... 22 SEZNAM PROGRAMŮ... 22 Cílem kapitoly je ukázat možnosti opětného použití rozhraní (dědičnosti) a rozhraní. Klíčové pojmy: Dědičnost, rozhraní (interface). Dědičnost Dědění je proces, jímž jeden objekt získá vlastnosti a metody jiného. Slouží k podpoře systému hierarchické klasifikace. Dědění odstraňuje nadbytečnost při definování tříd se společnými vlastnostmi a metodami. Společné vlastnosti a metody se definují v základní třídě a specifické vlastnosti a metody se uvedou v odvozených třídách. Je-li třída U téměř shodná s T, bylo by vhodné, aby U mohla požádat T o použití jejích operací. T nadřízenou (nadřazenou) třídou, základní třída U podřízenou (podřazenou) třídou, odvozená třída Dědičnost umožňuje budovat software postupně: Nejprve vytvořit třídy umožňující vyřešit nejobecnější případ. Potom přidat specializovanější třídy, které dědí z prvních tříd. Mohou použít jejich operace a atributy. Pro vyjádření dědičnosti lze použít slovo je například: větroň je letadlo. 2.11. 2011 kapitola 11 1/22
Větroň je Letadlo Obrázek 1 Příklad UML diagramu, znázorňujícího dědičnost Dědičnost je proces, prostřednictvím kterého může jeden objekt získávat vlastnosti jiného objektu. Podporuje hierarchickou klasifikaci. Většina našich znalostí o okolním světě má hierarchickou strukturu. Gold Delicious jablko ovoce potrava Třída potrava má jisté vlastnosti: nutriční hodnotu, kalorie, atd. Lze je zpětně aplikovat na podtřídu ovoce. Třída ovoce má oproti třídě potrava jisté specifické vlastnosti: kyselé ovoce sladké a podobně. Třída jablko má oproti třídě ovoce další specifické vlastnosti: roste na stromě, má křehkou dužinu a podobně. Jablko Gold Delicious dědí všechny vlastnosti nadřazených tříd a přitom má jistou kvalitu, která jej činí jedinečným. Pokud bychom nevyužívali dědičnost, musel by mít každý objekt explicitně deklarovány všechny svoje vlastnosti. Dědičnost umožní deklarovat v rámci třídy pouze jedinečné vlastnosti. Ostatní vlastnosti může zdědit z nadřazených tříd. Objekty základní třídy se často ani nedefinují. Základní třída je zde proto, aby zapouzdřovala obecné vlastnosti a metody pro použití v odvozených třídách. Dedičnost Mechanismus, jehož prostřednictvím může 1 třída zdědit vlastnosti jiné třídy. Odvozená třída Základní třída Obrázek 2 UMLdiagram, znázorňující dědičnost 2.11. 2011 kapitola 11 2/22
Obecný tvar zápisu dědění třídy: class odvozená: základní // definice třídy Přístupové specifikátory: private - privátní protected - chráněné veřejné internal interní můžeme používat pouze v rámci aktuálního programu Jak potomek dědí přístupová práva ke složkám předka určuje tabulka: Přístupový specifikátor Třída Potomci Ostatní Public ano ano ano internal protected ano ano ne protected ano ano ne internal ano ne ne private ano ne ne Pro privátní členy třídy z hlediska odvozené třídy platí: Jsou nepřístupné zachování zapouzdření. Veřejné členy jsou dostupné odvozeným třídám i jiným funkcím (včetně Mainu) soukromé členy nejsou dostupné odvozeným třídám, chráněné jsou. Program 1 Program ilustrující dědičnost třídy Y ze třídy X class X int i; X() // konstruktor X Console.WriteLine("Konstruktor X"); i=0; void nastav_i_x(int ii)i=ii; int vrat_i_x() Console.Write("vrat_i_X(): "); return i; ; class Y : X int i; Y() // konstruktor Y Console.WriteLine("Konstruktor Y"); i=0; void nastav_i_y(int ii)i=ii; int vrat_i_y() 2.11. 2011 kapitola 11 3/22
; Console.Write("vrat_i_Y(): "); return i; class Program static void Main() Y y=new Y(); Console.WriteLine("0", y.vrat_i_y()); Console.WriteLine("0", y.vrat_i_x()); y.nastav_i_y(20); // volání metod třídy Y Console.WriteLine("0", y.vrat_i_y()); // Přístup ke vnořenému objektu: // vnější.vnitřní.metoda() y.nastav_i_x(10); // volání metod třídy X Console.WriteLine( "0",y.vrat_i_X()); Vypíše se: Konstruktor X Konstruktor Y vrat_i_y(): 0 vrat_i_x(): 0 vrat_i_y(): 20 vrat_i_x(): 10 Konstruktory nemají parametry, nastavení hodnot privátních proměnných provádíme veřejnými metodami. Zápis dědičnosti pomocí diagramu UML: Maturant je Student Obrázek 3 UML diagram příkladu dědičnosti třídy Maturant ze třídy Student Program 2 Dědičnost: Maturant je student class student protected string jmeno; protected double prumer; student(string s,double p) jmeno=s; prumer=p; 2.11. 2011 kapitola 11 4/22
void vypis() Console.WriteLine("0 1",jmeno,prumer); ; class maturant: student double prum_mat; maturant(string s,double p,double pmat): base ( s, p) // konstruktor odvozené třídy maturant // musí předat parametry konstruktoru základní třídy student prum_mat=pmat; void vypis() Console.WriteLine("0 1",jmeno,prum_mat); ; class Program static void Main() student S1=new student("petr",1.6); student S2=new student("pavel",2.7); maturant S3=new maturant("josef",2.5,1.9); S1.vypis(); S2.vypis(); S3.vypis(); Vypíše se: Petr 1,6 Pavel 2,7 Josef 1,9 2.11. 2011 kapitola 11 5/22
Z jedné třídy může být odvozeno více různých tříd. PocetKol Delka Vozidlo +Vozidlo(int pk,int d) +zobraz() PocPas osobni +osobni(int pp, int pk, int d) +zobr() nakladni naklad +nakladni(int n, int pk,int d) +zobr() Obrázek 4 UML diagram příkladu dědičnosti tříd osobni a nakladni ze třídy Vozidlo Program 3 Osobní i nákladní je vozidlo class Vozidlo int PocetKol; int Delka; // Konstruktor Vozidlo(int pk,int d) PocetKol=pk; Delka=d; void zobraz() Console.WriteLine("Počet kol: 0", PocetKol); Console.WriteLine("Delka:0 ", Delka); ; class osobni: Vozidlo int PocPas; 2.11. 2011 kapitola 11 6/22
// Konstruktor odvozené třídy musí převzít i parametry // pro konstruktor základní třídy osobni(int pp,int pk, int d):base(pk,d) PocPas=pp; void zobr() zobraz(); Console.WriteLine("Pocet pasazeru:0 ", PocPas); ; class nakladni: Vozidlo int naklad; // Konstruktor odvozené třídy musí převzít i parametry // pro konstruktor základní třídy nakladni(int n,int pk, int d):base(pk,d) naklad=n; void zobr() zobraz(); Console.WriteLine("Naklad:0 ", naklad); ; class Program static void Main() osobni O=new osobni(5,4,2); nakladni N=new nakladni(30000,12,6); Console.WriteLine("osobni: "); O.zobr(); Console.WriteLine("nakladni: "); N.zobr(); Vypíše se: osobni: Počet kol: 4 Delka:2 Pocet pasazeru:5 nakladni: Počet kol: 12 Delka:6 Naklad:30000 2.11. 2011 kapitola 11 7/22
Obrázek 5 UML diagram pracovníků školy Osoba Zaměstnanec školy Učitel Uklízečka Interní Externí Chybné použití dědičnosti Převrácená hierarchie Osoba Zaměstnanec školy Učitel Obrázek 6 Špatné znázornění hierarchie zaměstnanců (šipky mají být otočené) Nesprávné použití dědičnosti U dědičnosti je vazba mezi odvozenou a základní třídou určena slovem je například: Automobil je vozidlo. Nelze říct: Motor je automobil. Zde je možné vazbu mezi jednotlivými třídami určit slovem má, což ukazuje na kompozici. (Automobil má motor.) Situaci zde nezlepší ani otočení šipek. Nelze říct: Automobil je motor. 2.11. 2011 kapitola 11 8/22
Automobil Motor Brzdy Kolo Motor Brzdy Kolo Automobil Obrázek 7 Nevhodně použitá dědičnost Program 4 Žárovka je světlo class Svetlo protected bool a; void Rosvit()a=true; void Zhasni()a=false; void Zobraz() if (a)console.writeline("svetlo sviti"); else Console.WriteLine("Svetlo nesviti"); ; class Zarovka : Svetlo int prikon; void Zobraz() if (a) Console.WriteLine("Žárovka svítí"); else Console.WriteLine("Žárovka nesvítí"); void NastavPrikon() Console.WriteLine("\nZadej prikon zarovky: "); prikon = Int32.Parse(Console.ReadLine()); void ZobrazPrikon() Console.WriteLine("Prikon zarovky je: 0 W", prikon); 2.11. 2011 kapitola 11 9/22
class Program static void Main() Zarovka Z=new Zarovka(); Z.Rosvit(); Z.Zobraz(); Z.Zhasni(); Z.Zobraz(); Z.NastavPrikon(); Z.ZobrazPrikon(); Vypíše se: Žárovka svítí Žárovka nesvítí Zadej prikon zarovky: 100 Prikon zarovky je: 100 W Vícenásobná dědičnost JEDEN OBJEKT JE INSTANCÍ VÍCE NEŽ JEDNÉ TŘÍDY V C# NENÍ ZAVEDENA Dopravní letadlo je letadlo je dopravní prostředek Reálný svět často vyžaduje podtřídy s vícenásobnou dědičností. Některé OO jazyky ji nepodporují (C#, Java, Smalltalk), některé ano (C++, Eiffel). Řešením vícenásobné dědičnosti v C# je rozhraní. 2.11. 2011 kapitola 11 10/22
Společnost Klient Interní klient Externí klient Obrázek 8 UML diagram vícenásobné dědičnosti Víceúrovňová hierarchií tříd Nepleťme si vícenásobnou dědičnost s víceúrovňovou hierarchií tříd ta v C# existuje: A +a +A(int b) +~A() B +a + B(int c,int b):a(b) +~B() C -k + C(int c, int b, int d ) +~C() Obrázek 9 Příklad UML diagramu pro víceúrovňovou hierarchii tříd 2.11. 2011 kapitola 11 11/22
Program 5 Víceúrovňová hierarchie tříd class A int a; A(int b) Console.WriteLine("Konstruktor A"); a=b; ; class B: A int a; B(int c,int b):base(b) Console.WriteLine("Konstruktor B"); a=c; ; class C: B int a; C(int c,int b,int d):base(b,d) Console.WriteLine("Konstruktor C"); a=c; ; class Program static void Main() C o=new C(10,20,30); Vypíše se: Konstruktor A Konstruktor B Konstruktor C Program 6 Víceúrovňová hierarchie - doplněno V dalším programu jsou v jednotlivých třídách doplněny metody pro výpis hodnot privátních proměnných. class A int a; A(int b) Console.WriteLine("Konstruktor A"); 2.11. 2011 kapitola 11 12/22
; a = b; void vypis_a_a() Console.WriteLine(a); class B : A int a; B(int c, int b) : base(b) Console.WriteLine("Konstruktor B"); a = c; void vypis_a_b() Console.WriteLine(a); ; class C : B int a; C(int c, int b, int d) : base(b, d) Console.WriteLine("Konstruktor C"); a = c; void vypis_a_c() Console.WriteLine(a); ; class Program static void Main() // Vytvoření nového objektu a předání hodnot privátním // proměnným v jednotlivých třídách. C o = new C(10, 20, 30); o.vypis_a_a(); o.vypis_a_b(); o.vypis_a_c(); Pomocí objektu odvozené třídy můžeme přistupovat k veřejným proměnným a metodám základní třídy, ale ne naopak pomocí objektu základní třídy nemůžeme přistupovat k veřejným datům a metodám odvozené třídy. Soukromé položky jsou přístupné jedině pomocí veřejných metod. 2.11. 2011 kapitola 11 13/22
Program 7 Maturant je student a je osoba Chráněné (protected) položky jsou kromě vlastních tříd přístupné z odvozených tříd ne však z Mainu. Máme základní třídu Osoba, z ní je odvozena třída Student a z ní třída Maturant. class Osoba protected string jmeno; protected int vek; Osoba(string j,int v) jmeno=j; vek = v; void vypis() Console.WriteLine("\nOsoba:"); Console.WriteLine("jmeno: 0", jmeno); Console.WriteLine("vek: 0", vek); ; class Student : Osoba protected double prumer; Student(string j, int v,double p) : base(j,v) prumer = p; new void vypis() Console.WriteLine("\nStudent:"); Console.WriteLine("jmeno: 0", jmeno); Console.WriteLine("vek: 0", vek); Console.WriteLine("prumer: 0", prumer); ; class Maturant : Student double matprum; Maturant(string j, int v, double p,double mp) : base(j, v,p) matprum = mp; new void vypis() Console.WriteLine("\nMaturant:"); Console.WriteLine("jmeno: 0", jmeno); 2.11. 2011 kapitola 11 14/22
Console.WriteLine("vek: 0", vek); Console.WriteLine("prumer: 0", prumer); Console.WriteLine("prumer z maturitnich predmetu: 0", matprum); ; class Program static void Main() Osoba O = new Osoba("Adam", 50); Student S = new Student("Josef", 17, 2.1); Maturant M = new Maturant("Petr", 18, 2.2, 1.9); O.vypis(); S.vypis(); M.vypis(); Program 8 Třídění student dle jména a průměru Hledání nejlepšího studenta a třídění studentů dle jména a průměru class Student string jmeno; double prum; Student(string jm, double pr) jmeno = jm; prum = pr; static void prohod(ref Student a, ref Student b) Student pom; pom = a; a = b; b = pom; static void tridjmen(ref Student[] V2) int j; for (int i = 0; i < V2.Length - 1; i++) for (j = i; j < V2.Length; j++) if (V2[i].jmeno[0] > V2[j].jmeno[0]) prohod(ref V2[i], ref V2[j]); static void tridprum(ref Student[] V2) 2.11. 2011 kapitola 11 15/22
int j; for (int i = 0; i < V2.Length - 1; i++) for (j = i; j < V2.Length; j++) if (V2[i].prum > V2[j].prum) prohod(ref V2[i], ref V2[j]); static Student Lepsi(Student S1, Student S2) return (S1.prum < S2.prum)? S1 : S2; class Pokus static void Main() Student[] V2 = new Student[] new Student("Josef",1.5), new Student("Jan",1.2), new Student("Petr",2.5), new Student("Pavel",2.1), new Student("Eva",1.9), new Student("Zuzana",1.95), new Student("Adam",2.45), new Student("Filip",2.75), new Student("Kateřina",3.5), ; Student Nej = new Student("aaa", 5); // Student.tridjmen(ref V2); foreach (Student s in V2) Nej=Student.Lepsi(Nej,s); Console.WriteLine("Nejlepsi je: 0, ktery ma prumer: 1", Nej.jmeno,Nej.prum); /* Student.tridprum(ref V2); foreach (Student s in V2) Console.WriteLine("0\t1", s.jmeno, s.prum);*/ Program 9 Nalezení nejlepšího studenta Položky jméno a prum jsou zde veřejné. Pokud by nebyly veřejné, je třeba k nim přistupovat pomocí přístupových metod. class Student string jmeno; double prum; 2.11. 2011 kapitola 11 16/22
Student(string jm, double pr) jmeno = jm; prum = pr; string VratJmeno() return jmeno; double VratPrumer() return prum; static Student Lepsi(Student S1, Student S2) return (S1.prum < S2.prum)? S1 : S2; class Pokus static void Main() Student[] V2 = new Student[] new Student("Josef",1.5), new Student("Jan",1.2), new Student("Petr",2.5), new Student("Pavel",2.1), new Student("Eva",1.9), new Student("Zuzana",1.95), new Student("Adam",2.45), new Student("Filip",2.75), new Student("Kateřina",3.5), ; Student Nej = new Student("aaa", 5); foreach (Student s in V2) Nej=Student.Lepsi(Nej,s); Console.WriteLine("Nejlepsi je: 0, ktery ma prumer: 1", Nej.VratJmeno(),Nej.VratPrumer()); Využití přepsané (overriding) funkce ToString() pro rozložení objektu na položky. class Student string jmeno; double prum; Student(string jm, double pr) jmeno = jm; prum = pr; 2.11. 2011 kapitola 11 17/22
static Student Lepsi(Student S1, Student S2) return (S1.prum < S2.prum)? S1 : S2; class Pokus static void Main() Student[] V2 = new Student[] new Student("Josef",1.5), new Student("Jan",1.2), new Student("Petr",2.5), new Student("Pavel",2.1), new Student("Eva",1.9), new Student("Zuzana",1.95), new Student("Adam",2.45), new Student("Filip",2.75), new Student("Kateřina",3.5), ; Student Nej = new Student("aaa", 5); foreach (Student s in V2) Nej=Student.Lepsi(Nej,s); Console.WriteLine("Nejlepsi je 0",Nej); Rozhraní (interface) Bylo řečeno, že jazyk C# (na rozdíl od C++) neumožňuje vícenásobnou dědičnost - řešením je použití rozhraní. Odvozování tříd je výkonný mechanismus, ale skutečnou sílu dědičnosti poznáte při odvozování rozhraní. Rozhraní můžeme chápat jako smlouva základní třídy s odvozenou třídou. Rozhraní v základní třídě deklaruje určité funkční členy a odvozená třída se zavazuje, že tyto funkční členy bude implementovat. Pokud tak neučiní, vznikne chyba již při překladu. Srovnání rozhraní s třídami: Třídy mají vlastnosti a metody a podle toho se chovají. Ve třídě není odděleno to, co se má dělat od toho, jak se to má dělat. Rozhraní toto oddělení co od jak poskytuje. Neobsahuje implementaci metody, ale pouze její název. Místo implementace metod pomocí rozhraní definujeme, jak má být objekt v určitém okamžiku implementován. Díky rozhraní lze vytvořit sadu souvisejících metod a vlastností, které je možné zahrnout do implementace vybrané třídy nezávisle na jejím umístění v dědičné hierarchii. 2.11. 2011 kapitola 11 18/22
Rozhraní je jakousi formou dohody mezi dvěma částmi kódu. Jakmile je jednou rozhraní definováno a je řečeno, že nějaká třída toto rozhraní implementuje, může se jakákoli klientská třída spolehnout na to, že skutečně bude obsahovat implementaci všech metod zahrnutých v daném rozhraní. Rozhraní jsou důležitou součástí v C# a mají významné postavení v programování založeném na komponentách. Rozhraní představuje definici chování, které třída implementuje. Rozhraní nikdy neobsahuje žádnou implementaci. Důsledkem toho jsou omezení: Nemůžete vytvořit instance rozhraní. V rozhraní nejsou povoleny žádné položky ani statické. V rozhraní nejsou povoleny žádné konstruktory. V rozhraní nemůžete psát modifikátor přístupu všechny položky jsou implicitně veřejné. Do rozhraní nemůžete vnořovat žádné typy (výčty, struktury, třídy, rozhraní, nebo zástupce) Není povoleno odvozovat rozhraní ze struktury nebo třídy Rozhraní vypadá jako definice třídy. Může obsahovat: metody vlastnosti indexery Žádná z těchto položek nemá tělo. Třída může dědit od jiné třídy, nebo od jednoho nebo i více rozhraní. Když třída dědí od rozhraní, musí implementovat všechny metody a vlastnosti definované v tomto rozhraní. Od rozhraní lze zdědit jen jména metod a vlastností, nikoliv konkrétní kód. Rozhraní pomáhají zobecnit třídy, neboť k definici proměnné mohou být místo jmen tříd nebo jmen struktur použita jména rozhraní. Třída pak může volat metody nebo vlastnosti, které jsou definované v rozhraní. Následující program ilustruje použití rozhraní Zvetseni, které je implementované v odvozené třídě KresleniCary. Bylo by možné definovat další odvozené třídy, které by implementovali rozhraní Zvetseni pro kružnici, čtverec a další geometrické útvary. Program 10 Implementace interface Zvetseni v odvozené třídě KresleniCary interface Zvetseni void ZvetseniX(int faktor); class KresleniCary : Zvetseni string cara; 2.11. 2011 kapitola 11 19/22
KresleniCary(string cara) this.cara = cara; void ZvetseniX(int faktor) string puvcara = cara; for (int i = 1; i < faktor; i++) cara += puvcara; void NakresliCaru() Console.WriteLine(cara); class Program static void Main() KresleniCary KC = new KresleniCary("-"); KC.ZvetseniX(2); KC.NakresliCaru(); for (int i = 1; i < 5; i++) KC.ZvetseniX(i); KC.NakresliCaru(); Následující program ilustruje použití více rozhraní. Program 11 Dvě rozhraní Máme definovány dvě rozhraní: IsourXY, Ivypis Implementaci rozhraní můžeme testovat operátorem is. interface IsourXY void NastavX(int x); int VratX(); void NastavY(int y); int VratY(); interface Ivypis void Vypis(); class sourxy :IsourXY, Ivypis int x; int y; void NastavX(int x) this.x = x; int VratX() return x; void ZobrazX() Console.WriteLine("0", x); void NastavY(int y) 2.11. 2011 kapitola 11 20/22
this.y = y; int VratY() return y; void ZobrazY() Console.WriteLine("0", y); void NastavXY() Console.SetCursorPosition(VratX(), VratY()); void Vypis() Console.WriteLine("Výpis textu na nastavenou pozici"); class Program static void Main() sourxy B = new sourxy(); if (B is IsourXY) Console.WriteLine("Je implementovano rozhrani IsourXY"); if (B is Ivypis) Console.WriteLine("Je implementovano rozhrani Ivypis"); B.NastavX(40); B.NastavY(12); B.NastavXY(); B.Vypis(); V programu je test, zda objekt B implementuje rozhraní IsourXY a Ivypis. Jestliže ano, jsou vypsány příslušné texty. 2.11. 2011 kapitola 11 21/22
Seznam obrázků Obrázek 1 Příklad UML diagramu, znázorňujícího dědičnost... 2 Obrázek 2 UMLdiagram, znázorňující dědičnost... 2 Obrázek 3 UML diagram příkladu dědičnosti třídy Maturant ze třídy Student... 4 Obrázek 4 UML diagram příkladu dědičnosti tříd osobni a nakladni ze třídy Vozidlo... 6 Obrázek 5 UML diagram pracovníků školy... 8 Obrázek 6 Špatné znázornění hierarchie zaměstnanců (šipky mají být otočené)... 8 Obrázek 7 Nevhodně použitá dědičnost... 9 Obrázek 8 UML diagram vícenásobné dědičnosti... 11 Obrázek 9 Příklad UML diagramu pro víceúrovňovou hierarchii tříd... 11 Seznam programů Program 1 Program ilustrující dědičnost třídy Y ze třídy X... 3 Program 2 Dědičnost: Maturant je student... 4 Program 3 Osobní i nákladní je vozidlo... 6 Program 4 Žárovka je světlo... 9 Program 5 Víceúrovňová hierarchie tříd... 12 Program 6 Víceúrovňová hierarchie - doplněno... 12 Program 7 Maturant je student a je osoba... 14 Program 8 Třídění student dle jména a průměru... 15 Program 9 Nalezení nejlepšího studenta... 16 Program 10 Implementace interface Zvetseni v odvozené třídě KresleniCary... 19 Program 11 Dvě rozhraní... 20 2.11. 2011 kapitola 11 22/22