I due motivi principali per non utilizzare metodi statici sono:
- è difficile testare il codice usando metodi statici
- il codice che utilizza metodi statici è difficile da estendere
Avere una chiamata di metodo statico all'interno di un altro metodo è in realtà peggio che importare una variabile globale. In PHP, le classi sono simboli globali, quindi ogni volta che chiami un metodo statico fai affidamento su un simbolo globale (il nome della classe). Questo è un caso in cui il globale è malvagio. Ho avuto problemi con questo tipo di approccio con alcuni componenti di Zend Framework. Ci sono classi che usano chiamate a metodi statici (factory) per costruire oggetti. Per me era impossibile fornire un'altra fabbrica a quell'istanza per ottenere la restituzione di un oggetto personalizzato. La soluzione a questo problema è usare solo istanze e metodi di installazione e applicare singleton e simili all'inizio del programma.
Miško Hevery , che lavora come Agile Coach presso Google, ha una teoria interessante, o meglio consiglia, che dovremmo separare il tempo di creazione dell'oggetto dal tempo in cui lo utilizziamo. Quindi il ciclo di vita di un programma è diviso in due. La prima parte (il main()
metodo diciamo), che si occupa di tutto il cablaggio dell'oggetto nell'applicazione e della parte che fa il lavoro effettivo.
Quindi invece di avere:
class HttpClient
{
public function request()
{
return HttpResponse::build();
}
}
Dovremmo piuttosto fare:
class HttpClient
{
private $httpResponseFactory;
public function __construct($httpResponseFactory)
{
$this->httpResponseFactory = $httpResponseFactory;
}
public function request()
{
return $this->httpResponseFactory->build();
}
}
E poi, nella pagina indice / principale, faremmo (questa è la fase di cablaggio dell'oggetto, o il tempo per creare il grafico delle istanze che verranno utilizzate dal programma):
$httpResponseFactory = new HttpResponseFactory;
$httpClient = new HttpClient($httpResponseFactory);
$httpResponse = $httpClient->request();
L'idea principale è quella di separare le dipendenze dalle tue classi. In questo modo il codice è molto più estensibile e, la parte più importante per me, testabile. Perché è più importante essere testabili? Poiché non scrivo sempre il codice della libreria, quindi l'estensibilità non è così importante, ma la testabilità è importante quando eseguo il refactoring. Ad ogni modo, il codice testabile di solito produce codice estensibile, quindi non è davvero una situazione o.
Miško Hevery fa anche una chiara distinzione tra singoli e singoli (con o senza la S maiuscola). La differenza è molto semplice I singoli con una "s" minuscola sono applicati dal cablaggio nell'indice / principale. Si crea un'istanza di un oggetto di una classe che non implementa il modello Singleton e si cura di passare tale istanza a qualsiasi altra istanza che ne abbia bisogno. D'altra parte, Singleton, con la "S" maiuscola è un'implementazione del modello classico (anti). Fondamentalmente un globale sotto mentite spoglie che non ha molto uso nel mondo PHP. Non ne ho visto uno fino a questo punto. Se vuoi che una singola connessione DB sia utilizzata da tutte le tue classi, è meglio farlo in questo modo:
$db = new DbConnection;
$users = new UserCollection($db);
$posts = new PostCollection($db);
$comments = new CommentsCollection($db);
Facendo quanto sopra è chiaro che abbiamo un singleton e abbiamo anche un bel modo di iniettare un finto o uno stub nei nostri test. È sorprendentemente come i test unitari portano a un design migliore. Ma ha molto senso pensare che i test ti costringano a pensare al modo in cui useresti quel codice.
/**
* 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);
}
}
L'unica situazione in cui userei (e li ho usati per imitare l'oggetto prototipo JavaScript in PHP 5.3) i membri statici è quando so che il rispettivo campo avrà lo stesso valore tra istanze. A quel punto puoi usare una proprietà statica e forse una coppia di metodi getter / setter statici. In ogni caso, non dimenticare di aggiungere la possibilità di sovrascrivere il membro statico con un membro di istanza. Ad esempio, Zend Framework utilizzava una proprietà statica per specificare il nome della classe dell'adattatore DB utilizzata nelle istanze di Zend_Db_Table
. È da un po 'che non li uso, quindi potrebbe non essere più pertinente, ma è così che me lo ricordo.
I metodi statici che non trattano le proprietà statiche dovrebbero essere funzioni. PHP ha funzioni e dovremmo usarle.