2010-12-13 21:55:34

Metoda, która nie istnieje…

Kilka lat temu, gdy nie znaÅ‚em jeszcze wszystkich tajników programowania obiektowego, zlecono mi modyfikacjÄ™ pewnego skryptu napisanego wÅ‚aÅ›nie obiektowo. Klasy, obiekty i metody nie byÅ‚y dla mnie zupeÅ‚nÄ… nowoÅ›ciÄ…, wiÄ™c zabraÅ‚em siÄ™ do pracy z nastawieniem, że szybko wykonam zlecenie, wezmÄ™ kasÄ™ i… Już teraz nie pamiÄ™tam na co chciaÅ‚em wydać te pieniÄ…dze ;) .

Zlecenie wydawało się dziecinnie proste. Przynajmniej do czasu, za nim nie wgłębiłem się w kod. Skrypt był podobny do tego poniżej: na początku definicje kilku klas, a później program, który ich używał. Klasy nie wzbudziły moich podejrzeń. Za to później trafiłem, na coś, czego zupełnie nie mogłem zrozumieć.

W poniższym kodzie, symulującym kilka kolejnych zakupów i sprzedaży złota przy zmieniającym się kursie, umieściłem coś podobnego. Chodzi mi o metody kup() i sprzedaj() wywoływane na obiekcie $zloto klasy Gold. Tyle że w klasie Gold nie ma definicji metod kup() i sprzedaj(). Klasa Gold nie rozszerza też żadnej innej klasy, po której mogłaby te metody odziedziczyć. O co więc chodzi?

Najdziwniejsze byÅ‚o to, że ten kod dziaÅ‚aÅ‚. Nie zgÅ‚aszaÅ‚ żadnych bÅ‚Ä™dów. W pewnym momencie zaczÄ…Å‚em podejrzewać mój komputer o magiÄ™ i jak siÄ™ okazaÅ‚o, w pewnym sensie trafiÅ‚em. RozwiÄ…zaniem mojej zagadki byÅ‚a metoda _call() należąca do grupy tzw. metod magicznych, które odpowiadajÄ… za wÅ‚aÅ›nie takie „niezwykÅ‚e” zachowanie programu.

Zajmijmy się jednak poniższym przykładem.

<?php
class Gold
{
	const NAZWA='zloto';
	private $cena=128.4;
 
	public function kurs()
	{
		return $this->cena;
	}
 
	public function __call($n, $p)
	{
		$this->zmien_kurs();
		return array('koszt' => $p[0]*$this->cena, 'towar' => array('nazwa' => self::NAZWA, 'ilosc' => $p[0]));
	}
 
	private function zmien_kurs()
	{
		$zmiana=rand(1, 200)/100;
 
		switch(rand(1, 2))
		{
			case '1': $this->cena+=$zmiana;
			break;
			case '2': $this->cena-=$zmiana;
			break;
		}
	}
}
 
class Portfel
{
	private $kasa=0;
	private $inwestycje=array();
 
	public function __construct($kasa)
	{
		$this->kasa=$kasa;
	}
 
	public function sprawdz()
	{
		return array_merge(array('pieniadze' => $this->kasa), $this->inwestycje);
	}
 
	public function transakcja($inwestycja)
	{
		$this->kasa-=$inwestycja['koszt'];
		$this->inwestycje[$inwestycja['towar']['nazwa']]+=$inwestycja['towar']['ilosc'];
	}
}
 
$portfel= new Portfel(10000);
$zloto= new Gold;
 
echo '<br />Sprawdzenie:<br />';
foreach($portfel->sprawdz() as $key => $value)
{
	echo $key.': '.$value.'<br />';
}
 
//kupuję 24 sztabki złota
$portfel->transakcja($zloto->kup(24));
echo '<br />Aktualny kurs: ';
echo $zloto->kurs();
 
echo '<br />Sprawdzenie:<br />';
foreach($portfel->sprawdz() as $key => $value)
{
	echo $key.': '.$value.'<br />';
}
 
//sprzedaję 32 sztabki złota
$portfel->transakcja($zloto->sprzedaj(-32));
echo '<br />Aktualny kurs: ';
echo $zloto->kurs();
 
echo '<br />Sprawdzenie:<br />';
foreach($portfel->sprawdz() as $key => $value)
{
	echo $key.': '.$value.'<br />';
}
 
$portfel->transakcja($zloto->kup(53));
echo '<br />Aktualny kurs: ';
echo $zloto->kurs();
 
echo '<br />Sprawdzenie:<br />';
foreach($portfel->sprawdz() as $key => $value)
{
	echo $key.': '.$value.'<br />';
}
 
$portfel->transakcja($zloto->sprzedaj(-20));
echo '<br />Aktualny kurs: ';
echo $zloto->kurs();
 
echo '<br />Sprawdzenie:<br />';
foreach($portfel->sprawdz() as $key => $value)
{
	echo $key.': '.$value.'<br />';
}
 
$portfel->transakcja($zloto->sprzedaj(-25));
echo '<br />Aktualny kurs: ';
echo $zloto->kurs();
 
echo '<br />Sprawdzenie:<br />';
foreach($portfel->sprawdz() as $key => $value)
{
	echo $key.': '.$value.'<br />';
}
?>

Metody sprzedaj() lub kup() są wywoływane na obiekcie $zloto z przekazanym do nich argumentem typu int. Dla metody kup() argument ma zawsze wartość dodatnią i oznacza ilość kupionego towaru (dodatnia wartość oznacza, że zyskaliśmy tyle sztuk tego towaru), natomiast dla metody sprzedaj() argument ma wartość ujemną (pozbywamy się towaru). Wynik działania tych metod przekazywany jest do metody transakcja() wywołanej na obiekcie $portfel klasy Portfel.

Ponieważ metody sprzedaj() i kup() nie zostaÅ‚y zdefiniowane w klasie Gold, obsÅ‚ugÄ… tych wywoÅ‚aÅ„ zajmie siÄ™ metoda magiczna __call() Metoda przyjmuje dwa argumenty $n i $p. W pierwszym argumencie, w tym akurat przypadku nieużywanym, jest dostÄ™pna nazwa wywoÅ‚ywanej metody. Natomiast drugi argument jest tablicÄ… argumentów, z jakimi metoda zostaÅ‚a wywoÅ‚ana. Metody sprzedaj() i kup() sÄ… wywoÅ‚ywane tylko z jednym argumentem wiÄ™c $p jest tablicÄ… jednoelementowÄ…. WywoÅ‚ujÄ…c metody sprzedaj() lub kup() robimy praktycznie to samo – zmieniamy ilość pieniÄ™dzy i zÅ‚ota w portfelu. To czy ta zmiana jest na plus czy na minus zależy od wartoÅ›ci argumentu, wiÄ™c nazwy metod sÄ… zbÄ™dne.

Na dobrą sprawę moglibyśmy metodę __call() zastąpić np. metodą kupno_sprzedaz(), którą wywoływalibyśmy zupełnie jawnie w kodzie i nie byłoby tego całego zamieszania. Jednak dzięki użyciu metod sprzedaj() i kup() kod jest bardziej intuicyjny (pod warunkiem, że zna się metody magiczne). Poza tym dzięki różnym nazwom moglibyśmy zmodyfikować metodę __call() w taki sposób, by przy transakcji sprzedaży doliczana byłaby marża, a przy kupnie nie. Wystarczyłoby sprawdzić wówczas wartość argumentu $n. Zresztą możliwości jest dużo więcej, a __call() to tylko jedna z metod magicznych. Warto znać je wszystkie.

Pisząc ten artykuł postawiłem sobie zadanie przybliżenia pojęcia metod magicznych na przykładzie metody __call(). Gdyby jednak inne elementy przykładowego programu były dla Ciebie nie do końca zrozumiałe, napisz o tym w komentarzu poniżej. Chętnie wszystko wyjaśnię.

Tagi: , ,

Dodaj odpowiedź

Musisz się zalogować aby dodać komentarz.