2010-08-06 23 views
15

¿Existe un método o estilo preferido para crear una implementación predeterminada para los métodos de interfaz? Supongamos que tengo una interfaz de uso común donde en el 90% de los casos la funcionalidad que quería es idéntica.Par de implementación de interfaz Java

Mi primer instinto es crear una clase concreta con métodos estáticos. Luego, delegaría la funcionalidad a los métodos estáticos cuando quiera la funcionalidad predeterminada.

Aquí está un ejemplo simple:

Interface

public interface StuffDoer{ 
    public abstract void doStuff(); 
} 

aplicación concreta de métodos

public class ConcreteStuffDoer{ 
    public static void doStuff(){ 
     dosomestuff... 
    } 
} 

aplicación concreta funcionalidad usando predeterminado

public class MyClass implements StuffDoer{ 
    public void doStuff(){ 
     ConcreteSuffDoer.doStuff();   
    } 
} 

¿Hay un mejor enfoque aquí?

EDITAR

Después de ver algunas de las soluciones propuestas Creo que debería ser más clara acerca de mi intención. Básicamente, estoy tratando de evitar que Java permita la herencia múltiple. También para dejar en claro que no estoy tratando de hacer una declaración acerca de si Java debería permitir herencia múltiple o no. Solo busco la mejor manera de crear una implementación de método predeterminada para las clases que implementan una interfaz.

+0

Espero que logren agregar métodos defensores en Java 7, eso resolvería este problema ... – Landei

+1

Por cierto, así es como se implementan los rasgos (http://codecrafter.blogspot.com/2010/03/scala -traits-under-hood.html) en Scala. –

+0

De hecho, esto es similar a los rasgos que se describen aquí http://stackoverflow.com/questions/263121/java-traits-or-mixins-pattern/16337353#16337353. Para mantener las cosas más cohesionadas, puede mover la clase de implementación predeterminada como una clase anidada de la interfaz como ya se indicó por @Yishai. –

Respuesta

23

Este es el enfoque que tomaría:

public interface MyInterface { 

     MyInterface DEFAULT = new MyDefaultImplementation(); 

     public static class MyDefaultImplemenation implements MyInterface { 
     } 
} 

Por supuesto, el MyDefaultImplementation puede necesitar ser privadas, o de su propia clase de nivel superior, en función de lo que tiene sentido.

entonces usted puede tener lo siguiente en sus implementaciones:

public class MyClass implements MyInterface { 
     @Override 
     public int someInterfaceMethod(String param) { 
      return DEFAULT.someInterfaceMethod(param); 
     } 
    } 

Es un poco más auto-documentado, y en última instancia, más flexible, que una clase de implementación por defecto que existe en otros lugares, pero no se hace referencia por la interfaz. Con esto puede hacer cosas como simplemente pasar la implementación predeterminada como un parámetro de método cuando sea necesario (lo que no se puede hacer con los métodos estáticos).

Por supuesto, lo anterior solo funciona si no hay un estado involucrado.

+1

¿Es necesario agregar 'public static' a' MyDefaultImplemenation'? Creo que está implícito en el caso de una interfaz. – tuxdna

+1

@tuxdna, no, no es necesario. Es una preferencia de estilo de codificación. – Yishai

16

Puede convertir la interfaz en una clase abstracta y proporcionar la implementación predeterminada para los métodos, según corresponda.

Actualización: Veo, la herencia múltiple se cierra al cambiar la interfaz a una clase abstracta ... en este caso yo haría lo mismo que usted. Si la implementación predeterminada de los métodos no depende del estado, el mejor lugar para ellos es, de hecho, en una clase de utilidad estática. Sin embargo, si hay un estado involucrado, consideraría la composición de objetos, que incluso podría terminar en algo así como un Decorator.

+0

Sí, eso es lo que hace la API de colecciones java.util. Lo seguiría como mi modelo de diseño e implementación. – duffymo

+0

Mi problema con esa solución es cuando estoy tratando con clases que ya tienen padres diferentes. – N8g

+0

@ N8g, si es posible refactorizar la jerarquía de clases, aún podría trabajar gradualmente hacia este diseño. De lo contrario, terminas con muchos códigos duplicados en las subclases. Tener la implementación predeterminada proporcionada por una clase externa alivia este problema, pero no es la solución ideal. Por supuesto, en el mundo real no siempre podemos tener soluciones ideales :-( –

1

Sigo más o menos el patrón que aprendí inicialmente de Swing. Tengo una interfaz y luego creo una clase "Base" o "Adaptador". Para mí, un adaptador a menudo tendrá implementaciones de "no hacer nada" para todos los métodos de interfaz, para permitir que un implementador escriba solo los métodos que necesita sin tener en cuenta los demás. Una base será una clase abstracta que proporcione implementaciones convenientes para algunos de los métodos de interfaz, es de esperar la implementación "más probable".

Por ejemplo, tengo una interfaz SearchFilter que, entre otras cosas, tiene un método apply(Collection<T>). Ese método casi siempre recorrerá la colección y llamará al método de interfaz boolean include(T item) para decidir si mantener o filtrar el elemento. My SearchFilterBase proporciona eso como una implementación para apply() y un implementador solo tiene que escribir su lógica include().

Los implementadores son libres, por supuesto, para implementar simplemente toda la interfaz y no derivar de la Base, que es la ventaja de cambiar la interfaz a una clase abstracta, lo que les obliga a usar su herencia única (Esta es la problema con java.util.Observable)


En respuesta al comentario de N8g - Puede subclase de la base o el adaptador, pero no es requerido subclase - se puede implementar la interfaz a sí mismo a partir de cero. La base o el adaptador se proporcionan para su comodidad, implementando métodos no operativos para que usted no tenga que hacerlo, o implementando una funcionalidad común conveniente en una clase base abstracta (como mi clase SearchFilterBase). La ventaja que tiene sobre convertir la interfaz en una clase abstracta es que no fuerce la herencia de su clase abstracta.

+0

Me puede estar perdiendo la intención de su enfoque, pero parece que todavía necesitaría subclasificar su clase base o su adaptador en estos ejemplos, lo que vuelve a la falta de herencia múltiple en Java que estoy tratando de solucionar aquí. – N8g

1

También utilizaría métodos estáticos para declarar la funcionalidad predeterminada para la funcionalidad sin estado.

Para una funcionalidad con estado, prefiero la composición en lugar de la herencia. Con la composición y el uso de delegados/adaptadores, puede combinar la funcionalidad predeterminada de muchas fuentes.

p. Ej.

public interface StuffDoer{ 
    void doStuff(); 
    void doOtherStuff(); 
} 

public class MyStuffDoer implements StuffDoer{ 
    private final StuffDoer mixin; 
    public MyStuffDoer(StuffDoer mixin){ 
     this.mixin = mixin; 
    } 
    public void doStuff(){ 
     mixin.doStuff(); 
    } 
    public void doOtherStuff(){ 
     mixin.doOtherStuff(); 
    } 

} 

public class MyStuffDoer2 implements StuffDoer{ 
    private final StuffDoer mixin1, mixin2; 
    public MyStuffDoer(StuffDoer mixin1, StuffDoer mixin2){ 
     this.mixin1 = mixin1; 
     this.mixin2 = mixin2; 
    } 
    public void doStuff(){ 
     mixin1.doStuff(); 
    } 
    public void doOtherStuff(){ 
     mixin2.doOtherStuff(); 
    } 

} 

Para casos simples, la herencia también está bien, pero no es realmente muy flexible.

La implementación de múltiples interfaces también es un caso donde este enfoque se escala mejor.

public interface A{ 
    void doStuff(); 
} 

public interface B{ 
    void doOtherStuff(); 
} 

public class MyStuffDoer implements A, B{ 
    private final A mixin1; 
    private final B mixin2; 
    public MyStuffDoer(A mixin1, B mixin2){ 
     this.mixin1 = mixin1; 
     this.mixin2 = mixin2; 
    } 
    public void doStuff(){ 
     mixin1.doStuff(); 
    } 
    public void doOtherStuff(){ 
     mixin2.doOtherStuff(); 
    } 
} 

No puede hacer esto con las clases abstractas. He utilizado este enfoque de composición en algunos proyectos, y funcionó bastante bien.

+0

Este es un enfoque interesante. Hacer que el constructor tome la clase que implementa la interfaz es una buena idea, ya que hace las cosas más explícitas. Todavía no estoy seguro sobre el requisito de funcionalidad que requiere estado. Parece que el estado que te interesaría es la clase "MyStuffDoer" y crear instancias de la clase mixin no soluciona esto. ¿Por qué sus métodos estáticos no solo tomarían su clase MyStuffDoer como un parm? Más genéricamente, ¿por qué no tomar una instancia de la interfaz? – N8g

+0

El estado está en la mezcla. Y normalmente no se puede acceder al estado a través de la firma del servicio, por lo que el servicio en sí mismo como parámetro del método estático no funciona. Puede suministrar todo el estado al método a través de parámetros separados, pero de esta manera obtendrá firmas de método bastante detalladas. Los métodos estáticos son realmente buenos para el comportamiento sin estado, todo lo demás debe ser manejado por métodos de instancia. –

1

implementaciones estáticas tienen dos problemas:

  • que realmente no los puede encontrar con cualquier análisis de código estático, como una lista de todos los ejecutores, debido a que el método estático no implementa la interfaz, por lo que está obligado a mencionar la implementación por defecto en el javadoc

  • a veces hay la necesidad de tener un estado en el implementador, que realmente no se puede hacer dentro de un método estático

Por lo tanto, prefiero usar una clase concreta que permita tanto herencia (si no está limitado por herencia individual) como composición, y llame a la clase resultante * Peer, ya que normalmente se usará junto con la clase principal que implementa La interfaz. El par implementará la interfaz, y también puede tener una referencia al objeto principal en caso de que necesite disparar eventos en el nombre de la clase principal.

+0

El título de la pregunta es "Par de implementación de interfaz Java". Estoy limitado a la herencia individual. Supongo que los problemas de estado podrían ser manejados por métodos estáticos que toman un parámetro de la interfaz.Esto me permitiría llamar al método estático y pasar el objeto que llama. Por supuesto, necesitaría asegurarme de que los métodos/atributos necesarios para la funcionalidad predeterminada también estuvieran definidos en la interfaz, pero eso parece razonable. de todas formas. – N8g

+0

Solo está limitado por la herencia individual si desea extender algo que no sea la implementación concreta (en cuyo caso usaría la composición con la misma clase). – mkadunc

+0

Pasar el objeto que llama a métodos estáticos lo obligará a exponer el estado del objeto a través de la misma interfaz pública, lo que de alguna manera limita sus opciones de implementación. Además, necesitará campos en cada implementación para almacenar el estado, mientras que la clase par almacenará el estado por usted. – mkadunc

0

La composición supera a la herencia múltiple en términos de flexibilidad. Al igual que con el Patrón de Adaptador (vea el libro de GOF), claramente nos permite adaptar (cambiar) las clases a diferentes comportamientos dependiendo de la solicitud de la persona que llama. Consulte la interfaz IAdaptable en Eclipse RCP. Básicamente, usar interfaces es lo mismo que usar el Patrón de Adaptador integrado en el lenguaje Java directamente. Pero usar clases adaptables es mejor que implementar múltiples interfaces. Las clases adaptables son más flexibles, pero tienen cierta sobrecarga de CPU: el operador instanceof consume menos CPU que la llamada canAdapt (Class clazz). Pero en las JVM modernas esto podría ser falso, ya que calcular instanceof con interfaces es más complejo que calcular instanceof con herencia.

1

mayoría de las veces, voy con algo como esto:

public interface Something { 
    public void doSomething(); 

    public static class Static { 
    public static void doSomething(Something self) { 
     // default implementation of doSomething() 
     // the name of the method is often the same 
     // the arguments might differ, so state can be passed 
    } 
    } 

    public static abstract class Abstract implements Something { 
    // the default abstract implementation 
    } 

    public static class Default extends Abstract { 
    // the default implementation 
    public void doSomething() { 
     Static.doSomething(this); 
    } 
    } 

    public static interface Builder extends Provider<Something> { 
    // initializes the object 
    } 
} 

que tienden a utilizar las clases internas debido a que estas clases están muy relacionados, pero también se pueden utilizar las clases regulares.

Además, es posible que desee colocar la clase de utilidad (Static) en un archivo separado si los métodos no solo afectan a la interfaz.


Por cierto, el problema con los decoradores es que ocultan otras interfaces implementadas. En otras palabras, si tiene un decorador, lo siguiente es falso new SomethingDecorator(new ArrayList<Object>()) instanceof List<?> si SomethingDecorator no implementa la interfaz de listas. Puede trabajar alrededor usando la API de reflexión y en particular usando un Proxy.

Otro buen método son los adaptadores, como señaló PersicsB.

1

Dependency injection podría proporcionar el delegado y especificar una implementación predeterminada, que podría anularse en las pruebas unitarias.

Por ejemplo, el marco Guice admite una anotación en la interfaz para su implementación predeterminada, que puede anularse mediante un enlace explícito en pruebas unitarias.

@ImplementedBy(ConcreteStuffDoer.class) 
public interface StuffDoer{ 
    void doStuff(); 
} 

public class ConcreteStuffDoer implements StuffDoer { 
    public void doStuff(){ ... } 
} 

public class MyClass implements StuffDoer{ 
    @Inject StuffDoer m_delegate; 
    public void doStuff(){ m_delegate.doStuff(); } 
} 

Esto hace introducir el requisito de que la creación de una instancia de MyClass requiere un método Guice en lugar de simplemente new.

0

Si su función es :-) funciona como estático, entonces no necesita delegarlo todo, ya que eso significa que no necesita "esto", es una función de utilidad.

Se puede volver a examinar si realmente tienes razón abrumadora de no fusionar lo tiene en cuenta implementaciones por defecto en la primera clase abstracta que implementa esa interfaz - usted sabe, la adaptación a la realidad :-)

Además, puede crear métodos virtuales protegidos en clase abstracta que los métodos derivados de la interfaz van a invocar, lo que le dará más control; es decir, la clase derivada no tiene que reemplazar los métodos y copie & pegue el 90% de su código; puede sobrescribirlo un método que se llama desde los métodos principales.Te da un grado de herencia de comportamiento.

7

Ahora que Java 8 está fuera, este patrón es mucho más agradable:

public interface StuffDoer{ 
    default void doStuff() { 
     dosomestuff... 
    } 
} 

public class MyClass implements StuffDoer { 
    // doStuff automatically defined 
} 

Lambda tiene toda la atención, pero esto era el beneficio más tangible de Java 8 para nuestra empresa. Nuestras jerarquías de herencia se hicieron mucho más simples cuando ya no necesitábamos tener clases abstractas solo para tener implementaciones de métodos por defecto.

Cuestiones relacionadas