Anúncio de serviço público:
Quero declarar que acredito que os traços são quase sempre um cheiro de código e devem ser evitados em favor da composição. É minha opinião que a herança única é frequentemente abusada a ponto de ser um antipadrão e a herança múltipla apenas compõe esse problema. Você será muito melhor atendido na maioria dos casos, favorecendo a composição sobre a herança (seja ela única ou múltipla). Se você ainda está interessado em características e sua relação com interfaces, continue lendo ...
Vamos começar dizendo o seguinte:
A Programação Orientada a Objetos (OOP) pode ser um paradigma difícil de entender. Só porque você está usando classes, não significa que seu código seja Orientado a Objetos (OO).
Para escrever código OO, você precisa entender que OOP é realmente sobre os recursos de seus objetos. Você precisa pensar nas aulas em termos do que elas podem fazer, e não do que realmente fazem . Isso contrasta fortemente com a programação processual tradicional, na qual o foco é fazer um pouco de código "fazer alguma coisa".
Se o código OOP é sobre planejamento e design, uma interface é o blueprint e um objeto é a casa totalmente construída. Enquanto isso, os traços são simplesmente uma maneira de ajudar a construir a casa estabelecida pelo projeto (a interface).
Interfaces
Então, por que devemos usar interfaces? Simplesmente, as interfaces tornam nosso código menos quebradiço. Se você duvida desta afirmação, pergunte a alguém que foi forçado a manter o código legado que não foi escrito nas interfaces.
A interface é um contrato entre o programador e seu código. A interface diz: "Enquanto você seguir minhas regras, poderá me implementar da maneira que quiser e prometo que não quebrarei seu outro código".
Por exemplo, considere um cenário do mundo real (sem carros ou widgets):
Você deseja implementar um sistema de armazenamento em cache para um aplicativo Web reduzir a carga do servidor
Você começa escrevendo uma classe para armazenar em cache as respostas das solicitações usando a APC:
class ApcCacher
{
public function fetch($key) {
return apc_fetch($key);
}
public function store($key, $data) {
return apc_store($key, $data);
}
public function delete($key) {
return apc_delete($key);
}
}
Em seguida, no seu objeto de resposta HTTP, verifique se há um acerto no cache antes de fazer todo o trabalho para gerar a resposta real:
class Controller
{
protected $req;
protected $resp;
protected $cacher;
public function __construct(Request $req, Response $resp, ApcCacher $cacher=NULL) {
$this->req = $req;
$this->resp = $resp;
$this->cacher = $cacher;
$this->buildResponse();
}
public function buildResponse() {
if (NULL !== $this->cacher && $response = $this->cacher->fetch($this->req->uri()) {
$this->resp = $response;
} else {
// Build the response manually
}
}
public function getResponse() {
return $this->resp;
}
}
Essa abordagem funciona muito bem. Mas talvez algumas semanas depois você decida usar um sistema de cache baseado em arquivo em vez da APC. Agora você precisa alterar o código do seu controlador, porque o programou para trabalhar com a funcionalidade da ApcCacher
classe, e não com uma interface que expresse os recursos da ApcCacher
classe. Digamos que, em vez do acima, você tenha tornado a Controller
classe dependente de um em CacherInterface
vez do concreto, da seguinte ApcCacher
forma:
// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)
Para acompanhar, você define sua interface da seguinte maneira:
interface CacherInterface
{
public function fetch($key);
public function store($key, $data);
public function delete($key);
}
Por sua vez, você ApcCacher
e suas novas FileCacher
classes implementam CacherInterface
e você programa sua Controller
classe para usar os recursos exigidos pela interface.
Este exemplo (espero) demonstra como a programação em uma interface permite alterar a implementação interna de suas classes sem se preocupar se as alterações quebrarão seu outro código.
Traits
Traços, por outro lado, são simplesmente um método para reutilizar o código. As interfaces não devem ser pensadas como uma alternativa mutuamente exclusiva às características. De fato, a criação de características que atendam aos recursos exigidos por uma interface é o caso de uso ideal .
Você só deve usar características quando várias classes compartilham a mesma funcionalidade (provavelmente ditada pela mesma interface). Não faz sentido usar uma característica para fornecer funcionalidade para uma única classe: isso apenas ofusca o que a classe faz e um design melhor transfere a funcionalidade da característica para a classe relevante.
Considere a seguinte implementação de característica:
interface Person
{
public function greet();
public function eat($food);
}
trait EatingTrait
{
public function eat($food)
{
$this->putInMouth($food);
}
private function putInMouth($food)
{
// Digest delicious food
}
}
class NicePerson implements Person
{
use EatingTrait;
public function greet()
{
echo 'Good day, good sir!';
}
}
class MeanPerson implements Person
{
use EatingTrait;
public function greet()
{
echo 'Your mother was a hamster!';
}
}
Um exemplo mais concreto: imagine que você FileCacher
e seu ApcCacher
da discussão da interface usem o mesmo método para determinar se uma entrada de cache é obsoleta e deve ser excluída (obviamente esse não é o caso na vida real, mas vá com ela). Você pode escrever uma característica e permitir que ambas as classes a usem para o requisito de interface comum.
Uma última palavra de cautela: tenha cuidado para não exagerar nas características. Muitas vezes, as características são usadas como muleta para o design deficiente quando implementações de classe exclusivas são suficientes. Você deve limitar as características ao cumprimento dos requisitos da interface para obter o melhor design de código.