Java: colisión de nombres de métodos en la implementación de la interfaz

88

Si tengo dos interfaces, ambas bastante diferentes en sus propósitos, pero con la misma firma de método, ¿cómo hago para que una clase implemente ambas sin estar obligado a escribir un solo método que sirva para ambas interfaces y escribir alguna lógica complicada en el método? implementación que verifica para qué tipo de objeto se está realizando la llamada e invoca el código adecuado?

En C #, esto se supera mediante lo que se denomina implementación de interfaz explícita. ¿Hay alguna forma equivalente en Java?

Bhaskar
fuente
37
Cuando una clase tiene que implementar dos métodos con la misma firma que hacen cosas diferentes , es casi seguro que su clase está haciendo demasiadas cosas.
Joachim Sauer
15
Es posible que lo anterior no siempre sea cierto en mi opinión. A veces, en una sola clase, necesita métodos que deben confirmar un contrato externo (por lo tanto, restringiendo las firmas), pero que tienen diferentes implementaciones. De hecho, estos son requisitos comunes al diseñar una clase no trivial. La sobrecarga y la anulación son necesariamente mecanismos para permitir métodos que hacen cosas diferentes que pueden no diferir en la firma, o diferir muy levemente. Lo que tengo aquí es un poco más restrictivo que no permite subclases / y no permite incluso mínima variación de firmas.
Bhaskar
1
Me intrigaría saber cuáles son estas clases y métodos.
Uri
2
Encontré un caso en el que una clase heredada "Dirección" implementaba interfaces Persona y Firma que tenían un método getName () simplemente devolviendo una Cadena del modelo de datos. Un nuevo requisito empresarial especificó que Person.getName () devuelva una cadena formateada como "Apellido, Nombres". Después de mucha discusión, los datos se reformaron en su lugar en la base de datos.
Belwood
12
Simplemente decir que la clase casi con certeza está haciendo demasiadas cosas NO ES CONSTRUCTIVO. Tengo este mismo caso en este momento que mi clase tiene colisiones de nombres de métodos de 2 interfaces diferentes, y mi clase NO está haciendo demasiadas cosas. Los propósitos son bastante similares, pero hacen cosas ligeramente diferentes. ¡No intente defender un lenguaje de programación obviamente gravemente discapacitado acusando al interrogador de implementar un mal diseño de software!
j00hi

Respuestas:

75

No, no hay forma de implementar el mismo método de dos formas diferentes en una clase en Java.

Eso puede llevar a muchas situaciones confusas, razón por la cual Java lo ha rechazado.

interface ISomething {
    void doSomething();
}

interface ISomething2 {
    void doSomething();
}

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

Lo que puede hacer es componer una clase de dos clases, cada una de las cuales implementa una interfaz diferente. Entonces esa clase tendrá el comportamiento de ambas interfaces.

class CompositeClass {
    ISomething class1;
    ISomething2 class2;
    void doSomething1(){class1.doSomething();}
    void doSomething2(){class2.doSomething();}
}
jjnguy
fuente
9
Pero de esta manera, no puedo pasar una instancia de CompositeClass en algún lugar, ¿se espera una referencia de las interfaces (ISomething o ISomething2)? Ni siquiera puedo esperar que el código de cliente pueda transmitir mi instancia a la interfaz adecuada, así que ¿no estoy perdiendo algo con esta restricción? También tenga en cuenta que de esta manera, al escribir clases que realmente implementan las respectivas interfaces, perdemos el beneficio de tener el código en una sola clase, lo que a veces puede ser un serio impedimento.
Bhaskar
9
@Bhaskar, haces puntos válidos. El mejor consejo que tengo es agregar un método ISomething1 CompositeClass.asInterface1();y ISomething2 CompositeClass.asInterface2();a esa clase. Entonces puede simplemente sacar uno u otro de la clase compuesta. Sin embargo, no existe una gran solución para este problema.
jjnguy
1
Hablando de las situaciones confusas a las que esto puede conducir, ¿puede dar un ejemplo? ¿No podemos pensar en el nombre de la interfaz agregado al nombre del método como una resolución de alcance adicional que luego puede evitar la colisión / confusión?
Bhaskar
@Bhaskar Es mejor si nuestras clases se adhieren al principio de responsabilidad única. Si existe una clase que implementa dos interfaces muy diferentes, creo que el diseño debería ser reelaborado para dividir las clases y hacerse cargo de una sola responsabilidad.
Anirudhan J
1
¿Qué tan confuso sería, realmente, permitir algo como public long getCountAsLong() implements interface2.getCount {...}[en caso de que la interfaz requiera un longpero los usuarios de la clase esperan int] o private void AddStub(T newObj) implements coolectionInterface.Add[suponiendo que collectionInterfacetenga un canAdd()método, y para todas las instancias de esta clase devuelve false]?
supercat
13

No hay una forma real de resolver esto en Java. Podría usar clases internas como solución:

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()
        };
    }
}

Aunque no permite lanzamientos de AlfaBetaa Beta, los abatidos son generalmente malvados, y si se puede esperar que una Alfainstancia a menudo también tenga un Betaaspecto, y por alguna razón (generalmente la optimización es la única razón válida), querrás poder para convertirlo en Beta, usted podría hacer un sub-interfaz de Alfacon Beta asBeta()en ella.

gustafc
fuente
¿Te refieres a una clase anónima en lugar de una clase interna?
Zaid Masud
2
@ZaidMasud Me refiero a clases internas, ya que pueden acceder al estado privado del objeto adjunto). Por supuesto, estas clases internas también pueden ser anónimas.
gustafc
11

Si se encuentra con este problema, lo más probable es que esté usando herencia donde debería usar la delegación . Si necesita proporcionar dos interfaces diferentes, aunque similares, para el mismo modelo subyacente de datos, entonces debe usar una vista para proporcionar acceso económico a los datos utilizando alguna otra interfaz.

Para dar un ejemplo concreto para el último caso, suponga que desea implementar ambos Collectiony MyCollection(que no hereda Collectiony tiene una interfaz incompatible). Puede proporcionar funciones de Collection getCollectionView()y MyCollection getMyCollectionView()que proporcionen una implementación ligera de Collectiony MyCollection, utilizando los mismos datos subyacentes.

Para el primer caso ... suponga que realmente desea una matriz de enteros y una matriz de cadenas. En lugar de heredar de ambos List<Integer>y List<String>, debe tener un miembro de tipo List<Integer>y otro miembro de tipo List<String>, y hacer referencia a esos miembros, en lugar de intentar heredar de ambos. Incluso si solo necesita una lista de números enteros, es mejor usar composición / delegación sobre herencia en este caso.

Michael Aaron Safyan
fuente
No lo creo. Te estás olvidando de las bibliotecas que requieren que implementes diferentes interfaces para ser compatibles con ellas. Puede encontrarse con esto utilizando varias bibliotecas en conflicto con mucha más frecuencia de lo que se encuentra con esto en su propio código.
Nightpool
1
@nightpool si usa varias bibliotecas que requieren diferentes interfaces, todavía no es necesario que un solo objeto implemente ambas interfaces; puede hacer que el objeto tenga descriptores de acceso para devolver cada una de las dos interfaces diferentes (y llamar al descriptor de acceso apropiado cuando pase el objeto a una de las bibliotecas subyacentes).
Michael Aaron Safyan
1

El problema "clásico" de Java también afecta mi desarrollo de Android ...
La razón parece ser simple:
más frameworks / bibliotecas tienes que usar, más fácilmente las cosas pueden estar fuera de control ...

En mi caso, tengo una clase BootStrapperApp heredado de android.app.Application ,
mientras que la misma clase también debería implementar una interfaz de plataforma de un marco MVVM para integrarse.
La colisión de métodos ocurrió en un método getString () , que es anunciado por ambas interfaces y debe tener una implementación de red diferente en diferentes contextos.
La solución alternativa (feo..IMO) es usar una clase interna para implementar todas las plataformasmétodos, solo debido a un conflicto menor de firma de método ... en algún caso, tal método prestado ni siquiera se usa en absoluto (pero afecta la semántica de diseño principal).
Tiendo a estar de acuerdo en que la indicación explícita de contexto / espacio de nombres estilo C # es útil.

Tiancheng
fuente
1
Nunca me di cuenta de cómo C # era reflexivo y rico en funciones hasta que comencé a usar Java para el desarrollo de Android. Di esas características de C # por sentado. Java carece de muchas funciones.
Malditas verduras
1

La única solución que me vino a la mente es usar objetos de referencia para el que desea implementar múltiples interfaces.

por ejemplo: suponiendo que tiene 2 interfaces para implementar

public interface Framework1Interface {

    void method(Object o);
}

y

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

puede incluirlos en dos 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);
    }
}

y

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);
    }
}

Al final, la clase que querías debería ser 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) {
    }
}

ahora puede usar los métodos getAs * para "exponer" su clase

De ningún modo
fuente
0

Puede utilizar un patrón de Adaptador para que estos funcionen. Cree dos adaptadores para cada interfaz y utilícelos. Debería resolver el problema.

AlexCon
fuente
-1

Todo está bien cuando tienes control total sobre todo el código en cuestión y puedes implementarlo por adelantado. Ahora imagine que tiene una clase pública existente que se utiliza en muchos lugares con un método

public class MyClass{

    private String name;

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

    public String getName(){
        return name;
    }
}

Ahora debe pasarlo al WizzBangProcessor estándar que requiere clases para implementar la WBPInterface ... que también tiene un método getName (), pero en lugar de su implementación concreta, esta interfaz espera que el método devuelva el nombre de un tipo del procesamiento de Wizz Bang.

En C # sería un truco

public class MyClass : WBPInterface{

    private String name;

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

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

    public String getName(){
        return name;
    }
}

En Java Tough, tendrá que identificar todos los puntos de la base de código implementado existente donde necesita convertir de una interfaz a otra. Seguro que la empresa WizzBangProcessor debería haber utilizado getWizzBangProcessName (), pero también son desarrolladores. En su contexto, getName estaba bien. En realidad, fuera de Java, la mayoría de los otros lenguajes basados ​​en OO admiten esto. Java es raro en forzar que todas las interfaces se implementen con el mismo método NAME.

La mayoría de los otros lenguajes tienen un compilador que está más que feliz de tomar una instrucción para decir "este método en esta clase que coincide con la firma de este método en esta interfaz implementada es su implementación". Después de todo, el objetivo de definir interfaces es permitir que la definición se abstraiga de la implementación. (Ni siquiera me hagas empezar a tener métodos predeterminados en interfaces en Java, y mucho menos anulación predeterminada ... porque seguro, todos los componentes diseñados para un automóvil de carretera deberían poder chocar contra un automóvil volador y simplemente funcionar, oye Ambos son automóviles ... Estoy seguro de que la funcionalidad predeterminada de, digamos, su navegador por satélite no se verá afectada con las entradas de cabeceo y balanceo predeterminadas, ¡porque los automóviles solo giran!

usuario8232920
fuente