Artykuł

sty 13 2009
0

Programowanie obiektowe - dziedziczenie

Tematem dzisiejszego artykułu z cyklu Programowanie obiektowe będzie dziedziczenie - drugi z trzech najważniejszych elementów paradygmatu programowania obiektowego. Dziedziczenie jest niezwykle ważnym pojęciem ponieważ pozwala na tworzenie klas, które mogą być tworzone na podstawie innych wcześniej zdefiniowanych klas. Dzięki temu nowa klasa posiada funkcjonalność klasy, która stoi wyżej w hierarchii, a także nową funkcjonalność zdefiniowaną tylko dla niej (lub ewentualnie dla innych klas, które odziedziczą po niej). Dzięki takiemu podejściu tworzymy w pełni hierarchiczny kod, który możemy w prosty sposób rozszerzać i dostosowywać do naszych potrzeb.

Istota dziedziczenia

Jeśli kogoś z was nie przekonały argumenty przedstawione we wstępie do tego artykułu, zapraszam do zapoznania się ze schematycznym rysunkiem przedstawionym na screenie 1.

Screen 1 przedstawia przykład motoryzacyjny. Na rysunku widzimy hierarchię klas. Na najwyższym miejscu znajduję się klasa Samochód. Klasa ta posiada pewne unikalne cechy i metody. Na jej podstawie zbudowano klasy Osobowy i Ciężarówka, które dokładają kolejne specyficzne cechy i metody rozszerzając tym samym funkcjonalność klasy bazowej. W kolejnym kroku dziedziczymy z klasy Osobowy. Powstają dwie kolejne klasy: Sedan i Kombi. Jak dobrze wiadomo samochody o różnych typach nadwozia posiadają odrębne parametry. Wiąże się to z dodaniem nowych specyficznych metod i zmiennych. Itd.

Reasumując, dziedziczenie jest ważne w momencie gdy zwiększamy poziom szczegółowości badanego zjawiska. Zawsze zaczynamy od opisania najbardziej ogólnego modelu (klasy), a w kolejnych krokach zgłębiając się w coraz większy poziom szczegółowości dziedzicząc zdefiniowane wcześniej, najważniejsze elementy. Używając dziedziczenia oszczędzamy czas i w znaczący sposób zmniejszamy ilość potrzebnego do działania aplikacji kodu źródłowego.

Dwa kolejne akapity poświęcone będą dziedziczeniu w praktyce. Ponieważ dziedziczenia dla PHP i dla Javy przebiega odrobinę w odmienny sposób postanowiłem rozdzielić te akapity ze względu na dany język. Przed przystąpieniem do analizy tych dwóch akapitów zachęcam się do zapoznania z poprzednimi artykułami poświęconymi programowaniu obiektowemu.

Dziedziczenia a hermetyzacja

Pisząc hierarchiczny kod, oparty na dziedziczeniu, należy pamiętać o znaczeniu w programowaniu obiektowym hermetyzacji. Jak zapewne pamiętacie z poprzedniego artykułu to czy klasa może być odziedziczona, jest właśnie uwarunkowane za pomocą modyfikatorów dostępu. Dla przypomnienia dziedziczyć możemy klasy oznaczone jako public, protected lub o modyfikatorze domyślnym. Nie możliwe jest natomiast, dziedziczenie klas prywatnych. Należy pamiętać o tej niezwykle ważnej informacji podczas tworzenia kodu naszych aplikacji.

Dziedziczenie w praktyce - PHP

Ponieważ wyżej skupiłem się tylko na teoretycznym ujęciu problemu, czas przejść do praktyki. Przyda się nam kod klasy Human, która tworzyliśmy w dwóch poprzednich artykułach z cyklu programowanie obiektowe (wprowadzenie i hermetyzacja). Na podstawie wspomnianej klasy utworzymy nową, którą nazwiemy Male (mężczyzna). Klasa Male będzie dziedziczyć z klasy Human wszystkie jej metody i zmienne składowe. Dlatego już sam szkielet klasy Male:

<?php
require_once('Human.php');
class Male extends Human
{
}
?>

jest w pełni funkcjonalny i pozwala na tworzenie działających obiektów, które jak na razie będą typu Male, ale posiadać będą funkcjonalność klasy Human:

<?php
$oMark = new Male('Marek','Brunet',25,185);
echo $oMark;
?>

Powyższy kod tworzy obiekt oMark typu Male. Choć w teorii klasa Male jest pusta, posiada jednak funkcjonalność klasy Human. Dlatego też było możliwe skorzystanie ze zdefiniowanego wcześniej konstruktora czy też metody __toString. Oczywiście można korzystać również ze wszystkich innych metod w tym także metody __get i __set (pamiętajmy, że korzystamy z nich niejawnie):

$oMark->iAge = 15;
echo "<br />".$oMark->iAge;

Powyższy kod również zadziała zgodnie z naszymi przewidywaniami, czyli zmienimy wiek Marka.

Przedstawiliśmy już jak korzystać z odziedziczonego kodu, ale istotą jest jego sensowne rozszerzanie. Dlatego zdefiniujemy nową wersję klasy Male wzbogaconą o nową zmienną klasową Kolor oczu. Wprowadzenie nowej zmiennej wiązać się będzie z modyfikacją konstruktora oraz metody __toString.

<?php
require_once('Human.php');
class Male extends Human
{
	private $sEyes = '';
	public function __construct($sName, $sHairs, $iAge, $iHeight, $sEyes)
	{
		parent::__construct($sName, $sHairs, $iAge, $iHeight);
		$this->sEyes = $sEyes;
	}
	
	public function __toString()
	{
		return(parent::__toString().', Kolor oczu: '.$this->sEyes);
	}
}
?>

Powyższy kod przedstawia nową, wypełnioną wersję klasy Male. Utworzyliśmy tutaj nowe wersja konstruktora i metody __toString. Warto zwrócić na kluczowe wyrażenie ::parentSygnaturaFunkcjiDziedziczonej. Pozwala ono odziedziczyć funkcjonalność określonej metody. Dzięki temu w szybki sposób przejeliśmy poprzednią zawartość naszych metod, które możemy teraz w prosty sposób roszerzyć o obsługę koloru oczu. Aby przetestować działanie nowego kodu, należy wykonać następujące polecenia:

<?php
$oMark = new Male('Marek','Brunet',25,185, 'Piwne');
echo $oMark;
?>

Oczywiście nic nie stoi na przeszkodzie aby w nowej klasie, która dziedzyczy po określonej innej klasie pisać całkowicie nową wersję konstruktora, metody __toString itd. Jednak mija się to z celowością dziedziczenia w takim przypadku. Odpowiednia hierarchia i struktura klas zapewnią niebywałe oszczędności w ilości napisanych linii kodu.

Uwaga. W tej chwili wykorzystaliśmy konstrukcję parent::, jednak być może w różnych innych okolicznościach potrzebny może się okazać dostęp do prywatnych składowych klasy. W takim wypadku, aby były one dostępne dla potomków, powinny być zadeklarowane jako protected.

Dziedziczenie w praktyce - Java

W wypadku dziedziczenia, w Javie panują odrobinę inne, bardziej surowsze wymagania. Prześledzmy przykładowe operacje. Na początku musimy zmodyfikować kod znajdujący się w klasie Human, chodzi konkretnie o poniższe linie:

private String sName = "";
private String sHairs = "";
private int iAge = 0;
private int iHeight = 0;

Powyższy kod należy zastąpić następującym:

protected String sName = "";
protected String sHairs = "";
protected int iAge = 0;
protected int iHeight = 0;

Dzięki takiej modyfikacji klasa Male posiadać będzie dostęp do elementów składowych klasy Human, a jednocześnie te elementy dalej posiadać będą prywatny charakter (specjalna właściwość modyfikatora protected). Utwórzmy teraz kod klasy Male, która posiadać będzie podobną funkcjonalność jak klasa Male, którą pisaliśmy w PHP:

public class Male extends Human
{
	private String sEyes = "";
	public Male()
	{
		super();
	}
	
	public Male(String sName, String sHairs, int iAge, int iHeight, String sEyes)
	{
		super(sName, sHairs, iAge, iHeight);
		this.sEyes = sEyes;
	}
	
	public String toString()
	{
		return("Imię: "+this.sName+", Kolor włosów: "+super.sHairs+
				", Wiek: "+super.iAge+", Wzrost: "+super.iHeight+ 
				", Kolor oczu: "+this.sEyes);
	}
}

Pierwsza rzeczą, która z pewnością rzuci się każdemu w oczy jest metoda super, która posiada dwa kluczowe zastosowania:

  1. Pozwala na dziedziczenie konstruktorów z klasy bazowej.
  2. Wywołana w sposób super.składowa_klasy_pierwotnej, pozwala uzyskać dostęp do wszystkich elementów, które zostały zadeklarowane jako public (lub modyfikator domyślny) i protected. Dlatego zmieniliśmy typ naszych zmiennych z private na protected, aby umożliwić ich dziedziczenie jednocześnie zapewniając odpowiednie bezpieczeństwo naszej aplikacji.

Wróćmy jednak do analizy powyższego listingu. Nasza klasa Male posiada ponownie dwa konstruktory. Pierwszy konstruktor Male jest bezparametrowy i zachowuje się identycznie jak jego pierwowzór z klasy Human (zapewnia to metoda super wywołana w środku konstruktora). Drugi konstruktor, został wzbogacony o kolejny parametr czyli zmienną sEyes odpowiadającą za kolor oczu. Wykorzystując słowo super wraz z parametrami (nazwy czterech zmiennych, które występowały w klasie Human), odziedziczyliśmy pierwotny konstruktor. Ponadto, dodaliśmy do niego naszą nową zmienną.

W kolejnym kroku utworzyliśmy nową metodę toString. Wykorzystaliśmy słowo kluczowe super aby odczytać zmienne typu protected. Ponadto dodaliśmy obsługę zmiennej sEyes.

Aby przetestować działanie klasy Male, utworzymy testową klasę MaleTest:

public class MaleTest extends Human
{
	public static void main(String[] args) 
	{
		Male oMark = new Male();
		System.out.println(oMark);
		Male oMark2 = new Male("Marek","Brunet",25,185,"Piwne");
		System.out.println(oMark2);
	}
}

Wewnątrz metody main, tworzymy dwa konstruktory. Pierwszy z nich jest bezparametrowy, drugi posiada wszystkie pięć parametrów. Wyświetlamy obydwa obiekty na ekranie. Jeśli wszystko poszło dobrze, powinniśmy otrzymać mniej więcej taki rezultat:

Konstruktor bezparametrowy
Imię: , Kolor włosów: , Wiek: 0, Wzrost: 0, Kolor oczu: 
Imię: Marek, Kolor włosów: Brunet, Wiek: 25, Wzrost: 185, Kolor oczu: Piwne

Wszystko poszło idealnie po naszej myśli. Warto zaznaczyć, że pisząc nową metodę __toString dla klasy Male powodujemy wtedy przesłonięcie istniejącej metody w klasie Human. Jest to bardzo popularne zjawisko w programowaniu obiektowym. Należy jednak zawsze zwrócić uwagę na zgodności typów według, których tworzymy obiekty.

Klasy abstrakcyjne

Pisząc o dziedziczeniu nie można zapomnieć o klasach abstrakcyjnych. Zapewne większość z Was zada sobie teraz pytanie co to jest klasa abstrakcyjna? Już spieszę z odpowiedzią. Jest to specjalna klasa, która posiada ramową strukturę, mającą pełnić fundament dla innych klas. W klasie abstrakcyjnej możemy definiować normalne metody, deklarować zmienne, ale również możemy tworzyć nagłówki metod (do takich metod należy dodać słowo kluczowe abstract w ich sygnaturze), które potem zostaną rozszerzone w nowych klasach potomkach. Aby odróżnić taką klasę od zwykłej klasy, musimy dodać słowo kluczowe abstract. Ważna uwaga: nie możemy tworzyć obiektów klas abstrakcyjnych, możemy je tylko dziedziczyć.

Klasy abstrakcyjne w PHP

Zdefiniujmy przykładową klasę HumanAbstract, która pozwoli ujrzeć aspekty klas abstrakcyjnych w praktyce:

<?php
abstract class HumanAbstract
{
	protected $sName;
	abstract function printName();
	protected function getName()
	{
		return($this->sName);
	}
	protected function setName($sName)
	{
		$this->sName = $sName;
	}
}
?>

Na początku deklarujemy zmienną typu protected, którą będziemy używać w naszych metodach. W kolejnym kroku deklarujemy abstrakcyjną metodę printName, która zostanie rozszerzona w klasach potomkach. Potem definiujemy dwie metody typu get i set. Modyfikator protected zapewni ich dziedziczenie.

Klasę dziedziczymy w standardowy sposób:

class Human extends HumanAbstract
{
	//
}

Uwaga: należy pamiętać o wypełnieniu treścią abstrakcyjnych metod.

Klasy abstrakcyjne w Javie

Napiszemy teraz klasę HumanAbstract, z funkcjonalnością do tej jaką posiada analogiczna klasa napisana wyżej w PHP.

abstract class AbstractHuman 
{
	protected String sName;
	abstract void printName();
	protected String getName()
	{
		return(this.sName);
	}
	
	protected void setName(String sName)
	{
		this.sName = sName;
	}
}

Na początku deklarujemy zmienną protected odpowiedzialną za przechowywanie imienia (zmienna może zostać dziedziczona). W kolejnym kroku tworzymy abstrakcyjną metodę printName, która zostanie rozszerzona w przyszłości. W ostatnim kroku tworzymy metody get i string, typu protected, które będą operować na zmiennej sName.

Klasę dziedziczymy w standardowy sposób:

class Human extends HumanAbstract
{
	//
}

Uwaga: należy pamiętać o wypełnieniu treścią abstrakcyjnych metod.

Podsumowanie

Dziedziczenie jest niezwykle ważnym, ale i skomplikowanym zagadnieniem. Korzystając z tego aspektu programowania obiektowego, zmniejszymy z pewnością ilość powtarzanego kodu. Należy jednak pamiętać o bezpieczeństwie. Jeśli elementy mają być dziedziczone, ale powinny zachować ukryty charakter, należy im nadać modyfikator protected. Powyższy artykuł nie wyczerpuje wszystkich aspektów dziedziczenia. Przedstawia jednak najważniejsze informacje i techniki używane w programowaniu obiektowym w PHP i Javie.

Data ostatniej modyfikacji: 05.06.2011, 17:18.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus