W
poprzednim tygodniu opowiedzieliśmy sobie o dwóch bardzo ważnych pojęciach
jakimi niewątpliwie są: dziedziczenie oraz polimorfizm. Wprowadziliśmy sobie
również definicje metod wirtualnych i przesłaniających, a także poznaliśmy nowe,
ważne słówko kluczowe: base.
Dzisiaj na
pewno każdy z nas potrafiłby wykorzystać swoją wiedzę na powyższe tematy,
dlatego też czym prędzej przejdźmy do przykładu:
public class Figura
{
protected double a;
protected double b;
public Figura(double
a, double b)
{
this.a = a;
this.b = b;
}
public virtual double PoleFigury()
{
System.Console.WriteLine("Niezdefiniowana
figura!");
return 0;
}
}
public class Prostokat : Figura
{
public Prostokat(double
a, double b) : base(a,
b)
{ }
public override double PoleFigury()
{
double pole = a * b;
System.Console.WriteLine("Obliczamy pole prostokąta.");
return pole;
}
}
public class Trojkat : Figura
{
public Trojkat(double
a, double b) : base(a,
b)
{ }
public override double PoleFigury()
{
double pole = (a * b) / 2;
System.Console.WriteLine("Obliczamy pole trójkąta.");
return pole;
}
}
class Glowna
{
static void Main(string[] args)
{
//deklarujemy
odpowiednie obiekty wraz z wartosciami ich zmiennych
Figura
f = new Figura(5,
6);
Prostokat
p = new Prostokat(7,
8);
Trojkat
t = new Trojkat(9,
10);
//wywolujemy
metode obliczajaca pole odpowiedniej figury
System.Console.WriteLine("Pole wynosi więc: {0}", f.PoleFigury());
System.Console.WriteLine("Pole wynosi: {0}", p.PoleFigury());
System.Console.WriteLine("Pole wynosi: {0}", t.PoleFigury());
}
}
Powyższy przykład jest dla nas przypomnieniem podstawowej
wiedzy na temat dziedziczenia oraz polimorfizmu. Po skompilowaniu otrzymamy
następujące wyniki:
Powyższy program jest bardzo dobrym przykładem, aby
wytłumaczyć sobie pewną sytuację. A mianowicie taką, w której chcielibyśmy
zadeklarować klasę bazową, która to z kolei byłaby wspólna dla pozostałych
(klasa Prostokat oraz klasa Trojkat dziedziczą po klasie bazowej: Figura, a więc Figura jest dla pozostałych wspólna) oraz dawałaby możliwość
uzupełnienia szczegółów (metoda PoleFigury()
w klasie Figura nie powinna obliczać
żadnego pola, a jedynie powinna zostać odziedziczona przez klasy pochodne od
klasy Figur i tam dopiero obliczać
odpowiednie wartości). Mówiąc jaśniej, rozważmy klasę Prostokat w powyższym kodzie. W klasie tej przesłoniliśmy
odziedziczoną od klasy Figura metodę PoleFigury(), która w tej klasie oblicza
po prostu pole prostokąta. Dla tej klasy nie ma znaczenia, że metoda PoleFigury() nie jest wcześniej
zdefiniowana (w naszym przypadku w klasie Figura).
Ma natomiast znaczenie, aby klasa pochodna przesłoniła wszystkie konieczne
metody (w tym przypadku właśnie metodę obliczającą pole prostokąta). Aby
uzyskać taki mechanizm musimy użyć klas
oraz metod abstrakcyjnych.
Innymi słowy, aby wymusić utworzenie nowej wersji metody
klasy bazowej, należy tę metodę oznaczyć jako metodę abstrakcyjną. Metoda taka
nie zawiera kodu, a jedynie w swej definicji ma: nazwę oraz sygnaturkę. Dopiero
w klasach pochodnych należy dodać do takiej metody kod. Jeżeli klasa posiada co
najmniej jedną metodę abstrakcyjną, to taką klasę nazywamy klasą abstrakcyjną.
Aby zadeklarować metodę musimy użyć modyfikatora: abstract w następujący sposób:
abstract public void
MojaMetoda();
Ponieważ metoda abstrakcyjna nie posiada kodu, po jej
sygnaturze nie ma nawiasów klamrowych, a jej definicja kończy się średnikiem.
Aby zadeklarować klasę abstrakcyjną, również używamy słówka:
abstract jak poniżej:
abstract class
MojaKlasa
{ }
Jak zostało wyżej napisane, klasa abstrakcyjna stanowi jak
gdyby bazę metod dla wszystkich jej klas pochodnych, ale – co bardzo ważne – w
programie nie wolno nam tworzyć instancji tej klasy. Próba zdefiniowania
obiektu klasy abstrakcyjnej zakończy się niepowodzeniem, dlatego też, jeśli
jakaś klasa zawiera choć jedną metodę abstrakcyją, to już nie będziemy mogli
się do niej odwoływać przez obiekt tej klasy a jedynie poprzez mechanizm dziedziczenia
oraz polimorfizmu.
Prześledźmy poniższy przykład:
abstract public class Pierwsza
{
abstract public void
Abstrakcja();
public void Rzeczywistosc()
{
System.Console.WriteLine("Moja najzwyklejsza metoda.");
}
}
public class Druga : Pierwsza
{
public override void
Abstrakcja()
{
System.Console.WriteLine("Moja pierwsza przesłonięta metoda
abstrakcyjna!");
}
}
class Glowna
{
static void Main(string[] args)
{
Druga
dr = new Druga();
dr.Abstrakcja();
dr.Rzeczywistosc();
}
}
Klasa Pierwsza
implementuje abstrakcyjną metodę Abstrakcja(),
która w swej definicji posiada jedynie nazwę oraz sygnaturkę. Klasa ta posiada
więc co najmniej jedną abstrakcyjną metodę, stąd jest klasą abstrakcyjną. Klasę
tę dziedziczy klasa Druga, w której
przesłaniamy metodę abstrakcyjną i dodajemy jej kod. Zauważmy, że w powyższym
przykładzie nie ma definicji obiektu klasy pierwszej, ponieważ klasa ta jest
abstrakcyjna. Drugi ważny wniosek do zapamiętania: klasa pochodna po klasie
abstrakcyjnej musi przesłonić wszystkie metody abstrakcyjne jakie ona posiada.
Po uruchomieniu powyższego programu otrzymamy następujące
wyniki:
Znając definicję oraz sposób deklarowania klas oraz metod
abstrakcyjnych, spróbujmy udoskonalić pierwszy przykład z dzisiejszego artykułu
poprzez zdefiniowanie między innymi klasy Figura
jako klasy abstrakcyjnej:
//deklaracja abstrakcyjnej klasy
abstract public class Figura
{
protected double a;
protected double b;
public Figura(double
a, double b)
{
this.a = a;
this.b = b;
}
//deklaracja
abstrakcyjnej metody
abstract public double
PoleFigury();
}
public class Prostokat : Figura
{
public Prostokat(double
a, double b)
:
base(a, b)
{ }
//przesłoniece
abstrakcyjnej klasy
public override double PoleFigury()
{
double pole = a * b;
System.Console.WriteLine("Obliczamy pole prostokąta.");
return pole;
}
}
public class Trojkat : Figura
{
public Trojkat(double
a, double b)
: base(a,
b)
{ }
//przesloniecie
abstrakcyjnej metody
public override double PoleFigury()
{
double pole = (a * b) / 2;
System.Console.WriteLine("Obliczamy pole trójkąta.");
return pole;
}
}
class Glowna
{
static void Main(string[] args)
{
//deklarujemy
odpowiednie obiekty wraz z wartosciami ich zmiennych
Prostokat
pr = new Prostokat(5,
6);
Prostokat
p = new Prostokat(7,
8);
Trojkat t = new Trojkat(9, 10);
//wywolujemy
metode obliczajaca pole odpowiedniej figury
System.Console.WriteLine("Pole wynosi więc: {0}",
pr.PoleFigury());
System.Console.WriteLine("Pole wynosi: {0}", p.PoleFigury());
System.Console.WriteLine("Pole wynosi: {0}", t.PoleFigury());
}
}
Klasa Figura stała
się klasą abstrakcyjną, która posiada abstrakcyjną metodę: PoleFigury(), która to dopiero w klasach pochodnych zostanie
odpowiednio zaimplementowana do obliczania pól odpowiednich figur. Oczywiście
nie jest możliwe utworzenie obiektu klasy abstrakcyjnej, gdyż kompilator
zwróciłby nam błąd. Po prawidłowym skompilowaniu orztymamy następujące wyniki:
W
dzisiejszym artykule powiedzieliśmy sobie o klasach i metodach abstrakcyjnych,
które ściśle wiążą się z mechanizmami dziedziczenia oraz polimorfizmu.
Dowiedliśmy, że klasy abstrakcyjne nie są jedynie sztuczką programowania
obiektowego, a reprezentują w pełni tego słowa znaczeniu „abstrakcję”, która
umożliwia utworzenie kontraktu dla wszystkich klas pochodnych. Innymi słowy,
klasy abstrakcyjne są opisem publicznych metod klas pochodnych, które to
rozwijają abstrakcję. Alternatywą dla klas abstrakcyjnych są interfejsy, o
których już niedługo sobie opowiemy. Za tydzień natomiast będziemy kontynuować
temat klas w języku C# 2.0.