Princípio de segregação de interface

O princípio de segregação de interface torna as nossas classes independentes de interfaces que necessitam implementações desnecessárias.

Categoria de Programação

Postado em 27 abril 2022

Atualizado em 27 abril 2022

Visualizações: 403



A abstração na programação pode ser usada em várias linguagens de programação. Ela pode ser bastante útil, mas também pode se tornar um problema no decorrer de algum projeto.

“Clients should not be forced to depend upon interfaces that they do not use.”

Os aplicativos não devem ser forçados a depender de interfaces que eles não usam.

A abstração em si traz muitas vantagens, mas se mal implementada pode trazer alguns problemas como dependência de interfaces.

Princípio de segregação de interface

Seguindo o princípio de segregação de interface podemos tornar as nossas classes menos dependentes de interfaces desnecessárias.

Segregar, significa separar e isolar. É sobre isso que esse princípio fala. Considere a interface abaixo:

interface FormaDePagamento {
    public function pagarEmDinheiro(int $quantia);
    public function pagarNoCartao(int $quantia);
}

A interface acima possui duas formas de pagamento. Quando implementamos a interface acima, somos forçados a implementar essas duas funções.

class Loja implements FormaDePagamento {
    public function pagarEmDinheiro(int $quantia) {
	    // Implementação
    }
    
	public function pagarNoCartao(int $quantia) {
	    // Implementação
    }
}

O modo de usar a abstração não está errado, porém temos que pensar ao longo termo. Se no futuro aparecer alguma loja que só aceite dinheiro, seremos forçados a implementar a função “pagarNoCartao”. Nessa ocasião só nos resta criar uma exceção, o que não é certo.

class LojaDoDinheiro implements FormaDePagamento {
    public function pagarEmDinheiro(int $quantia) {
	    // Implementação
    }
    
	public function pagarNoCartao(int $quantia) {
	    // Não é possível pagar no cartão
	    throw new Exception('Nao implementado');
    }
}

Uma vez que somos forçados a implementar funções que não utilizamos, nos tornamos dependentes dessas interfaces, isso é uma violação do princípio de segregação de interface.

Solução

Como o próprio nome desse princípio diz, segregar essas interfaces resolve o problema.

Se segregarmos a interface “FormaDePagamento”, temos o seguinte código:

interface PagamentoEmDinheiro {
    public function pagarNoDinheiro(int $quantia);
}

interface PagamentoNoCartao {
    public function pagarNoCartao(int $quantia);
}

A implementação das interfaces pode ser realizada estendendo interface com interface, ou implementando as duas interfaces na mesma classe.

// Exemplo 1: Implementando duas interfaces na mesma classe
class LojaDoDinheiro implements PagamentoEmDinheiro, PagamentoNoCartao  {
    public function pagarNoCartao(int $quantia) {
	    // Implementação
    }
	
	public function pagarNoCartao(int $quantia) {
	    // Implementação
    }
}

// Exemplo 2: Estendendo interface com interface
interface AceitaTudo extends PagamentoEmDinheiro, PagamentoNoCartao {
	public function fiado(int $quantia);
}

class LojaDoDinheiro implements AceitaTudo  {
    public function pagarNoCartao(int $quantia) {
	    // Implementação
    }
	
	public function pagarNoCartao(int $quantia) {
	    // Implementação
    }
    
	public function fiado(int $quantia) {
	    // Implementação
    }
}

Uma vez que as interfaces estão segregadas estrategicamente, não ficamos mais dependentes de funções que não utilizamos.

Como aplicar esse princípio de segregação de interface após ter violado as regras?

Felizmente, podemos usar uma classe para adaptar uma interface volumosa mal planejada.

Levando em consideração a interface abaixo:

// Essa interface viola os princípios de segregação de interface
interface FormaDePagamento {
    public function pagarEmDinheiro(int $quantia);
    public function pagarNoCartao(int $quantia);
    public function fiado(int $quantia);
}

class ClasseVioladora implements FormaDePagamento {
	public function pagarEmDinheiro(int $quantia) {
		// Implementação
	}
	
    public function pagarNoCartao(int $quantia) {
	    // Não é possível pagar no cartão
	    throw new Exception('Nao implementado');
    }
    
    public function fiado(int $quantia) {
	    // Não é possível fiado
	    throw new Exception('Nao implementado');
    }
}

A interface acima viola o princípio de segregação de interface. Não podemos modificar o código acima, pois pode quebrar outras partes do programa. Invés de modificar o código, adicionaremos mais código(respeitando o princípio aberto-fechado).

Criaremos uma nova interface.

interface PagamentoSoNoDinheiro {
    public function pagarSoEmDinheiro(int $quantia);
}

Criaremos a nossa classe adaptadora.

class SoAceitamosDinheiro implements PagamentoSoNoDinheiro {
    private FormaDePagamento $adaptador;
	
	public function __construct(FormaDePagamento $formaDePagamento) {
		$this->adaptador = formaDePagamento;
	}
	
    public function pagarSoEmDinheiro(int $quantia) {
	    return $this->adaptador->pagarEmDinheiro($quantia);
    }
}

Com o código acima adicionado, podemos prosseguir para a implementação da classe violadora.

$classeVioladora = new ClasseVioladora();
$soAceitamosDinheiro = new SoAceitamosDinheiro($classeVioladora);
$soAceitamosDinheiro->pagarSoEmDinheiro(10);

Com a ajuda de uma classe adaptadora conseguimos implementar uma classe violadora de princípios, cortar as funções não necessárias e tornar duas interfaces compatíveis.

Conclusão

O princípio de segregação de interfaces não quer dizer que interfaces só podem ter uma função, mas requer um planejamento mais detalhado.

Se conseguirmos utilizar esse princípio na prática, podemos ter um programa que não se torna um problema ao longo termo.