O princípio de segregação de interface se aplica a métodos concretos?

10

Como o princípio de segregação de interface sugere que nenhum cliente deve ser forçado a depender dos métodos que não usa, portanto, um cliente não deve implementar um método vazio para seus métodos de interface, caso contrário, esse método de interface deve ser colocado em outra interface.

Mas e os métodos concretos? Devo separar os métodos que nem todos os clientes usariam? Considere a seguinte classe:

public class Car{
    ....

    public boolean isQualityPass(){
        ...
    }

    public int getTax(){
        ...
    }

    public int getCost(){
        ...
    }
}

public class CarShop{
    ...
    public int getCarPrice(int carId){
        Car car=carList[carId];
        int price=car.getTax() + car.getCost()... (some formula);
        return price;
    }
}

no código acima, o CarShop não usa o método isQualityPass () em Car, devo separar isQualityPass () em uma nova classe:

public class CheckCarQualityPass{
    public boolean isQualityPass(Car car){
    }
}

para reduzir o acoplamento do CarShop? Porque acho que uma vez se isQualityPass () precisa de dependência extra, por exemplo:

public boolean isQualityPass(){
    HttpClient client=...
}

O CarShop dependeria do HttpClient, mesmo que ele nunca use o HttpClient. Portanto, minha pergunta é: de acordo com o princípio de segregação de interface, devo separar métodos concretos que nem todos os clientes usariam, para que esses métodos dependam do cliente somente quando o cliente realmente usar, para reduzir o acoplamento?

ggrr
fonte
2
Geralmente um carro sabe quando passa na "qualidade"? Ou talvez seja uma regra de negócios que possa ser encapsulada por conta própria?
LAIV
2
Como a palavra interface no ISP implica, trata-se de interfaces . Portanto, se você tiver um método em sua Carclasse que não deseja que todos (os) usuários criem, crie (mais de uma) interface que a Carclasse implemente e que declare apenas métodos úteis no contexto de interfaces.
Timothy Truckle
@ Laiv Tenho certeza de que veremos veículos que sabem muito mais do que isso em breve. ;)
sanduíche de modelagem unificada
1
Um carro saberá o que seu fabricante quer que ele saiba. Volkswagen sabe do que estou falando :-)
LAIV
1
Você mencionou uma interface, mas não há interface no seu exemplo. Estamos falando de transformar o Car em uma interface e quais métodos incluir nessa interface?
Neil

Respostas:

6

No seu exemplo, CarShopnão depende isQualityPasse não é forçado a fazer uma implementação vazia para um método. Não há nem uma interface envolvida. Portanto, o termo "ISP" simplesmente não corresponde aqui. E enquanto um método como esse isQualityPassfor um método que se encaixa bem no Carobjeto, sem sobrecarregá-lo com responsabilidades ou dependências adicionais, tudo bem. Não há necessidade de refatorar um método público de uma classe para outro local apenas porque existe um cliente que não está usando o método.

No entanto, tornar uma classe de domínio como Cardiretamente dependente de algo como HttpClientprovavelmente também não é uma boa ideia, independentemente de quais clientes usam ou não o método. Mover a lógica para uma classe separada CheckCarQualityPasssimplesmente não é chamado de "ISP", isso é chamado de "separação de preocupações" . A preocupação de um objeto de carro reutilizável provavelmente não deve ser fazer chamadas HTTP externas, pelo menos não diretamente, isso limita a reutilização e, além disso, a testabilidade demais.

Se isQualityPassnão puder ser facilmente movido para outra classe, a alternativa seria fazer as Httpchamadas através de uma interface abstrata IHttpClientque é injetada Carno momento da construção ou injetando toda a estratégia de verificação "QualityPass" (com a solicitação Http encapsulada) no Carobjeto . Mas essa é a IMHO apenas a segunda melhor solução, pois aumenta a complexidade geral em vez de reduzi-la.

Doc Brown
fonte
e o padrão Strategy para resolver o método isQualityPass?
LAIV
@Laiv: tecnicamente, isso funcionará, com certeza (veja minha edição), mas resultará em um Carobjeto mais complexo . Não seria minha primeira escolha para uma solução (pelo menos não no contexto deste exemplo artificial). No entanto, pode fazer mais sentido no código "real", não sei.
Doc Brown
6

Portanto, minha pergunta é: de acordo com o princípio de segregação de interface, devo separar métodos concretos que nem todos os clientes usariam, para que esses métodos dependam do cliente somente quando o cliente realmente usar, para reduzir o acoplamento?

O princípio de segregação de interface não se trata de proibir o acesso ao que você não precisa. É sobre não insistir no acesso ao que você não precisa.

As interfaces não são de propriedade da classe que as implementa. Eles pertencem aos objetos que os usam.

public class CarShop{
    ...
    public int getCarPrice(int carId){
        Car car=carList[carId];
        int price=car.getTax() + car.getCost()... (some formula);
        return price;
    }
}

O que é usado aqui é getTax()e getCost(). O que está sendo insistido é tudo acessível Car. O problema é insistir Carsignifica que está insistindo no acesso ao isQualityPass()qual não é necessário.

Isso pode ser consertado. Você pergunta se pode ser corrigido concretamente. Pode.

public class CarShop{
    ...
    public int getCarPrice(int carId){
        CarLiability carLiability=carLiabilityList[carId];
        int price=carLiability.getTax() + carLiability.getCost()... (some formula);
        return price;
    }
}

Nenhum desses códigos sequer sabe se CarLiabilityé uma interface ou uma classe concreta. Isso é uma coisa boa. Não quer saber.

Se é uma interface Carpode implementá-lo. Isso não violaria o ISP, porque, apesar de isQuality()estar presente Car CarShop, não insiste nisso. Isto é bom.

Se for concreto, pode ser que isQuality()ou não exista ou tenha sido transferido para outro lugar. Isto é bom.

Também pode ser que CarLiabilityseja um invólucro de concreto Carque esteja delegando trabalho a ele. Contanto CarLiabilityque não exponha isQuality(), tudo CarShopbem. É claro que isso apenas chuta a lata no caminho e CarLiabilitytem que descobrir como seguir o ISP Carda mesma maneira que CarShoptinha que fazer.

Em resumo, isQuality()não precisa ser removido por Carcausa do ISP. A necessidade implícita precisa isQuality()ser removida CarShopporque CarShopnão precisa, portanto, não deve solicitá-la.

candied_orange
fonte
4

O princípio de segregação de interface se aplica a métodos concretos?

Mas e os métodos concretos? Devo separar os métodos que nem todos os clientes usariam?

Na verdade não. Existem diferentes maneiras para se esconder Car.isQualityPassde CarShop.

1. Modificadores de acesso

Do ponto de vista da Lei de Deméter , poderíamos considerar Care CardShopnão ser amigos . Ele nos legitima a fazer o próximo.

package com.my.package.domain.model
public class Car{
    ...
    protected boolean isQualityPass(){...}
}

package com.my.package.domain.services
public class CarShop{
    ...
}

Esteja ciente de que ambos os componentes estão em pacotes diferentes. Agora CarShopnão tem visibilidade sobre comportamentos Car protegidos . (Com licença, se o exemplo acima parecer tão simplista).

2. Segregação de Interface

O ISP trabalha com a premissa de que trabalhamos com abstrações, não com classes concretas. Assumirei que você já esteja familiarizado com a implementação do ISP e com as interfaces de função .

Apesar da Carimplementação real , nada nos impede de praticar o ISP.

//role interfaces 
public interface Billable{
   public int getCosts();
   public int getTaxs();
}

//role interfaces
public interface QualityAssurance{
   public boolean isQualityPass();
}

public class Car implements Billable, QualityAssurance{
   ...
}

public class CarShop {
  ...
  public int getPrice(Billable billable){
     return billable.getCosts() * billable.getTaxs();
  }
}

O que eu fiz aqui. Eu reduzi a interação entre Care CarShopatravés da interface de função Billable . Esteja ciente da alteração na getPriceassinatura. Modifiquei intencionalmente o argumento. Eu queria deixar óbvio que CarShopé apenas "vinculado / vinculado" a uma das interfaces de função disponíveis. Eu poderia ter seguido a implementação real, mas não conheço os detalhes reais da implementação e tenho medo do getPrice(String carId)acesso real (visibilidade) sobre a classe concreta. Se tiver, todo o trabalho feito com o ISP se torna inútil, porque está nas mãos do desenvolvedor fazer casting e trabalhar apenas com a interface Billable . Não importa quão metódicos somos, a tentação sempre estará lá.

3. Responsabilidade única

Receio não estar em posição de dizer se a dependência entre Care HttpClienté adequada, mas concordo com o @DocBrown, isso gera alguns avisos que valem a pena uma revisão de design. Nem a Lei de Demeter nem o ISP tornarão seu design "melhor" neste momento. Eles apenas mascaram o problema, não o corrigem.

Sugeri ao DocBrown o Padrão de Estratégia como uma possível solução. Concordei com ele que o padrão acrescenta complexidade, mas também acho que qualquer re-design o fará. É uma troca, quanto mais dissociação queremos, mais partes móveis temos (geralmente). De qualquer forma, acho que ambos concordam com um novo design, é altamente recomendável.

Resumindo

Não, você não precisa mover métodos concretos para classes externas para não torná-las acessíveis. Pode haver inúmeros consumidores. Você mudaria todos os métodos concretos para classes externas toda vez que um novo consumidor entra em jogo? Espero que não.

Laiv
fonte