Interfaces para abstrair as operações de objetos e variar/trocar as implementações


Neste artigo será mostrada a aplicação prática do uso de interfaces para abstrair operações, pois elas são uma ferramenta/instrumento útil e valioso para fazer isso quando são concebidas se despreendendo dos detalhes específicos das operações que carregam. E se despreender dos detalhes é abstrair os detalhes e trabalhar só com as noções das operações.

Noção indefinida da operação

Quando uma pessoa vai jogar videogame para ter um passatempo é necessário ligar o aparelho para começar a jogar. A forma como cada aparelho de videogame é ligado é um detalhe específico de cada modelo e esse tipo de detalhe não importa para quem liga o videogame, pois quem vai ligá-lo só precisa ter uma noção básica (e abstrata) do que essa ação/operação (ligar) faz para usar ela, é só disso que se precisa depender (e essa noção é o que aparece quando se trabalha com injeção de dependência).

A seguir está uma inferface criada para abstrair a operação em questão. Ela será a ferramenta usada para trabalhar com a abstração da operação de ligar um videogame.

<?php

interface Videogame
{

    public function ligar(): void;

}

A interface Videogame não leva em consideração os detalhes de como um videogame é ligado, por isso ela define apenas o método referente a essa operação de ligar o videogame, pois o que importa é definir essa operação na sua essência (sem detalhes) e não como ela vai ser feita (implementada) na prática com detalhes específicos.

Definindo as implementações da interface criada

Uma implementação desta interface é onde estariam os detalhes específicos, mas quem for utilizar/depender da interface não vai lidar com esses detalhes, pois a interface irá estar entre a implementação e quem for utilizar/depender da implementação. A seguir está o exemplo de uma implementação para a interface em questão.

<?php

class MeuVideoGameObsoleto implements Videogame
{

    private $ligaSeTiverSorte;

    private const AZAR  = 0;
    private const SORTE = 1;

    public function __construct()
    {

        $this->ligaSeTiverSorte = rand(self::AZAR, self::SORTE);

    }

    public function ligar(): void
    {

        if(!$this->ligaSeTiverSorte){
            throw new RuntimeException('Dessa vez não ligou.');
        }

        echo 'Dessa vez o videogame ligou, que sorte!!!';

    }

}

Como é possível perceber, essa classe trata de um tipo de videogame específico que contém a operação ligar, ou seja, essa classe é uma implementação que contém os detalhes de como a operação é feita de forma concreta na prática (definindo o que estava indefinido na interface).

Além dessa implementação, poderiam ser criadas várias outras classes diferentes que implementam essa mesma interface Videogame representando videogames específicos com o mesmo método ligar, cada implementação tendo seus detalhes específicos.

Por exemplo, imagine um videogame novo que funciona sem dar problema nenhum, mas que implementa a mesma interface Videogame. Segue uma implementação da interface para representar esse cenário.

<?php

class VideoGameNovo implements Videogame
{

    private $voltagem;

    private const VOLTAGEM_MINIMA = 127;
    private const VOLTAGEM_MAXIMA = 220;

    public function __construct(
        int $voltagem
    ){

        $this->voltagem = $voltagem;

    }

    private function estaDentroDaVoltagemEsperada(): bool
    {

        return (
            $this->voltagem >= self::VOLTAGEM_MINIMA and 
            $this->voltagem <= self::VOLTAGEM_MAXIMA
        );

    }

    public function ligar(): void
    {

        if(!$this->estaDentroDaVoltagemEsperada()){
            throw new Exception('Voltagem errada, não ligou.');
        }

        echo 'Ligou e está funcionando bem demais!';

    }

}

Infinitas implementações da mesma interface

Uma interface trabalha em cima da noção básica de uma operação, com isso ela fornece a possibilidade para criar qualquer implementação a partir dessa noção básica mais abstrata.

Basicamente, ao usar a interface Videogame no exemplo aprensentado a possibilidade de criar novas implementações é infinita. Esse é o tipo de flexibilidade que é obtida ao usar uma interface para abstrair os detalhes de uma operação. Usar uma interface é abrir o caminho para usar qualquer implementação, por isso ela é uma ferramenta/instrumento valioso.

A fórmula a seguir é uma maneira de decompor a vantagem obtida com o uso de uma interface.

Interfaces + abstração de operações = várias formas de fazer a mesma coisa

Outro aspecto vantajoso de usar interfaces pode ser representado com essa outra fórmula:

Várias implementações = várias possibilidades = liberdade de troca

A criação de novas implementações de uma mesma interface permite a troca delas para mudar o comportamento dos objetos. A vantagem aqui se traduz através de uma palavra-chave: flexibilidade. Ao utilizar abstrações a mudança/troca de implementações é facilitada, consequentemente isso deixa o código mais flexível/maleável.

Esse tipo de vantagem é crucial para deixar o código com a manutenção mais fácil, pois ele se torna mais adaptável a mudanças (tendo um nível baixo de acoplamento). Com isso, a flexibilidade um ponto-chave: liberdade de troca.

Os conceitos de interface e abstração são muito vantajosos quando usados como os tipos definidos e esperados em parâmetros dos métodos dos objetos, pois ao definir uma interface que abstraí operações como parâmetro os detalhes de uma implementação estão sendo abstraídos.

Referências

Hangout sobre OOD - Princípio da inversão de dependências


ver todos os posts
Publicado em 28/11/2023