PHP/Klasy i obiekty: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Znaczniki: Zastąpiono blanking
Linia 3:
jdjdjdjdjd
 
== ZakończenieKlasy i obiekty ==
W tym rozdziale nauczymy się, jak tworzyć klasy i obiekty w PHP.
Mamy już solidne podstawy programowania obiektowego, a także pokazaliśmy, w jaki sposób wykorzystuje się jego własności podczas tworzenia oskryptowania stron internetowych, pisząc modularny i łatwy w rozbudowie system konfiguracji. W następnym rozdziale pokażemy, jak sterować tworzeniem i niszczeniem obiektów.
 
{{Uwaga|Począwszy od tego miejsca będziemy stosować angielskie nazewnictwo w programowaniu obiektowym, gdyż jest ono podstawą wielu uznanych i powszechnie stosowanych konwencji.}}
 
resident sleeper
 
to tez spanie jest
we we starym se trzasnij haHAA
=== Kontrola dostępu (hermetyzacja) ===
W przeciwieństwie do funkcji i programowania strukturalnego, klasy posiadają precyzyjne mechanizmy kontroli dostępu do swojego wnętrza. Proces ukrywania części funkcjonalności przed programistą nosi nazwę ''hermetyzacji'' i pomaga zwiększyć niezawodność oprogramowania. Tworząc klasę, będziemy zawsze starali się określić tzw. publiczny interfejs, z którego może korzystać programista, odwołując się do tworzonych obiektów, jednocześnie ukrywając wszystkie wewnętrzne aspekty działania klasy. Interpreter będzie pilnować, aby użytkownicy nie wywołali żadnej "wewnętrznej" metody, co mogłoby doprowadzić do błędów w działaniu lub użycia jej niezgodnie z przeznaczeniem.
 
Poznaliśmy już jeden z modyfikatorów dostępu, '''public''', który podaliśmy przed każdą metodą oraz polem. Jest on wyjątkowy, ponieważ w przypadku metod można go pominąć, zostawiając samo <code>function nazwa()</code>, a w przypadku pól zastąpić synonimem '''var'''. My jednak będziemy stosować konwencję, w której zawsze jawnie określamy widzialność elementu.
 
Pozostałe modyfikatory dostępu to '''protected''' oraz '''private'''. Pierwszy z nich dotyczy dziedziczenia, dlatego zajmiemy się nim później, a tymczasem przyjrzymy się drugiemu z nich. Mówi on, że dane pole lub metoda jest prywatnym elementem klasy. W praktyce oznacza to, że odwołać się do niego możemy wyłącznie z poziomu innej metody w naszej klasie, a z zewnątrz jest on niedostępny. W naszych przykładach będziemy stosować konwencję, w której prywatne elementy będą posiadać nazwy zaczynające się od podkreślenia.
 
<source lang="php" line><?php
class SomeClass
{
private $_field;
 
public function getField()
{
// Poprawne odwołanie
return $this->_field;
} // end getField();
 
} // end SomeClass;
 
$someObject = new SomeClass;
 
// dostęp jest możliwy poprzez metodę
echo $someObject->getField();
 
// błąd, próba dostępu do pola prywatnego!
echo $someObject->_field;
</source>
 
Powyższy skrypt się nie wykona. W ostatniej linijce PHP zgłosi nam błąd, jakim jest próba dostania się do prywatnego elementu klasy spoza obiektu. Jednocześnie działa odwołanie umieszczone wewnątrz metody <code>getField()</code>. Taki rodzaj metody zwie się po angielsku ''getter'', a oprócz niego mamy też ''setter'', który pozwala ustawić wartość określonemu polu. Jest to jedna z konwencji stosowanych w hermetyzacji, w myśl której klasa nie powinna, poza szczególnymi wyjątkami, zawierać publicznych pól. Jeżeli użytkownik ma mieć dostęp do jakiegoś pola, musi to robić za pośrednictwem dodatkowych metod, czyli ''getterów'' i ''setterów''. Zauważmy, że takie podejście pozwala nam tworzyć pola dostępne publicznie tylko do odczytu bez możliwości ich modyfikacji. Wystarczy zadeklarować je jako prywatne i stworzyć publiczny ''getter'', bez ''settera''. Przepiszmy zatem naszą klasę '''Person''' zgodnie z regułami hermetyzacji, ograniczając nieco dostęp:
 
<source lang="php" line><?php
class Person
{
private $_name = null;
private $_surname = null;
public function setName($name)
{
if($this->_name === null)
{
$this->_name = $name;
return true;
}
return false;
} // end setName();
 
public function setSurname($surname)
{
if($this->_surname === null)
{
$this->_surname = $surname;
return true;
}
return false;
} // end setSurname();
public function getName()
{
return $this->_name;
} // end getName();
 
public function getSurname()
{
return $this->_surname;
} // end getSurname();
 
public function getFullName()
{
return $this->_name.' '.$this->_surname;
} // end getFullName();
} // end Person;
</source>
 
Teraz pola <code>$_name</code> oraz <code>$_surname</code> są prywatne, a ich wartość można odczytać wyłącznie poprzez ''gettery'' <code>getName()</code> oraz <code>getSurname()</code>. Dostępne są także analogiczne ''settery'' <code>setName()</code> oraz <code>setSurname()</code>, jednak zauważmy, że w praktyce można je wywołać tylko raz. Oznacza to, że gdy raz ustawimy danemu obiektowi nazwisko, nie jesteśmy w stanie go już później zmienić, gdyż metoda będzie zawsze zwracała wartość '''false''', odmawiając modyfikacji. Jest to typowy przykład kontroli dostępu i ma on szczególne znaczenie w dużych aplikacjach liczących sobie setki klas, gdzie umożliwia to wymuszenie stosowania się do określonych konwencji i pomaga zapobiegać bałaganowi w kodzie.
 
=== Praktyczne zastosowanie ===
Spróbujmy teraz napisać kod, który będzie przydatny w aplikacji WWW. Stworzymy prosty, obiektowy i rozszerzalny mechanizm konfiguracji, na przykładzie którego pokażemy kilka technik projektowania obiektowego. Będzie on składać się z dwóch klas:
 
# ''Config'' - zarządza konfiguracją i udostępnia ją skryptowi.
# ''ConfigLoader'' - rodzaj ładowarki, czyli klasy potrafiącej załadować skądś konfigurację.
 
Zacznijmy od napisania klasy głównej. Programista będzie mógł dodawać do niej różne ładowarki, a ona będzie udostępniała wczytaną konfigurację pozostałej części skryptu. Opcje konfiguracyjne będą wczytywane ''leniwie'', tj. gdy zajdzie taka potrzeba. Domyślnie ładowarka będzie jedynie kolejkowana w tablicy <code>$_awaitingLoaders</code>. Dopiero przy próbie odczytu nieistniejącej opcji, skrypt będzie prosić kolejne ładowarki o wczytanie swojej części konfiguracji, dopóki nie trafi na taką, która wczyta to, czego potrzebujemy.
 
<source lang="php" line><?php
 
class Config
{
private $_config = array();
private $_awaitingLoaders = array();
 
public function get($option)
{
// Jeśli podana opcja istnieje, zwróć jej wartość
if(isset($this->_config[$option]))
{
return $this->_config[$option];
}
 
// Opcja nie istnieje, sprawdzamy, czy któraś z oczekujących ładowarek ją ma.
foreach($this->_awaitingLoaders as $id => $loader)
{
$this->_config = array_merge($this->_config, $loader->load());
unset($this->_awaitingLoaders[$id]);
if(isset($this->_config[$option]))
{
return $this->_config[$option];
}
}
return null;
} // end get();
 
public function addLoader(ConfigLoader $loader)
{
$this->_awaitingLoaders[] = $loader;
} // end addLoader();
} // end Config;
</source>
 
Pojawił się tu nowy element składni: <code>public function addLoader(ConfigLoader $loader)</code> - przed nazwą zmiennej pojawiła się nazwa obiektu. Jest to jedno z kolejnych udoskonaleń programowania obiektowego w PHP, czyli określanie typów argumentów. Działa ono zarówno w metodach, jak i funkcjach i mówi, że dany argument może przyjąć wyłącznie obiekt klasy '''ConfigLoader'''. Próba podania dowolnego innego rodzaju wartości zakończy się błędem. W PHP określanie typów argumentów ograniczone jest wyłącznie do klasy, a od PHP 5.1 również do tablic (typ '''Array'''). Nie można wymuszać typów skalarnych (np. liczby całkowite) ani zasobów. W naszym przypadku daje to nam pewność, że programista nie będzie próbował nakarmić naszego systemu konfiguracji jakimiś dziwnymi danymi, które mogłyby doprowadzić do błędu.
 
Nasz system konfiguracji stosuje leniwe ładowanie opcji, lecz zwróćmy uwagę, że jego użytkownik wcale nie musi o tym wiedzieć. Dla niego korzystanie z tej klasy ogranicza się do pamiętania, że musi zdefiniować przynajmniej jedną ładowarkę oraz że dostęp do opcji możliwy jest do odczytu poprzez metodę <code>get()</code>. Sposób wczytywania opcji nie jest już przedmiotem jego zainteresowań, dlatego wewnętrzne aspekty działania klasy oraz faktyczny sposób reprezentacji danych został przed nim ukryty, by nie mógł przy nim majstrować.
 
Napiszmy teraz ładowarkę, która będzie wczytywać opcje z pliku INI:
 
<source lang="php" line><?php
 
class ConfigLoader
{
private $_fileName = '';
 
public function setFilename($filename)
{
$this->_fileName = $filename;
} // end setFilename();
 
public function load()
{
if(file_exists($this->_fileName))
{
return parse_ini_file($this->_fileName);
}
 
// jeśli pliku nie ma, zwróć pustą tablicę
return array();
} // end load();
} // end ConfigLoader;
</source>
 
Tu również zastosowaliśmy hermetyzację. Można, a nawet trzeba ustawić nazwę pliku, który chcemy odczytać, jednak nie musi być ona później dostępna, dlatego pominęliśmy całkowicie ''getter''. Metoda ''load()'' wywoływana przez naszą klasę '''Config''' musi zwrócić tablicę z opcjami wczytanymi z pliku.
 
To wszystko, spójrzmy teraz, jak wykorzystać nasz system w praktyce:
 
<source lang="php" line><?php
require('./Config.php');
require('./ConfigLoader.php');
 
$config = new Config;
 
// utworz ladowarki wczytujace rozne fragmenty konfiguracji
$basicConfig = new ConfigLoader;
$basicConfig->setFilename('./config/basic.ini.php');
 
$securityConfig = new ConfigLoader;
$securityConfig->setFilename('./config/security.ini.php');
 
$layoutConfig = new ConfigLoader;
$layoutConfig->setFilename('./config/layout.ini.php');
 
$config->addLoader($basicConfig);
$config->addLoader($securityConfig);
$config->addLoader($layoutConfig);
 
// zaladujmy pare opcji
echo $config->get('website_name');
echo $config->get('session_time');
</source>
 
Naszemu systemowi brakuje wciąż parę rzeczy. Przykładowo, można by pokusić się o zrobienie różnych rodzajów ładowarek: z plików, z bazy danych itd., lecz do tego potrzebna jest nam znajomość dziedziczenia. Ponadto przydałoby się, aby system potrafił odpowiednio raportować błędy. Póki co, jeśli pomylimy się w nazwie pliku konfiguracji, dowiemy się o tym dopiero po wnikliwym śledztwie, ponieważ jedynym śladem w ładowarce jest zwrócenie pustej tablicy. Odpowiednie mechanizmy poznamy w dalszej części podręcznika.
 
Stworzony system konfiguracji wykorzystuje jeszcze jedną technikę programistyczną o nazwie '''kompozycja'''. Jest to alternatywny do dziedziczenia sposób rozszerzania funkcjonalności obiektów, który jednak nie wymaga żadnej dodatkowej składni. Obrazowo mówiąc, polega on na tym, że jeden obiekt przechowuje referencje do innych obiektów, które potrafi wykorzystywać lub których funkcjonalność potrafi udostępnić na zewnątrz. W przeciwieństwie do dziedziczenia, kompozycja ma charakter dynamiczny. Możemy napisać algorytm, który w locie skomponuje nam gotowy obiekt, niczym z klocków lego, np. na podstawie konfiguracji lub opisu w bazie danych. Kompozycja jest bardzo często stosowana w praktyce obok dziedziczenia.