Nesse artigo vamos ver a teoria por trás do Princípio da Segregação de Interface (Interface Segregation Principle), ou simplesmente ISP, e como representar esse conceito em aplicações JavaScript.
Sua definição formal diz:
Uma classe não deve ser forçada a implementar métodos que não irá usar.
Exemplo de violação
Abaixo temos um exemplo que viola o ISP:

A interface ISmartDevice define os métodos print e scan, e a classe Printer a implementa, dessa forma, toda classe que herdar de Printer deve implementar os dois métodos.
Simples, não? O problema é que a classe Economic **deve ter apenas o método print e dessa forma forçamos a implementação do método scan, violando o ISP**.
Resolvendo a violação do ISP
Aqui temos um exemplo onde aplicamos o ISP, dividindo ISmartDevice em duas interfaces menores: IPrinter, e IScanner. Desta forma, temos um código mais desacoplado e fácil de manter, podendo implementar classes que não precisam de todas as funcionalidades.

Até aí tudo bem, mas como vamos implementar isso em JavaScript se a linguagem não suporta interfaces? Vamos parar um pouco para pensar, o que é uma interface?
Interface é um contrato que força uma classe ou um objeto (no caso do JavaScript) a implementar uma funcionalidade especifica.
E na prática, como funciona?
Realmente não existe uma implementação concreta de interfaces em JavaScript, mas isso não nos impede de criar um “contrato” que se comporte dessa maneira. Vejamos o seguinte código:
class Printer {
constructor(name) {
this.name = name;
}
}
class AllInOne extends Printer {
constructor(name) {
super(name);
}
scan() {
return 'scanning...';
}
}
function printerFunctionalities(printers) {
console.log('Impressoras digitalizando: ');
for (let printer of printers) {
console.log(`${printer.name} is ${printer.scan()}`);
}
}
let printers = [
new AllInOne('HP 1'),
new AllInOne('HP 2'),
];
printerFunctionalities(printers);
Aqui temos uma classe Printer e uma subclasse AllInOne que implementa o método scan. Temos também uma função chamada printerFunctionalities que percorre uma lista de instâncias de AllInOne ****chamando o método scan de cada um dos itens da lista.
Mas temos um problema, e se criarmos uma classe Economic que não deve ter o método scan?
class Economic extends Printer {
constructor(name) {
super(name);
}
}
Se adicionarmos uma instância de Economic dentro do array o código retornará um erro, e é óbvio que é porque não implementamos o método scan dentro da classe Economic.
let printers = [
new AllInOne('HP 1'),
new AllInOne('HP 2'),
new Economic('HP 3'),
];
De fato, JavaScript não tem uma implementação de interfaces, mas nesse código criamos um "contrato" que nos força a implementar o método scan.
Poderíamos acabar com esse erro implementando o método scan na classe Economic, mas não é isso que queremos, pois desta forma estaríamos violando o ISP. Então, para resolver esse problema de uma maneira simples que nos trará o resultado esperado. Vamos adicionar uma condição que verifica a existência do método em cada uma das instâncias.
function printerFunctionalities(printers) {
console.log('Impressoras digitalizando: ');
for (let printer of printers) {
if (printer.scan) {
console.log(`${printer.name} is ${printer.scan()}`);
}
}
}
Dessa forma tornamos o método scan opcional, não forçando classes concretas a implementarem métodos desnecessários, evitamos um aumento no acoplamento e deixamos nossas classes mais coesas.
Conclusão
O fato de JavaScript não possuir interfaces faz com que esse princípio não se aplique estritamente como os outros. Entretanto, é importante lembrar que interfaces são “contratos” e podemos implementá-los de forma implícita em JavaScript.
Espero que estejam gostando da série, se tiverem dúvidas ou sugestões não deixem de comentar. Abraço!