Autor wpisu: sokzzuka, dodany: 16.04.2011 10:38, tagi: php
Jakiś czas temu, zainspirowany artykułem Giorgio Sironiego - „How to remove getters and setters” rozpocząłem dyskusję na forum goldenline na temat przydatności getterów i setterów. Dyskusja była dość burzliwa, jak to zawsze ma miejsce, gdy próbuje się naruszać dogmaty. Jednak wszystko przebiegło w na tyle przyjaznej atmosferze, że nie skończyło się na „flejmłorze” i padło kilka interesujących argumentów. Teraz nadszedł czas by z perspektywy czasu podsumować tą dyskusje i przedstawić wnioski, do jakich doszedłem po jej zakończeniu. Dla tych, którym się nie chce czytać artykułu, szybki szkic problematyki – Giorgio wysunął śmiałą tezę, że stosowanie „getterów i setterów” psuje enkapsulację, która jest jedną z podstaw programowania zorientowanego obiektowo. W swoim artykule opisuje on sposoby na całkowite pozbycie się tych często używanych artefaktów.
Jako, że zgadzam się z główną tezą artykułu Giorgia, w dalszej części artykułu zamierzam zaprezentować przypadki w których użycie getterów bądź setterów jest szkodliwe, wraz z uzasadnieniem i przykładowym rozwiązaniem problemu. Pokaże również, przypadki w których zastosowanie tych konstrukcji ma sens. Abyśmy się jednak dobrze zrozumieli, najpierw moja definicja – czym są gettery i settery.
Jako getter i setter, rozumiem metody klasy, zwykle nazwane wg wzoru „getFoo()”, „setFoo()”, które ”mapują się 1:1″ z odpowiadającymi im polami klasy. Przykład:
class Bar { private $_foo; public getFoo(){ return $this->_foo; } public setFoo($foo){ $this->_foo = $foo; } }
Przypadki problematyczne:
1.Setter, który wpływa na obiekty kolaborujące klasy.
Wyobraźmy sobie, że mamy sobie taką oto klasę do obsługi tabeli w bazie danych:
class Foo_Table { private $_db; public function setDb($db){ $this->_db = $db; } public function getDb(){ return $this->_db; } public function fetchAll(){ //kod } }
Przypomina ona z grubsza klase Zend_Db_Table_Abstract i jest to właściwe skojarzenie (różnica polega na obecności statycznego settera). . Zobaczmy teraz jakie problemy generuje zastosowanie settera:
//gdzies w kodzie tworzymy obiekt $fooTable $db1 = new PDO($dsn, $username, $passwd, $options); $fooTable = new Foo_Table; $fooTable->setDb($db1); //inne miejsce w kodzie $fooTable->fetchAll(); //zwraca array(1,2,3); //inne miejsce w kodzie $db2 = new PDO($dsn2, $username, $password,$options); $fooTable->setDb($db2); //inne miejsce w kodzie $fooTable->fetchAll(); //zwraca array(4,5,6) - inna baza danych
Jak widać na wyżej załączonym kodzie, z powodu zastosowania settera, można w międzyczasie zmienić obiekt adaptera bazy danych, z którego korzysta klasa tabeli. Konsekwencją tego jest mniejsza przewidywalność klasy – w drugim przypadku dostajemy inny wynik. W przypadku Zend_Db_Table_Abstract sytuację pogarsza fakt, zastosowania statycznego settera, co wielokrotnie zwiększa możliwości zmiany adaptera i nieprzewidywalność takiego kodu. Łatwo usunąć ten problem poprzez likwidację settera i zastosowanie wstrzykiwania obiektu połączenia z bazą danych poprzez konstruktor.
2. Rozpełzanie się kodu
Dziwna nazwa, ale już tłumacze o co chodzi. Zostawmy na chwilę poprzedni przykład z tabelą, żeby nie było, że przyczepiłem się do Zend_Db. Kolejnym przykładem będzie klasa reprezentująca użytkownika, oczywiście dla potrzeb przykładu została skrócona do minimum: