¿Es la "interfaz estática" una buena práctica?

13

Recientemente me di cuenta de que hay una opción para tener métodos estáticos en las interfaces. Al igual que con los campos estáticos de interfaz, hay un comportamiento interesante: estos no se heredan.

No estoy seguro de que sea útil en las interfaces reales que se implementarán. Sin embargo, permite al programador crear interfaces que son solo sobres para cosas estáticas, como por ejemplo, las clases de utilidad.

Un ejemplo simple es solo un sobre para constantes globales. En comparación con una clase, puede notar fácilmente la repetitiva faltante, public static finalya que se asume (lo que lo hace menos detallado).

public interface Constants {
    String LOG_MESSAGE_FAILURE = "I took an arrow to the knee.";
    int DEFAULT_TIMEOUT_MS = 30;
}

También podría hacer algo más complejo, como este pseudoenum de claves de configuración.

public interface ConfigKeys {
    static createValues(ConfigKey<?>... values) {
        return Collections.unmodifiableSet(new HashSet(Arrays.asList(values)));
    }

    static ConfigKey<T> key(Class<T> clazz) {
        return new ConfigKey<>(clazz);
    }

    class ConfigKey<T> {
        private final Class<T> type;
        private ConfigKey(Class<T> type) {
            this.type = type;
        }
        private Class<T> getType() {
            return type;
        }
    }
}

import static ConfigKeys.*;
public interface MyAppConfigKeys {
    ConfigKey<Boolean> TEST_MODE = key(Boolean.class);
    ConfigKey<String> COMPANY_NAME = key(String.class);

    Set<ConfigKey<?>> VALUES = createValues(TEST_MODE, COMPANY_VALUE);

    static values() {
        return VALUES;
    }
}

También podría crear alguna "clase" de utilidad de esta manera. Sin embargo, en las utilidades, a menudo es útil usar métodos auxiliares privados o protegidos, lo cual no es posible en las clases.

Considero que es una nueva característica agradable, y especialmente el hecho de que los miembros estáticos no se heredan es un concepto interesante que se introdujo solo en las interfaces.

Me pregunto si puedes considerarlo una buena práctica. Si bien el estilo de código y las mejores prácticas no son axiomáticas y hay espacio para la opinión, creo que generalmente hay razones válidas para respaldar la opinión.

Estoy más interesado en las razones (no) para usar patrones como estos dos.


Tenga en cuenta que no tengo la intención de implementar esas interfaces. Son simplemente un sobre para su contenido estático. Solo pretendo usar las constantes o métodos y posiblemente usar la importación estática.

Vlasec
fuente
1
Mi reacción instintiva a esto es que solo puede conducir a cosas malas. Aunque los métodos estáticos públicos son esencialmente funciones globales con espacios de nombres. Parece que permitir que cualquier código de implementación en una interfaz vaya en contra de su propósito como una definición de contrato sin implementación. Yo digo que deje una implementación parcial a las clases abstractas. Tendré que leer sobre por qué se agregó esto.
Adrian
Tengo que ir por ahora, pero mañana escribiré algo sobre eso. En resumen, antes de que una interfaz tuviera un propósito claro que estaba claramente separado del propósito de una clase abstracta. Ahora se superponen fuertemente de una manera que viola la definición agnóstica del lenguaje de una interfaz. Además, hay casos en los que ya no puede heredar de varias interfaces si implementan métodos predeterminados. Entonces, ahora hay excepciones a la herencia de múltiples interfaces en las que no solía haber ninguna. Dado que Java se ha desviado drásticamente de la definición común de una interfaz ...
Adrian
También podría confundir a los nuevos desarrolladores que están tratando de obtener un manejo adecuado de los conceptos fundamentales de OOP como Interfaces, AbstractClases, cuándo usar la composición en lugar de la herencia, etc.
Adrian
Aliester, estás hablando de defaultmétodos. Estoy hablando de staticmétodos y campos. Esos no son heredados, por lo que no rompen la herencia múltiple.
Vlasec

Respuestas:

1

Un ejemplo simple es solo un sobre para constantes globales.

Poner constantes en las interfaces se llama Constant Interface Antipatterndado que las constantes a menudo son simplemente un detalle de implementación. Otros inconvenientes se resumen en el artículo de Wikipedia sobre la interfaz de Constant .

También podría crear alguna "clase" de utilidad de esta manera.

De hecho, el documento de Java establece que los métodos estáticos pueden facilitar la organización de los métodos auxiliares, por ejemplo, al llamar a los métodos auxiliares desde los métodos predeterminados.

Markus Pscheidt
fuente
1
No creo que esta sea realmente una respuesta a la pregunta planteada, ya que podría hacer ambas cosas sin esta nueva funcionalidad.
Adrian
Ah, entonces, según la documentación, pretende ser un método auxiliar. Sin embargo, también es público, lo que lo contradice. Por lo tanto, tal vez esté destinado a ayudar también a las clases que implementan la interfaz. Pero no son heredados, por lo que tendrías que usar la importación estática para que parezca que la has heredado. Bueno, gracias por buscarlo en los documentos.
Vlasec
2
No hay absolutamente nada de malo en poner constantes en las interfaces, si esas constantes son de alguna manera parte de la API descrita por la interfaz. Lo único que es un antipatrón es poner constantes en interfaces sin métodos y hacer que las clases implementen la interfaz solo para referirse a las constantes sin un prefijo de clase. Es un abuso de las interfaces y, desde la introducción de las importaciones estáticas, es completamente obsoleto.
Michael Borgwardt
1

Como se indicó en la pregunta, la interfaz no está destinada a ser implementada (o instanciada). Entonces podríamos compararlo con una clase que tenga un constructor privado:

  • La clase no se puede extender sin no super()llamar, pero la interfaz se puede "implementar"
  • La clase no se puede instanciar sin reflexiones, mientras que la interfaz se puede instanciar fácilmente como una clase anónima tan simple como esta: new MyInterface() {};

Esta comparación está a favor de la clase, ya que permite un mayor control. Dicho esto, la clase tampoco pretende ser un titular de material estático, es de esperar que tenga instancias. Bueno, no hay una opción perfecta.

También hay algo extraño sobre la herencia de la "interfaz estática":

  • Una clase "implementadora" hereda campos, pero no hereda métodos estáticos
  • Una interfaz que se extiende no hereda ningún miembro estático.
  • (Si bien una clase que extiende una clase hereda todos los miembros no privados ... y dado que no puede ser extendida por una interfaz, hay menos espacio para la confusión)

Estas podrían ser razones para considerarlo un mal patrón y seguir usando clases estáticas para estos casos. Sin embargo, para alguien adicto al azúcar sintáctico, puede ser tentador incluso con las desventajas.

Vlasec
fuente
... De todos modos, las convenciones de codificación de un equipo pueden cubrir esos problemas. Para mí, si bien no ata la mano de un programador tan estrechamente como lo hace una clase con un constructor privado, todavía conlleva menos riesgos que los setters, y los setters se usan con frecuencia.
Vlasec
0

Como @MarkusPscheidt, esta idea es esencialmente una extensión del antipatrón Constant Interface . Este enfoque se usó inicialmente ampliamente para permitir que un conjunto único de constantes sea heredado por muchas clases dispares. La razón por la que esto terminó siendo considerado un antipatrón fue por los problemas que se encontraron al usar este enfoque en proyectos reales. No veo ninguna razón para pensar que estos problemas solo se relacionen con constantes.

Probablemente más importante, realmente no hay necesidad de hacer esto. Utilice las importaciones estáticas en su lugar y sea explícito sobre lo que está importando y desde dónde.

JimmyJames
fuente
Sí, las importaciones estáticas suenan como una mejor idea que la herencia de constantes y funciona con miembros estáticos de clases e interfaces. Y solo es visible dentro de la clase / interfaz.
Vlasec
@Vlasec Correcto, pero no hay necesidad de hacer esto en una interfaz. La única diferencia entre hacer esto con una clase y una interfaz es que puede heredar de más de una interfaz. La práctica estándar para una clase de utilidad como esta es hacer que el constructor sea privado para evitar la creación de instancias o la extensión. No hay ningún beneficio en hacer esto en una interfaz en una clase. Simplemente abre la puerta al mal uso.
JimmyJames
Puedo ver el valor en constantes en las interfaces. Las interfaces tienden a definir métodos y los métodos a veces aceptan constantes como parámetros. Por ejemplo: interface ColorFilter {Image applyGradient(int colorSpace, int width, byte[] pixels); }. Me puedo imaginar que exista algunas constantes diferentes RGB, RGBA, CMYK, GREYSCALE, INDEXEDetc
Stijn De Witt
@StijndeWitt Esto no sería lo peor, pero puede usar una enumeración o una clase anidada en la interfaz para este propósito. El problema real es usar esto para llevar las estadísticas al alcance de muchas clases a través de la herencia. Ese enfoque es claramente inferior al uso de importaciones estáticas.
JimmyJames
0

Yo diría que si está interesado en cómo usar la nueva función correctamente, eche un vistazo al código en el JDK. Los métodos predeterminados en las interfaces son muy utilizados y fáciles de encontrar, pero los métodos estáticos en las interfaces parecen ser muy poco comunes. Algunos ejemplos incluyen java.time.chrono.Chronology, java.util.Comparator, java.util.Map, y algunas interfaces de bastantes en java.util.functiony java.util.stream.

La mayoría de los ejemplos que analicé involucran usos de esas API que probablemente sean muy comunes: conversión entre diferentes tipos en la API de tiempo, por ejemplo, para comparaciones que probablemente se hagan con frecuencia entre objetos pasados ​​a funciones u objetos contenidos en colecciones. Mi conclusión: si hay una parte de su API que necesita ser flexible, pero puede cubrir, por ejemplo, el 80% de los casos de uso con un puñado de valores predeterminados, considere usar métodos estáticos en sus interfaces. Dada la poca frecuencia con la que esta característica parece ser utilizada en el JDK, sería muy conservador al usarla en mi propio código.

Sus ejemplos no se parecen en nada a lo que veo en el JDK. Para esos casos específicos, es casi seguro que debería crear una enumque implemente la interfaz deseada. Una interfaz describe un conjunto de comportamientos y realmente no tiene por qué mantener una colección de sus implementaciones.

ngreen
fuente
-1

Me pregunto si hay razones para no usarlo.

¿Qué tal, um, compatibilidad con JVMs más antiguas?

¿Considera esto una buena idea en general?

No. Es un cambio retrocompatible que agrega zilch a la expresividad del lenguaje. Dos pulgares hacia abajo.

¿Hay algunas situaciones modelo en las que recomiendas las clases?

Recomiendo usar clases para contener detalles de implementación. En realidad, una interfaz solo significa "este Objeto admite operaciones XYZ" y eso no tiene nada que ver con ningún método estático que se le ocurra incluir en la declaración de la interfaz. Esta característica es como un sillón reclinable con un mini refrigerador incorporado. Claro que tiene algunos usos de nicho, pero no tiene el peso suficiente para merecer ser dominante.

ACTUALIZACIÓN: Por favor, no más votos negativos. Claramente, algunas personas piensan que los métodos estáticos en las interfaces son las rodillas de la abeja, y por la presente estoy capitulando. Prefiero eliminar esta respuesta, pero los comentarios adjuntos son una discusión interesante.

Daniel deprimido
fuente
Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
maple_shaft
Si rechazo su pregunta, lo haría porque nuestras dos primeras respuestas no son respuestas reales. La nueva versión del lenguaje trae nuevas herramientas para facilitar las cosas, ese es el propósito de estas nuevas versiones. La tercera respuesta está un poco perdida entre los escombros de los dos primeros reventados.
Vlasec