Główne dwa powody przeciwko stosowaniu metod statycznych to:
- kod przy użyciu metod statycznych jest trudny do przetestowania
- kod przy użyciu metod statycznych jest trudny do rozszerzenia
Posiadanie statycznego wywołania metody wewnątrz innej metody jest w rzeczywistości gorsze niż importowanie zmiennej globalnej. W PHP klasy są symbolami globalnymi, więc za każdym razem, gdy wywołujesz metodę statyczną, polegasz na symbolu globalnym (nazwie klasy). Tak jest w przypadku, gdy globalność jest zła. Miałem problemy z takim podejściem z jakimś komponentem Zend Framework. Istnieją klasy, które używają statycznych wywołań metod (fabryk) do budowania obiektów. Nie mogłem dostarczyć innej fabryki do tej instancji w celu zwrotu dostosowanego obiektu. Rozwiązaniem tego problemu jest używanie tylko instancji i metod instancji oraz wymuszanie singletonów i tym podobnych na początku programu.
Miško Hevery , który pracuje jako Agile Coach w Google, ma ciekawą teorię, a raczej radzi, że powinniśmy oddzielić czas powstania obiektu od momentu jego użycia. Tak więc cykl życia programu jest podzielony na dwie części. Pierwsza część ( main()
powiedzmy metoda), która zajmuje się całym okablowaniem obiektów w Twojej aplikacji oraz częścią, która wykonuje rzeczywistą pracę.
Więc zamiast mieć:
class HttpClient
{
public function request()
{
return HttpResponse::build();
}
}
Powinniśmy raczej zrobić:
class HttpClient
{
private $httpResponseFactory;
public function __construct($httpResponseFactory)
{
$this->httpResponseFactory = $httpResponseFactory;
}
public function request()
{
return $this->httpResponseFactory->build();
}
}
Następnie na stronie indeksu / głównej zrobilibyśmy (jest to krok okablowania obiektu lub czas na utworzenie wykresu instancji, które mają być używane przez program):
$httpResponseFactory = new HttpResponseFactory;
$httpClient = new HttpClient($httpResponseFactory);
$httpResponse = $httpClient->request();
Głównym pomysłem jest oddzielenie zależności od twoich klas. W ten sposób kod jest znacznie bardziej rozszerzalny i, co dla mnie najważniejsze, testowalny. Dlaczego ważniejsze jest, aby być testowalnym? Ponieważ nie zawsze piszę kod biblioteki, więc rozszerzalność nie jest tak ważna, ale testowalność jest ważna podczas refaktoryzacji. W każdym razie, testowalny kod zazwyczaj daje rozszerzalny kod, więc tak naprawdę nie jest to sytuacja „albo-albo”.
Miško Hevery dokonuje również wyraźnego rozróżnienia między singletonami i singletonami (z wielką literą S lub bez niej). Różnica jest bardzo prosta. Singletony z małymi literami „s” są wymuszane przez okablowanie w indeksie / głównym. Tworzysz obiekt klasy, która nie implementuje wzorca Singleton, i dbasz o to, aby przekazać to wystąpienie tylko do każdej innej instancji, która tego potrzebuje. Z drugiej strony Singleton, przez duże „S” jest implementacją klasycznego (anty) wzorca. Zasadniczo globalny w przebraniu, który nie ma większego zastosowania w świecie PHP. Do tej pory żadnego nie widziałem. Jeśli chcesz, aby jedno połączenie DB było używane przez wszystkie Twoje klasy, lepiej zrób to w ten sposób:
$db = new DbConnection;
$users = new UserCollection($db);
$posts = new PostCollection($db);
$comments = new CommentsCollection($db);
Robiąc powyższe, widać wyraźnie, że mamy singletona i mamy też fajny sposób na wprowadzenie makiety lub kodu pośredniczącego w naszych testach. Zaskakujące jest, jak testy jednostkowe prowadzą do lepszego projektu. Ale ma to duży sens, gdy myślisz, że testy zmuszają cię do przemyślenia sposobu, w jaki użyjesz tego kodu.
/**
* An example of a test using PHPUnit. The point is to see how easy it is to
* pass the UserCollection constructor an alternative implementation of
* DbCollection.
*/
class UserCollection extends PHPUnit_Framework_TestCase
{
public function testGetAllComments()
{
$mockedMethods = array('query');
$dbMock = $this->getMock('DbConnection', $mockedMethods);
$dbMock->expects($this->any())
->method('query')
->will($this->returnValue(array('John', 'George')));
$userCollection = new UserCollection($dbMock);
$allUsers = $userCollection->getAll();
$this->assertEquals(array('John', 'George'), $allUsers);
}
}
Jedyną sytuacją, w której użyłbym (i użyłem ich do naśladowania obiektu prototypu JavaScript w PHP 5.3) statycznych elementów członkowskich, jest sytuacja, gdy wiem, że odpowiednie pole będzie miało tę samą wartość między instancjami. W tym momencie możesz użyć statycznej właściwości i być może pary statycznych metod pobierających / ustawiających. W każdym razie nie zapomnij dodać możliwości zastąpienia statycznego elementu członkowskiego elementem instancji. Na przykład Zend Framework używał statycznej właściwości w celu określenia nazwy klasy adaptera DB używanej w instancjach Zend_Db_Table
. Minęło trochę czasu, odkąd ich używałem, więc może już nie mieć znaczenia, ale tak to pamiętam.
Metody statyczne, które nie obsługują właściwości statycznych, powinny być funkcjami. PHP ma funkcje i powinniśmy ich używać.