Interfaces com campos estáticos em java para compartilhamento de ‘constantes’

Eu estou olhando para alguns projetos Java de código aberto para entrar em Java e percebo que muitos deles têm algum tipo de interface ‘constantes’.

Por exemplo, processing.org tem uma interface chamada PConstants.java , e a maioria das outras classs principais implementam essa interface. A interface está repleta de membros estáticos. Existe uma razão para essa abordagem, ou isso é considerado uma prática ruim? Por que não usar enums onde isso faz sentido ou uma class estática?

Eu acho estranho usar uma interface para permitir algum tipo de pseudo ‘variables ​​globais’.

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 } 

Geralmente é considerado uma prática ruim. O problema é que as constantes fazem parte da “interface” pública (por falta de uma palavra melhor) da class de implementação. Isso significa que a class de implementação está publicando todos esses valores em classs externas, mesmo quando elas são necessárias apenas internamente. As constantes proliferam ao longo do código. Um exemplo é a interface SwingConstants no Swing, que é implementada por dezenas de classs que “reexportam” todas as constantes (mesmo as que não usam) como próprias.

Mas não acredite apenas na minha palavra, Josh Bloch também diz que é ruim:

O padrão de interface constante é um mau uso de interfaces. Que uma class usa algumas constantes internamente é um detalhe de implementação. A implementação de uma interface constante faz com que esse detalhe de implementação vaze para a API exportada da class. Não tem importância para os usuários de uma class que a class implemente uma interface constante. Na verdade, isso pode até confundi-los. Pior, representa um compromisso: se em uma versão futura a class for modificada para não precisar mais usar as constantes, ela ainda deverá implementar a interface para garantir a compatibilidade binária. Se uma class nonfinal implementa uma interface constante, todas as suas subclasss terão seus namespaces poluídos pelas constantes na interface.

Um enum pode ser uma abordagem melhor. Ou você pode simplesmente colocar as constantes como campos estáticos públicos em uma class que não pode ser instanciada. Isso permite que outra class os acesse sem poluir sua própria API.

Em vez de implementar uma “interface de constantes”, no Java 1.5+, você pode usar importações estáticas para importar as constantes / methods estáticos de outra class / interface:

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

Isso evita a fealdade de fazer com que suas classs implementem interfaces sem funcionalidade.

Quanto à prática de ter uma class apenas para armazenar constantes, acho que às vezes é necessário. Há certas constantes que simplesmente não têm um lugar natural em uma class, então é melhor tê-las em um local “neutro”.

Mas, em vez de usar uma interface, use uma class final com um construtor privado. (Tornando impossível instanciar ou subclassificar a class, enviando uma mensagem forte de que ela não contém funcionalidade / dados não estáticos.)

Por exemplo:

 /** 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; } 

Eu não pretendo o direito de estar certo, mas vamos ver este pequeno exemplo:

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

A ToyotaCar não sabe nada sobre a FordCar e a FordCar não sabe sobre a ToyotaCar. princípio CarConstants deve ser alterado, mas …

As constantes não devem ser mudadas, porque a roda é redonda e a egina é mecânica, mas … No futuro, os engenheiros de pesquisa da Toyota inventaram o motor eletrônico e as rodas planas! Vamos ver nossa nova interface

 public interface InnovativeCarConstants { static final String ENGINE = "electronic"; static final String WHEEL = "flat"; // ... } 

e agora podemos mudar nossa abstração:

 public interface ToyotaCar extends CarConstants 

para

 public interface ToyotaCar extends InnovativeCarConstants 

E agora, se precisarmos mudar o valor central se o MOTOR ou o RODO pudermos mudar a interface ToyotaCar no nível de abstração, não toque nas implementações

Não é seguro, eu sei, mas eu ainda quero saber o que você acha sobre isso

Há muito ódio por esse padrão em Java. No entanto, uma interface de constantes estáticas às vezes tem valor. Você precisa basicamente atender às seguintes condições:

  1. Os conceitos fazem parte da interface pública de várias classs.

  2. Seus valores podem mudar em versões futuras.

  3. É essencial que todas as implementações usem os mesmos valores.

Por exemplo, suponha que você esteja escrevendo uma extensão para uma linguagem de consulta hipotética. Nesta extensão, você expandirá a syntax da linguagem com algumas novas operações, que são suportadas por um índice. Por exemplo, você terá uma tree R com suporte a consultas geoespaciais.

Então você escreve uma interface pública com a constante estática:

 public interface SyntaxExtensions { // query type String NEAR_TO_QUERY = "nearTo" // params for query String POINT = "coordinate" String DISTANCE_KM = "distanceInKm" } 

Agora, mais tarde, um novo desenvolvedor acha que precisa criar um índice melhor, então ele vem e cria uma implementação do R *. Ao implementar essa interface em sua nova tree, ele garante que os diferentes índices terão syntax idêntica na linguagem de consulta. Além disso, se mais tarde você decidir que “nearTo” era um nome confuso, você poderia alterá-lo para “withinDistanceInKm” e saber que a nova syntax seria respeitada por todas as suas implementações de índice.

PS: A inspiração para este exemplo é extraída do código espacial Neo4j.

Dada a vantagem da retrospectiva, podemos ver que o Java está quebrado de várias maneiras. Uma grande falha do Java é a restrição de interfaces a methods abstratos e campos finais estáticos. As linguagens OO mais novas e mais sofisticadas, como a Scala, incluem interfaces por características que podem (e geralmente incluem) methods concretos, que podem ter aridade zero (constantes!). Para uma exposição sobre características como unidades de comportamento composto, consulte http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf . Para uma breve descrição de como as características em Scala se comparam às interfaces em Java, consulte http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-5 . No contexto do ensino de design OO, regras simplistas como asseverar que interfaces nunca devem include campos estáticos são bobas. Muitas características naturalmente incluem constantes e estas constantes são apropriadamente parte da “interface” pública suportada pela característica. Ao escrever o código Java, não há uma maneira limpa e elegante de representar características, mas o uso de campos finais estáticos nas interfaces geralmente faz parte de uma boa solução alternativa.

De acordo com a especificação da JVM, os campos e methods em uma interface podem ter apenas Public, Static, Final e Abstract. Ref de dentro da Java VM

Por padrão, todos os methods na interface são abstratos, mesmo que você não os tenha mencionado explicitamente.

Interfaces são destinadas a dar apenas especificação. Não pode conter implementações. Portanto, para evitar a implementação de classs para alterar a especificação, ela é finalizada. Como a interface não pode ser instanciada, eles ficam estáticos para acessar o campo usando o nome da interface.

Eu não tenho reputação suficiente para dar um comentário ao Pleerock, então eu tenho que criar uma resposta. Eu sinto muito por isso, mas ele colocou um bom esforço nisso e eu gostaria de responder a ele.

Pleerock, você criou o exemplo perfeito para mostrar por que essas constantes devem ser independentes das interfaces e independentes da inheritance. Para o cliente da aplicação, não é importante que exista uma diferença técnica entre a implementação de carros. Eles são os mesmos para o cliente, apenas carros. Então, o cliente quer olhar para eles a partir dessa perspectiva, que é uma interface como I_Somecar. Ao longo da aplicação, o cliente usará apenas uma perspectiva e não diferentes para cada marca de carro diferente.

Se um cliente quiser comparar carros antes de comprar, ele pode ter um método como este:

 public List compareCars(List pCars); 

Uma interface é um contrato sobre comportamento e mostra objects diferentes de uma perspectiva. A maneira como você o projeta, cada marca de carro terá sua própria linha de inheritance. Embora, na realidade, seja bastante correto, porque os carros podem ser tão diferentes que podem ser como comparar tipos de objects completamente diferentes, no final há uma escolha entre carros diferentes. E essa é a perspectiva da interface que todas as marcas precisam compartilhar. A escolha de constantes não deve tornar isso impossível. Por favor, considere a resposta de Zarkonnen.

Isso veio de um tempo antes do Java 1.5 existir e nos trazer enums. Antes disso, não havia uma boa maneira de definir um conjunto de constantes ou valores restritos.

Isso ainda é usado, na maioria das vezes, seja por compatibilidade com versões anteriores ou devido à quantidade de refatoração necessária para se livrar, em muitos projetos.