Java - Colisão do nome do método na implementação da interface


87

Se eu tenho duas interfaces, ambas bastante diferentes em seus propósitos, mas com a mesma assinatura de método, como faço para fazer uma classe implementar ambas sem ser forçado a escrever um único método que sirva para ambas as interfaces e escrever alguma lógica complicada no método implementação que verifica para qual tipo de objeto a chamada está sendo feita e invoca o código adequado?

Em C #, isso é superado pelo que é chamado de implementação de interface explícita. Existe alguma forma equivalente em Java?


37
Quando uma classe precisa implementar dois métodos com a mesma assinatura que fazem coisas diferentes , é quase certo que sua classe está fazendo muitas coisas.
Joachim Sauer

15
O exposto acima pode nem sempre ser verdadeiro IMO. Às vezes, em uma única classe, você precisa de métodos que devem confirmar a um contrato externo (portanto, restringindo as assinaturas), mas que têm implementações diferentes. Na verdade, esses são requisitos comuns ao projetar uma classe não trivial. Sobrecarga e sobrescrever são necessariamente mecanismos para permitir métodos que fazem coisas diferentes que podem não diferir na assinatura, ou muito ligeiramente. O que eu tenho aqui é apenas um pouco mais restritivo que não permite a subclasse de / e não permite nem mesmo menor variação nas assinaturas.
Bhaskar,

1
Eu ficaria intrigado em saber o que são essas classes e métodos.
Uri,

2
Eu encontrei um caso onde uma classe legada "Address" implementou interfaces Person e Firm que tinham um método getName () simplesmente retornando uma String do modelo de dados. Um novo requisito de negócios especificou que o Person.getName () retornasse uma String formatada como "Sobrenome, Nomes próprios". Depois de muita discussão, os dados foram formatados novamente no banco de dados.
belwood

12
Apenas afirmar que a classe quase certamente está fazendo coisas demais NÃO É CONSTRUTIVO. Eu tenho esse mesmo caso agora que minha classe tem colisões de nomes de mehod de 2 interfaces diferentes, e minha classe NÃO está fazendo muitas coisas. Os objetivos são bastante semelhantes, mas fazem coisas ligeiramente diferentes. Não tente defender uma linguagem de programação obviamente severamente prejudicada acusando o questionador de implementar um projeto de software ruim!
j00hi

Respostas:


75

Não, não há como implementar o mesmo método de duas maneiras diferentes em uma classe em Java.

Isso pode levar a muitas situações confusas, e é por isso que Java não permite isso.

interface ISomething {
    void doSomething();
}

interface ISomething2 {
    void doSomething();
}

class Impl implements ISomething, ISomething2 {
   void doSomething() {} // There can only be one implementation of this method.
}

O que você pode fazer é compor uma classe de duas classes, cada uma implementando uma interface diferente. Então, essa classe terá o comportamento de ambas as interfaces.

class CompositeClass {
    ISomething class1;
    ISomething2 class2;
    void doSomething1(){class1.doSomething();}
    void doSomething2(){class2.doSomething();}
}

9
Mas dessa forma, não consigo passar uma instância de CompositeClass em algum lugar onde uma referência das interfaces (ISomething ou ISomething2) é esperada? Não posso nem mesmo esperar que o código do cliente seja capaz de lançar minha instância para a interface apropriada, então não estou perdendo algo com essa restrição? Observe também que, desta forma, ao escrever classes que realmente implementam as respectivas interfaces, perdemos o benefício de ter o código em uma única classe, o que às vezes pode ser um sério impedimento.
Bhaskar,

9
@Bhaskar, você faz pontos válidos. O melhor conselho que tenho é adicionar um método ISomething1 CompositeClass.asInterface1();e ISomething2 CompositeClass.asInterface2();a essa classe. Em seguida, você pode obter apenas um ou outro da classe composta. No entanto, não existe uma grande solução para este problema.
jjnguy de

1
Falando das situações confusas a que isso pode levar, você pode dar um exemplo? Não podemos pensar no nome da interface adicionado ao nome do método como uma resolução de escopo extra que pode evitar a colisão / confusão?
Bhaskar,

@Bhaskar É melhor que nossas classes sigam o princípio de responsabilidade única. Se existe uma classe que implementa duas interfaces muito diferentes, acho que o design deve ser retrabalhado para dividir as classes para cuidar da responsabilidade única.
Anirudhan J

1
O quão confuso seria, realmente, permitir algo como public long getCountAsLong() implements interface2.getCount {...}[no caso de a interface requerer um longmas os usuários da classe esperam int] ou private void AddStub(T newObj) implements coolectionInterface.Add[assumindo que collectionInterfacetem um canAdd()método, e para todas as instâncias desta classe ele retorna false]?
supercat

13

Não há uma maneira real de resolver isso em Java. Você pode usar classes internas como uma solução alternativa:

interface Alfa { void m(); }
interface Beta { void m(); }
class AlfaBeta implements Alfa {
    private int value;
    public void m() { ++value; } // Alfa.m()
    public Beta asBeta() {
        return new Beta(){
            public void m() { --value; } // Beta.m()
        };
    }
}

Embora não permita conversões de AlfaBetapara Beta, downcasts geralmente são ruins e, se for esperado que uma Alfainstância também tenha um Betaaspecto, por algum motivo (geralmente a otimização é o único motivo válido), você deseja poder para convertê-lo Beta, você poderia fazer uma subinterface Alfacom Beta asBeta()nele.


Você quer dizer classe anônima em vez de classe interna?
Zaid Masud

2
@ZaidMasud quero dizer classes internas, uma vez que podem acessar o estado privado do objeto envolvente). Essas classes internas também podem ser anônimas.
gustafc

11

Se você estiver encontrando esse problema, é mais provável que esteja usando herança, onde deveria usar delegação . Se você precisar fornecer duas interfaces diferentes, embora semelhantes, para o mesmo modelo de dados subjacente, você deve usar uma visualização para fornecer acesso aos dados de maneira econômica usando alguma outra interface.

Para dar um exemplo concreto para o último caso, suponha que você deseja implementar Collectione MyCollection(que não herda de Collectione tem uma interface incompatível). Você pode fornecer funções a Collection getCollectionView()e MyCollection getMyCollectionView()que fornecem uma implementação leve de Collectione MyCollection, usando os mesmos dados subjacentes.

Para o primeiro caso ... suponha que você realmente queira um array de inteiros e um array de strings. Em vez de herdar de ambos List<Integer>e List<String>, você deve ter um membro do tipo List<Integer>e outro membro do tipo List<String>e referir-se a esses membros, em vez de tentar herdar de ambos. Mesmo que você precise apenas de uma lista de inteiros, é melhor usar composição / delegação sobre herança neste caso.


Acho que não. Você está se esquecendo das bibliotecas que requerem a implementação de interfaces diferentes para serem compatíveis com elas. Você pode se deparar com isso usando várias bibliotecas conflitantes com muito mais frequência do que em seu próprio código.
Nightpool de

1
@nightpool se você usar várias bibliotecas em que cada uma requer interfaces diferentes, ainda não é necessário que um único objeto implemente ambas as interfaces; você pode fazer com que o objeto tenha acessores para retornar cada uma das duas interfaces diferentes (e chamar o acessador apropriado ao passar o objeto para uma das bibliotecas subjacentes).
Michael Aaron Safyan

1

O problema "clássico" do Java também afeta meu desenvolvimento Android ...
A razão parece ser simples:
mais frameworks / bibliotecas que você tem que usar, mais facilmente as coisas podem ficar fora de controle ...

No meu caso, eu tenho uma classe BootStrapperApp herdado de android.app.Application ,
enquanto a mesma classe também deve implementar uma interface de plataforma de uma estrutura MVVM para ser integrada.
A colisão de métodos ocorreu em um método getString () , que é anunciado por ambas as interfaces e deve ter implementação diferente em contextos diferentes.
A solução alternativa (feio..IMO) é usar uma classe interna para implementar todas as plataformasmétodos, apenas por causa de um conflito menor de assinatura de método ... em alguns casos, tal método emprestado nem mesmo é usado (mas afetou a semântica de design principal).
Eu tendo a concordar que a indicação explícita de contexto / namespace no estilo C # é útil.


1
Nunca percebi como o C # era pensativo e rico em recursos até começar a usar Java para desenvolvimento Android. Eu considerava esses recursos C # garantidos. Java não possui muitos recursos.
Damn Vegetables

1

A única solução que me veio à mente é usar objetos de referência para aquele que você deseja implantar interfaces múltiplas.

por exemplo: supondo que você tenha 2 interfaces para implementar

public interface Framework1Interface {

    void method(Object o);
}

e

public interface Framework2Interface {
    void method(Object o);
}

você pode agrupá-los em dois objetos Facador:

public class Facador1 implements Framework1Interface {

    private final ObjectToUse reference;

    public static Framework1Interface Create(ObjectToUse ref) {
        return new Facador1(ref);
    }

    private Facador1(ObjectToUse refObject) {
        this.reference = refObject;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Framework1Interface) {
            return this == obj;
        } else if (obj instanceof ObjectToUse) {
            return reference == obj;
        }
        return super.equals(obj);
    }

    @Override
    public void method(Object o) {
        reference.methodForFrameWork1(o);
    }
}

e

public class Facador2 implements Framework2Interface {

    private final ObjectToUse reference;

    public static Framework2Interface Create(ObjectToUse ref) {
        return new Facador2(ref);
    }

    private Facador2(ObjectToUse refObject) {
        this.reference = refObject;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Framework2Interface) {
            return this == obj;
        } else if (obj instanceof ObjectToUse) {
            return reference == obj;
        }
        return super.equals(obj);
    }

    @Override
    public void method(Object o) {
        reference.methodForFrameWork2(o);
    }
}

No final, a aula que você queria deve algo como

public class ObjectToUse {

    private Framework1Interface facFramework1Interface;
    private Framework2Interface facFramework2Interface;

    public ObjectToUse() {
    }

    public Framework1Interface getAsFramework1Interface() {
        if (facFramework1Interface == null) {
            facFramework1Interface = Facador1.Create(this);
        }
        return facFramework1Interface;
    }

    public Framework2Interface getAsFramework2Interface() {
        if (facFramework2Interface == null) {
            facFramework2Interface = Facador2.Create(this);
        }
        return facFramework2Interface;
    }

    public void methodForFrameWork1(Object o) {
    }

    public void methodForFrameWork2(Object o) {
    }
}

agora você pode usar os métodos getAs * para "expor" sua classe


0

Você pode usar um padrão Adaptador para fazer isso funcionar. Crie dois adaptadores para cada interface e use-os. Deve resolver o problema.


-1

Tudo bem quando você tem controle total sobre todo o código em questão e pode implementar isso antecipadamente. Agora imagine que você tem uma classe pública existente usada em muitos lugares com um método

public class MyClass{

    private String name;

    MyClass(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

Agora você precisa passá-lo para o WizzBangProcessor de prateleira, que requer classes para implementar a WBPInterface ... que também tem um método getName (), mas em vez de sua implementação concreta, esta interface espera que o método retorne o nome de um tipo do processamento Wizz Bang.

Em C # seria um trvial

public class MyClass : WBPInterface{

    private String name;

    String WBPInterface.getName(){
        return "MyWizzBangProcessor";
    }

    MyClass(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

Em Java Tough, você terá que identificar cada ponto na base de código implementado existente onde você precisa converter de uma interface para outra. Claro, a empresa WizzBangProcessor deveria ter usado getWizzBangProcessName (), mas eles também são desenvolvedores. Em seu contexto, getName estava bem. Na verdade, fora do Java, a maioria das outras linguagens baseadas em OO suportam isso. Java é raro em forçar todas as interfaces a serem implementadas com o mesmo método NAME.

A maioria das outras linguagens tem um compilador que fica mais do que feliz em receber uma instrução para dizer "este método nesta classe que corresponde à assinatura deste método nesta interface implementada é a sua implementação". Afinal, o objetivo da definição de interfaces é permitir que a definição seja abstraída da implementação. (Nem me fale sobre como ter métodos padrão em Interfaces em Java, muito menos sobreposição padrão ... porque com certeza, cada componente projetado para um carro de estrada deve ser capaz de bater em um carro voador e apenas funcionar - ei ambos são carros ... Tenho certeza que a funcionalidade padrão de digamos que seu navegador por satélite não será afetado com as entradas padrão de pitch e roll, porque os carros apenas guinam!

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.