À medida que você leva a programação mais a sério, inevitavelmente encontrará a frase "Codificar para uma interface", seja em vídeos, livros ou artigos. E isso nunca fez sentido para mim. Questionei a necessidade de criar uma interface e depois implementá-la. Como determino quando e onde usar essas interfaces?
Sempre que eu assistia um tutorial ou lia um artigo, eles explicavam o que é uma interface, “É uma classe sem implementação”, e eu dizia “Ehmm, obrigado 😏”. Quer dizer, eu já sabia disso; o que eu realmente queria saber era por que e quando usá-lo.
Lembro-me de um dia perguntar na comunidade Discord, e um dos idosos simplesmente disse: "Não se preocupe; eventualmente, isso vai clicar para você", e funcionou, demorou um pouco, mas funcionou. Se você está passando por isso, saiba que todos nós já passamos por isso; vamos ajudá-lo a entender por que você precisa codificar para uma interface .
Já que a IA está assumindo o controle e todo mundo está perdendo a cabeça com isso, não queremos nos atrasar para a festa. Queremos adicioná-lo ao nosso site, um pequeno chatbot que responda a perguntas sobre o nosso produto fará isso.
Usarei PHP como exemplo; sinta-se à vontade para usar qualquer idioma com o qual se sinta confortável. O que importa é o conceito.
Nosso chatbot pode ser tão simples quanto isto:
<?php class ChatBot { public function ask(string $question): string { $client = new OpenAi(); $response = $client->ask($question); return $response; } }
Existe um único método ask()
, que usa o OpenAI
SDK para se conectar à API, fazer uma pergunta e simplesmente retornar a resposta.
Agora podemos começar a usar nosso chatbot
$bot = new ChatBot(); $response = $bot->ask('How much is product X'); // The product costs $200.
Até agora, a implementação parece boa, está funcionando conforme esperado e o projeto está implantado e em uso. Mas não podemos negar que nosso chatbot depende fortemente da API Open AI; Tenho certeza que você concorda.
Agora, vamos considerar um cenário em que os preços da Open AI dobram e continuam a aumentar. Quais são as nossas opções? Ou apenas aceitamos nosso destino ou procuramos outra API. A primeira opção é fácil, apenas continuamos pagando, e a segunda não é tão simples quanto parece. O novo provedor provavelmente terá sua própria API e SDK; teremos que fazer atualizações em todas as classes, testes e componentes relacionados originalmente projetados para Open AI, isso dá muito trabalho.
Isto também levanta preocupações; e se a nova API não atender às nossas expectativas em termos de precisão ou aumentar o tempo de inatividade? E se quisermos apenas experimentar diferentes fornecedores simultaneamente? Por exemplo, fornecer aos nossos clientes assinantes o cliente OpenAI enquanto usamos uma API mais simples para convidados? Você pode ver como isso pode ser complexo e sabe por quê? Porque nosso código foi mal projetado.
Não tivemos uma visão; acabamos de escolher uma API e éramos totalmente dependentes dela e de sua implementação. Agora, o princípio de “Código para interface” teria nos salvado de tudo isso. Como? Vamos ver.
Vamos começar criando uma interface:
<?php interface AIProvider { public function ask(string $question): string; }
Temos nossa interface, ou como gosto de chamar, um contrato. Vamos implementá-lo ou codificá-lo.
<?php class OpenAi implements AIProvider { public function ask(string $question): string { $openAiSdk = new OpenAiSDK(); $response = $openAiSdk->ask($question); return "Open AI says: " . $response; } } class RandomAi implements AIProvider { public function ask(string $question): string { $randomAiSdk = new RandomAiSDK(); $response = $randomAiSdk->send($question); return "Random AI replies: " . $response->getResponse(); } }
Na realidade, tanto
OpenAiSDK
quantoRandomAiSDK
serão injetados por meio do construtor. Dessa forma, delegamos a lógica complexa de instanciação a um contêiner DI , conceito conhecido como inversão de controle . Isso ocorre porque cada provedor normalmente exige determinadas configurações.
Agora temos dois provedores que podemos usar para responder perguntas. Independentemente da sua implementação, estamos confiantes de que, quando receberem uma pergunta, eles se conectarão à sua API e responderão a ela. Eles devem aderir ao contrato AIProvider
.
Agora, no nosso ChatBot
, podemos fazer o seguinte
class ChatBot { private AIProvider $client; // A dependency can be injected via the constructor public function __construct(AIProvider $client) { $this->client = $client; } // It can also be set via a setter method public function setClient(AIProvider $client): void { $this->client = $client; } public function ask(string $question): string { return $this->client->ask($question); } }
Observe bem que o exemplo visa demonstrar as múltiplas formas de injetar uma dependência, neste caso, um
AIProvider
. Você não é obrigado a usar construtores e setters.
Você pode ver que fizemos alguns ajustes; não dependemos mais do OpenAI e você não encontrará nenhuma referência a ele. Em vez disso, dependemos do contrato/interface. E, de alguma forma, podemos nos identificar com este exemplo na vida real; todos nós já fomos ChatBot
pelo menos uma vez.
Imagine comprar um sistema de painel solar. A empresa promete enviar técnicos para instalá-lo, garantindo que independente do funcionário que enviar, o trabalho estará feito e no final você terá seus painéis instalados. Então, você realmente não se importa se eles mandam Josh ou George. Podem ser diferentes, sendo um melhor que o outro, mas ambos são contratados para a instalação dos painéis.
Eles não vão ficar tipo, você sabe o que estou consertando sua TV, eles são obrigados pela empresa a fazer o trabalho especificado. Tanto RandomAi
quanto OpenAi
atuam como funcionários do AIProvider
; você faz uma pergunta e eles fornecerão uma resposta. Assim como você não se importou com quem instala os painéis, o ChatBot
não deveria se importar com quem faz o trabalho. Ele só precisa saber que qualquer implementação fornecida servirá.
Agora você pode usar livremente um ou outro.
$bot = new ChatBot(); // For subscribed users $bot = new ChatBot(new OpenAi()); $response = $bot->ask('How much is Product X'); // Open AI says: 200$ // For guests $bot->setClient(new RandomAi()); $response = $bot->ask('How much is Product X'); // Random AI replies: 200$
Agora você tem a flexibilidade de alterar todo o provedor de API e seu código sempre se comportará da mesma forma. Você não precisa alterar nada porque codificou para um interface , portanto, nenhuma das preocupações que levantamos anteriormente será um problema.
Em nosso exemplo, ao codificar para uma interface, também respeitamos três dos princípios SOLID , mesmo sem saber que o fizemos, deixe-me explicar.
Não vou entrar em detalhes; cada um dos princípios pode ter um artigo longo. Esta é apenas uma breve explicação para mostrar o que ganhamos ao codificar para uma interface.
O primeiro princípio que respeitamos é o Princípio Aberto-Fechado, que afirma que o código deve ser aberto para extensão e fechado para modificação. Por mais desafiador que possa parecer, você conseguiu. Pense nisso, o ChatBot
está fechado para modificação agora; não tocaremos no código novamente. Este foi o nosso objetivo desde o início.
Mas está aberto para extensão; se adicionarmos um terceiro, quarto ou mesmo um quinto provedor, nada nos impedirá. Podemos implementar a interface e nossa classe pode usá-la imediatamente, nenhuma alteração é necessária.
Não vou aborrecê-lo com sua definição, mas basicamente afirma que você pode substituir classes por TODAS as suas subclasses e vice-versa. Tecnicamente, todos os nossos provedores de IA are-a
AIProvider
e suas implementações podem ser trocadas entre si, sem afetar a correção do ChatBot
, este último nem sabe qual provedor está usando 😂, então sim, respeitamos a Sra. .
Devo admitir que este poderia ter seu próprio artigo. Mas, para simplificar, o princípio afirma que se deve depender de abstrações e não de concretos, que é exatamente o que estamos fazendo. Dependemos de um fornecedor, não de um fornecedor específico como o Open AI.
Lembre-se, tudo isso é porque codificamos para uma interface.
Sempre que você atualiza uma classe que você sabe que não deveria atualizar, e seu código fica hackeado com instruções if, você precisa de uma interface. Sempre pergunte a si mesmo: esta turma realmente precisa saber como? Usarei para sempre este provedor de serviços? Ou driver de banco de dados? Se não, você sabe o que fazer.
Com isso dito, espere um pouco, ele eventualmente clicará para você .