Os dois principais motivos contra o uso de métodos estáticos são:
- código usando métodos estáticos é difícil de testar
- é difícil estender o código usando métodos estáticos
Ter uma chamada de método estático dentro de outro método é realmente pior do que importar uma variável global. No PHP, as classes são símbolos globais; portanto, toda vez que você chama um método estático, depende de um símbolo global (o nome da classe). Este é um caso em que global é mau. Eu tive problemas com esse tipo de abordagem com algum componente do Zend Framework. Existem classes que usam chamadas de método estático (fábricas) para construir objetos. Era impossível fornecer outra fábrica para essa instância para obter um objeto personalizado retornado. A solução para esse problema é usar apenas instâncias e instalar métodos e aplicar singletons e similares no início do programa.
Miško Hevery , que trabalha como treinador ágil no Google, tem uma teoria interessante, ou melhor, aconselha que devemos separar o tempo de criação do objeto do momento em que o usamos. Portanto, o ciclo de vida de um programa é dividido em dois. A primeira parte ( main()
digamos o método), que cuida de toda a fiação do objeto em seu aplicativo e da parte que faz o trabalho real.
Então, ao invés de ter:
class HttpClient
{
public function request()
{
return HttpResponse::build();
}
}
Devemos fazer:
class HttpClient
{
private $httpResponseFactory;
public function __construct($httpResponseFactory)
{
$this->httpResponseFactory = $httpResponseFactory;
}
public function request()
{
return $this->httpResponseFactory->build();
}
}
E então, na página principal / índice, faríamos (esta é a etapa de fiação do objeto ou o tempo para criar o gráfico de instâncias a serem usadas pelo programa):
$httpResponseFactory = new HttpResponseFactory;
$httpClient = new HttpClient($httpResponseFactory);
$httpResponse = $httpClient->request();
A idéia principal é separar as dependências de suas classes. Dessa forma, o código é muito mais extensível e, a parte mais importante para mim, testável. Por que é mais importante ser testável? Como nem sempre escrevo o código da biblioteca, a extensibilidade não é tão importante, mas a testabilidade é importante quando refatoro. De qualquer forma, o código testável geralmente gera código extensível, portanto, não é realmente uma situação do tipo "ou-ou".
Miško Hevery também faz uma distinção clara entre singletons e singletons (com ou sem S maiúsculo). A diferença é muito simples. Singletons com minúsculas "s" são aplicados pela fiação no índice / principal. Você instancia um objeto de uma classe que não implementa o padrão Singleton e cuida para que somente passe essa instância para qualquer outra instância que precise. Por outro lado, Singleton, com letra maiúscula "S", é uma implementação do padrão clássico (anti). Basicamente um disfarçado global que não tem muito uso no mundo PHP. Eu não vi um até este ponto. Se você deseja que uma única conexão de banco de dados seja usada por todas as suas classes, é melhor fazê-lo assim:
$db = new DbConnection;
$users = new UserCollection($db);
$posts = new PostCollection($db);
$comments = new CommentsCollection($db);
Ao fazer o exposto, fica claro que temos um singleton e também temos uma boa maneira de injetar um mock ou um stub em nossos testes. É surpreendente como os testes de unidade levam a um design melhor. Mas faz muito sentido quando você pensa que os testes o forçam a pensar na maneira como você usaria esse código.
/**
* 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);
}
}
A única situação em que eu usaria (e os usei para imitar o objeto de protótipo JavaScript no PHP 5.3) membros estáticos é quando eu sei que o respectivo campo terá o mesmo valor entre instâncias. Nesse ponto, você pode usar uma propriedade estática e talvez um par de métodos estáticos getter / setter. De qualquer forma, não se esqueça de adicionar a possibilidade de substituir o membro estático por um membro da instância. Por exemplo, o Zend Framework estava usando uma propriedade estática para especificar o nome da classe do adaptador DB usada nas instâncias de Zend_Db_Table
. Faz algum tempo desde que eu os usei, para que não seja mais relevante, mas é assim que me lembro.
Métodos estáticos que não lidam com propriedades estáticas devem ser funções. PHP tem funções e devemos usá-las.