2008-11-26 10 views
100

Estoy viendo algunos proyectos de código abierto de Java para entrar en Java y me doy cuenta de que muchos de ellos tienen algún tipo de interfaz de 'constantes'.Interfaces con campos estáticos en Java para compartir 'constantes'

Por ejemplo, processing.org tiene una interfaz llamada PConstants.java, y la mayoría de las otras clases principales implementan esta interfaz. La interfaz está plagada de miembros estáticos. ¿Hay alguna razón para este enfoque o se considera una mala práctica? ¿Por qué no utilizar enumeraciones donde tiene sentido, o una clase estática?

Me resulta extraño utilizar una interfaz para permitir algún tipo de pseudo 'variables globales'.

public interface PConstants { 

    // LOTS OF static fields... 

    static public final int SHINE = 31; 

    // emissive (by default kept black) 
    static public final int ER = 32; 
    static public final int EG = 33; 
    static public final int EB = 34; 

    // has this vertex been lit yet 
    static public final int BEEN_LIT = 35; 

    static public final int VERTEX_FIELD_COUNT = 36; 


    // renderers known to processing.core 

    static final String P2D = "processing.core.PGraphics2D"; 
    static final String P3D = "processing.core.PGraphics3D"; 
    static final String JAVA2D = "processing.core.PGraphicsJava2D"; 
    static final String OPENGL = "processing.opengl.PGraphicsOpenGL"; 
    static final String PDF = "processing.pdf.PGraphicsPDF"; 
    static final String DXF = "processing.dxf.RawDXF"; 


    // platform IDs for PApplet.platform 

    static final int OTHER = 0; 
    static final int WINDOWS = 1; 
    static final int MACOSX = 2; 
    static final int LINUX = 3; 

    static final String[] platformNames = { 
    "other", "windows", "macosx", "linux" 
    }; 

    // and on and on 

} 
+10

Nota: 'static final' no es necesario, es redundante para una interfaz. – ThomasW

+0

También tenga en cuenta que 'platformNames' puede ser' public', 'static' y' final', pero definitivamente no es una constante. La única matriz constante es una con longitud cero. – Vlasec

Respuesta

140

por lo general es considerada una mala práctica. El problema es que las constantes son parte de la "interfaz" pública (a falta de una mejor palabra) de la clase implementadora. Esto significa que la clase de implementación está publicando todos estos valores en clases externas, incluso cuando solo se requieren internamente. Las constantes proliferan en todo el código. Un ejemplo es la interfaz SwingConstants en Swing, que se implementa mediante docenas de clases que todas "reexportan" todas las de sus constantes (incluso las que no usan) como propias.

Pero no tome mi palabra para ella, Josh Bloch also says es malo:

El patrón constante interfaz es un mal uso de las interfaces. Que una clase usa algunas constantes internamente es un detalle de implementación. La implementación de una interfaz constante hace que este detalle de implementación se filtre en la API exportada de la clase. No tiene ninguna consecuencia para los usuarios de una clase que la clase implemente una interfaz constante. De hecho, incluso puede confundirlos. Peor aún, representa un compromiso: si en una versión futura la clase se modifica para que ya no necesite usar las constantes, todavía debe implementar la interfaz para garantizar la compatibilidad binaria.Si una clase no final implementa una interfaz constante, todas sus subclases tendrán sus espacios de nombres contaminados por las constantes en la interfaz.

Un enum puede ser un mejor enfoque. O simplemente podría poner las constantes como campos públicos estáticos en una clase que no se puede instanciar. Esto permite que otra clase acceda a ellos sin contaminar su propia API.

+1

Vale la pena señalar que en algunos casos no tiene otra opción, por ejemplo JavaME. – izb

+7

Los blogs son un gran problema aquí, o al menos una pregunta aparte. Por supuesto, se deben usar los enum, pero también deben ocultarse si los implementadores no los necesitan. – DJClayworth

+10

BTW: puede usar una enumeración sin instancias como una clase que no se puede instanciar. ;) –

-1

Esto vino de un tiempo antes de que existiera Java 1.5 y nos trajo enumeraciones. Antes de eso, no había una buena manera de definir un conjunto de constantes o valores restringidos.

Esto se sigue utilizando, la mayoría de las veces por compatibilidad con versiones anteriores o debido a la cantidad de refactorización necesaria para deshacerse, en una gran cantidad de proyectos.

+2

Antes de Java 5, puede usar el patrón de enum seguro de tipo (vea http://java.sun.com/developer/Books/shiftintojava/page1.html). –

91

En lugar de implementar una "interfaz constantes", en Java 1.5+, puede utilizar las importaciones estáticas para importar las constantes/métodos estáticos de otra clase/interfaz:

import static com.kittens.kittenpolisher.KittenConstants.*; 

Esto evita la fealdad de hacer su las clases implementan interfaces que no tienen ninguna funcionalidad.

En cuanto a la práctica de tener una clase solo para almacenar constantes, creo que a veces es necesario. Hay ciertas constantes que simplemente no tienen un lugar natural en una clase, por lo que es mejor tenerlas en un lugar "neutral".

Pero en lugar de usar una interfaz, use una clase final con un constructor privado. (Lo que es imposible crear una instancia de la clase o subclase, el envío de un mensaje de que no contiene funcionalidad/datos no estáticos.)

Ej:

/** Set of constants needed for Kitten Polisher. */ 
public final class KittenConstants 
{ 
    private KittenConstants() {} 

    public static final String KITTEN_SOUND = "meow"; 
    public static final double KITTEN_CUTENESS_FACTOR = 1; 
} 
+0

Entonces, ¿está explicando que, debido a la importación estática, deberíamos usar clases en lugar de intefaces para volver a hacer el mismo error que antes? ¡Eso es tonto! – gizmo

+9

No, eso no es lo que estoy diciendo en absoluto. Estoy diciendo dos cosas independientes. 1: usa importaciones estáticas en lugar de abusar de la herencia.2: si debe tener un repositorio de constantes, conviértalo en una clase final en lugar de una interfaz. – Zarkonnen

+0

"Interfaces constantes" donde no están diseñadas para ser parte de una herencia, nunca. Entonces la importación estática es solo para azúcar sintáctico, y heredar de tal interfaz es un error horrible. Sé que Sun hizo esto, pero también cometió muchos otros errores básicos, eso no es una excusa para imitarlos. – gizmo

5

Dada la ventaja de la retrospectiva, podemos ver que Java se rompe de muchas maneras. Una falla importante de Java es la restricción de interfaces a métodos abstractos y campos finales estáticos. Los lenguajes OO más nuevos y más sofisticados, como Scala, incluyen interfaces por rasgos que pueden (y típicamente lo hacen) incluir métodos concretos, que pueden tener una razón cero (¡constantes!). Para una exposición sobre rasgos como unidades de comportamiento composable, vea http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf. Para una breve descripción de cómo los rasgos en Scala se comparan con las interfaces en Java, vea http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-5. En el contexto de la enseñanza del diseño OO, las reglas simplistas como afirmar que las interfaces nunca deberían incluir campos estáticos son tontas. Muchos rasgos, naturalmente, incluyen constantes y estas constantes son apropiadamente parte de la "interfaz" pública soportada por el rasgo. Al escribir código Java, no existe una manera limpia y elegante de representar rasgos, pero el uso de campos finales estáticos dentro de las interfaces a menudo es parte de una buena solución.

+10

Terriblemente pretencioso y hoy en día anticuado. – Esko

+1

Visión maravillosa (+1), aunque tal vez un poco demasiado crítica contra Java. – peterh

7

No pretendo el derecho a estar en lo cierto, pero vamos a ver este pequeño ejemplo:

public interface CarConstants { 

     static final String ENGINE = "mechanical"; 
     static final String WHEEL = "round"; 
     // ... 

} 

public interface ToyotaCar extends CarConstants //, ICar, ... { 
     void produce(); 
} 

public interface FordCar extends CarConstants //, ICar, ... { 
     void produce(); 
} 

// and this is implementation #1 
public class CamryCar implements ToyotaCar { 

     public void produce() { 
      System.out.println("the engine is " + ENGINE); 
      System.out.println("the wheel is " + WHEEL); 
     } 
} 

// and this is implementation #2 
public class MustangCar implements FordCar { 

     public void produce() { 
      System.out.println("the engine is " + ENGINE); 
      System.out.println("the wheel is " + WHEEL); 
     } 
} 

ToyotaCar no sabe nada acerca de FordCar y FordCar no sabe acerca ToyotaCar. principio CarConstants debe ser cambiado, pero ...

Las constantes no deben cambiarse, porque la rueda es redonda y egine es mecánica, pero ... ¡En el futuro, los ingenieros de investigación de Toyota inventaron el motor electrónico y las ruedas planas! Vamos a ver nuestra nueva interfaz

public interface InnovativeCarConstants { 

      static final String ENGINE = "electronic"; 
      static final String WHEEL = "flat"; 
      // ... 
} 

y ahora podemos cambiar nuestra abstracción:

public interface ToyotaCar extends CarConstants 

a

public interface ToyotaCar extends InnovativeCarConstants 

Y ahora si alguna vez necesitamos cambiar el valor de la base si el motor o WHEEL podemos cambiar la interfaz ToyotaCar en el nivel de abstracción, no tocar las implementaciones

It s NO ES SEGURO, lo sé, pero aún quiero saber que piensas sobre esto

+1

Sí, la cosa parece interesante, quiero saber sobre esto también ... – aeracode

0

Según la especificación de JVM, los campos y métodos en una interfaz solo pueden tener Público, Estático, Final y Abstracto. Ref from Inside Java VM

Por defecto, todos los métodos en la interfaz son abstractos, incluso si no lo menciona explícitamente.

Las interfaces están destinadas a dar solo las especificaciones. No puede contener ninguna implementación. Entonces, para evitar implementar clases para cambiar la especificación, se hace final. Como no se puede crear una instancia de la interfaz, se convierten en estáticos para acceder al campo utilizando el nombre de la interfaz.

0

No tengo suficiente reputación para comentar sobre Pleerock, por lo tanto, tengo que crear una respuesta. Lo siento por eso, pero hizo un buen esfuerzo y me gustaría responderle.

Pleerock, creó el ejemplo perfecto para mostrar por qué esas constantes deberían ser independientes de las interfaces e independientes de la herencia. Para el cliente de la aplicación no es importante que exista una diferencia técnica entre esa implementación de autos. Son lo mismo para el cliente, solo autos. Entonces, el cliente quiere verlos desde esa perspectiva, que es una interfaz como I_Somecar. A lo largo de la aplicación, el cliente utilizará solo una perspectiva y no diferentes para cada marca de automóvil diferente.

Si un cliente quiere comparar coches antes de comprar que puede tener un método como este:

public List<Decision> compareCars(List<I_Somecar> pCars); 

Una interfaz es un contrato sobre el comportamiento y muestra diferentes objetos de una perspectiva. De la forma en que lo diseñe, cada marca de automóviles tendrá su propia línea de herencia. Aunque en realidad es bastante correcto, debido a que los automóviles pueden ser tan diferentes que puede ser como la comparación de un tipo de objeto completamente diferente, al final hay opciones entre los diferentes automóviles. Y esa es la perspectiva de la interfaz que todas las marcas deben compartir. La elección de constantes no debería hacer esto imposible. Por favor, considera la respuesta de Zarkonnen.

5

Hay mucho odio por este patrón en Java. Sin embargo, una interfaz de constantes estáticas a veces tiene valor. Es necesario para cumplir básicamente las siguientes condiciones:

  1. Los conceptos son parte de la interfaz pública de varias clases .

  2. Sus valores pueden cambiar en futuras versiones.

  3. Es fundamental que todas las implementaciones utilicen los mismos valores.

Por ejemplo, supongamos que está escribiendo una extensión a un lenguaje hipotético de consulta. En esta extensión, va a expandir la sintaxis del lenguaje con algunas operaciones nuevas, que son compatibles con un índice. P.ej. Va a tener un R-Tree que soporte consultas geoespaciales.

Así se escribe una interfaz pública con la constante estática:

public interface SyntaxExtensions { 
    // query type 
    String NEAR_TO_QUERY = "nearTo" 

    // params for query 
    String POINT = "coordinate" 
    String DISTANCE_KM = "distanceInKm" 
} 

Ahora después, un nuevo desarrollador cree que tiene que construir un mejor índice, por lo que llega y construye una implementación R *. Al implementar esta interfaz en su nuevo árbol, garantiza que los diferentes índices tendrán una sintaxis idéntica en el lenguaje de consulta. Además, si posteriormente decidió que "nearTo" era un nombre confuso, podría cambiarlo por "withinDistanceInKm" y saber que todas las implementaciones de índice respetarían la nueva sintaxis.

PD: La inspiración para este ejemplo proviene del código espacial de Neo4j.

Cuestiones relacionadas