Interfaces com campos estáticos em java para compartilhar 'constantes'

116

Estou olhando alguns projetos Java de software livre para entrar em Java e observei que muitos deles têm algum tipo de interface de 'constantes'.

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

Acho estranho usar uma interface para permitir algum tipo de pseudo 'variáveis ​​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

}
kitsune
fonte
15
Nota: static finalnão é necessário, é redundante para uma interface.
ThomasW
Observe também que platformNamespode ser public, statice final, mas definitivamente não é uma constante. A única matriz constante é uma com comprimento zero.
Vlasec
@ThomasW Eu sei que isso tem alguns anos, mas precisava apontar um erro em seu comentário. static finalnão é necessariamente redundante. Um campo de classe ou interface com apenas a finalpalavra - chave criaria instâncias separadas desse campo à medida que você cria objetos da classe ou interface. Usar static finalfaria com que cada objeto compartilhasse um local de memória para aquele campo. Em outras palavras, se uma classe MyClass tivesse um campo final String str = "Hello";, para N instâncias de MyClass, haveria N instâncias do campo str na memória. Adicionar a staticpalavra - chave resultaria em apenas 1 instância.
Sintrias

Respostas:

160

Geralmente é considerada uma prática ruim. O problema é que as constantes são parte da "interface" pública (na falta de uma palavra melhor) da classe de implementação. Isso significa que a classe de implementação está publicando todos esses valores em classes externas, mesmo quando eles são necessários apenas internamente. As constantes proliferam em todo o código. Um exemplo é a interface SwingConstants no Swing, que é implementada por dezenas de classes que "reexportam" todas as suas constantes (mesmo as que não usam) como suas próprias.

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

O padrão de interface constante é um uso inadequado de interfaces. O fato de uma classe usar algumas constantes internamente é um detalhe de implementação. Implementar uma interface constante faz com que esse detalhe de implementação vaze para a API exportada da classe. É irrelevante para os usuários de uma classe que a classe implemente uma interface constante. Na verdade, pode até mesmo confundi-los. Pior, representa um compromisso: se em uma versão futura a classe for modificada de forma que não precise mais usar as constantes, ela ainda deverá implementar a interface para garantir a compatibilidade binária. Se uma classe não final implementa uma interface constante, todas as suas subclasses 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 classe que não pode ser instanciada. Isso permite que outra classe os acesse sem poluir sua própria API.

Dan Dyer
fonte
8
Enums são uma pista falsa aqui - ou pelo menos uma questão separada. É claro que enums devem ser usados, mas também devem ser ocultados se não forem necessários aos implementadores.
DJClayworth
12
BTW: você pode usar um enum sem instâncias como uma classe que não pode ser instanciada. ;)
Peter Lawrey
5
Mas por que implementar essas interfaces em primeiro lugar? Por que não usá-los apenas como repositórios de constantes? Se preciso de algum tipo de constante compartilhada globalmente, não vejo maneiras "mais limpas" de fazer isso.
shadox de
2
@DanDyer sim, mas uma interface faz algumas declarações implícitas. Como public static final é apenas um padrão. Por que se preocupar com uma aula? Enum - bem, depende. Um enum deve definir uma coleção de valores possíveis para uma entidade e não uma coleção de valores para diferentes entidades.
shadox
4
Pessoalmente, acho que Josh está batendo na bola errado. Se você não quiser que suas constantes vazem - independentemente do tipo de objeto que você as colocou - você precisa ter certeza de que NÃO fazem parte do código exportado. Interface ou classe, ambas podem ser exportadas. Portanto, a pergunta certa a fazer não é: em que tipo de objeto eu os coloco, mas como organizo esse objeto. E se as constantes são usadas no código exportado, você ainda precisa ter certeza de que elas estarão disponíveis depois de exportadas. Portanto, a alegação de "má prática" é, em minha humilde opinião, inválida.
Lawrence
99

Em vez de implementar uma "interface de constantes", em Java 1.5+, você pode usar importações estáticas para importar as constantes / métodos estáticos de outra classe / interface:

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

Isso evita a feiura de fazer suas classes implementarem interfaces sem funcionalidade.

Quanto à prática de ter uma aula apenas para armazenar constantes, acho que às vezes é necessário. Existem certas constantes que simplesmente não têm um lugar natural em uma classe, então é melhor colocá-las em um lugar "neutro".

Mas em vez de usar uma interface, use uma classe final com um construtor privado. (Tornando impossível instanciar ou criar uma subclasse da classe, 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;
}
Zarkonnen
fonte
Então, você está explicando que, por causa da importação estática, devemos usar classes em vez de interfaces para refazer o mesmo erro de antes ?! Isso é bobagem!
gizmo
11
Não, não é isso que estou dizendo. Estou dizendo duas coisas independentes. 1: use importações estáticas em vez de abusar da herança. 2: Se você deve ter um repositório de constantes, torne-o uma classe final em vez de uma interface.
Zarkonnen
As "interfaces constantes" não foram projetadas para fazer parte de qualquer herança, nunca. Portanto, a importação estática é apenas para o açúcar sintático, e herdar dessa interface é um erro horrível. Eu sei que a Sun fez isso, mas eles também cometeram vários outros erros básicos, isso não é uma desculpa para imitá-los.
gizmo
3
Um dos problemas com o código postado para a pergunta é que a implementação da interface é usada apenas para obter acesso mais fácil às constantes. Quando vejo algo implementando FooInterface, espero que isso afete sua funcionalidade, e o acima viola isso. As importações estáticas corrigem esse problema.
Zarkonnen
2
gizmo - Não sou fã de importações estáticas, mas o que ele está fazendo é evitar o uso do nome da classe, ou seja, ConstClass.SOME_CONST. Fazer uma importação estática não adiciona esses membros à classe que você Z. não diz para herdar da interface, ele diz o contrário, na verdade.
mtruesdell
8

Não pretendo 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);
      }
}

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

As constantes não devem ser alteradas, porque a roda é redonda e a roda é 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 do MOTOR ou RODA, podemos mudar a Interface do ToyotaCar no nível de abstração, sem tocar nas implementações

NÃO É SEGURO, eu sei, mas ainda quero saber o que você pensa sobre isso

pleerock
fonte
Quero saber sua ideia agora em 2019. Para mim, os campos de interface devem ser compartilhados entre alguns objetos.
Chovendo em
Escrevi uma resposta relacionada à sua ideia: stackoverflow.com/a/55877115/5290519
Chovendo em
este é um bom exemplo de como as regras do PMD são muito limitadas. Tentar obter um código melhor por meio da burocracia continua sendo uma tentativa inútil.
bebbo
6

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

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

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

  3. É fundamental 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ê vai expandir a sintaxe da linguagem com algumas novas operações, que são suportadas por um índice. Por exemplo, você terá um R-Tree 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";
}

Mais tarde, um novo desenvolvedor pensa que precisa construir um índice melhor, então ele vem e constrói uma implementação R *. Ao implementar esta interface em sua nova árvore, ele garante que os diferentes índices terão sintaxe idêntica na linguagem de consulta. Além disso, se mais tarde você decidiu que "nearTo" era um nome confuso, poderia alterá-lo para "withinDistanceInKm" e saber que a nova sintaxe seria respeitada por todas as implementações de índice.

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

phil_20686
fonte
5

Dada a vantagem da retrospectiva, podemos ver que o Java está corrompido de várias maneiras. Uma das principais falhas do Java é a restrição das interfaces a métodos abstratos e campos finais estáticos. Linguagens OO mais novas e sofisticadas, como Scala, classificam as interfaces por traços que podem (e normalmente incluem) métodos concretos, que podem ter aridade zero (constantes!). Para uma exposição sobre traços como unidades de comportamento combinável, consulte http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf . Para uma breve descrição de como as características em Scala se comparam a 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 afirmar que as interfaces nunca devem incluir campos estáticos são bobas. Muitas características incluem naturalmente constantes e essas constantes são apropriadamente parte da "interface" pública suportada pela característica. Ao escrever 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 costuma ser parte de uma boa solução alternativa.

Corky Cartwright
fonte
12
Terrivelmente pretensioso e hoje em dia desatualizado.
Esko
1
Visão maravilhosa (+1), embora talvez um pouco crítica demais contra Java.
peterh - Reintegração de Monica em
0

De acordo com a especificação JVM, os campos e métodos em uma Interface podem ter apenas Public, Static, Final e Abstract. Ref de Inside Java VM

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

As interfaces destinam-se a fornecer apenas especificações. Ele não pode conter nenhuma implementação. Portanto, para evitar a implementação de classes para alterar a especificação, ela é finalizada. Como a Interface não pode ser instanciada, eles se tornam estáticos para acessar o campo usando o nome da interface.

om39a
fonte
0

Não tenho reputação suficiente para fazer um comentário a Pleerock, portanto, devo criar uma resposta. Lamento por isso, mas ele fez um grande esforço e gostaria de lhe responder.

Pleerock, você criou o exemplo perfeito para mostrar por que essas constantes devem ser independentes de interfaces e independentes de herança. Para o cliente do aplicativo não é importante que haja uma diferença técnica entre aquelas implementações de carros. São iguais para o cliente, apenas carros. Então, o cliente quer olhar para eles dessa perspectiva, que é uma interface como I_Somecar. Ao longo do aplicativo, 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<Decision> compareCars(List<I_Somecar> pCars);

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

Loek Bergman
fonte
-1

Isso veio de um tempo antes de o Java 1.5 existir e trazer enums para nós. 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 para compatibilidade com versões anteriores ou devido à quantidade de refatoração necessária para se livrar, em muitos projetos.

gizmo
fonte
2
Antes do Java 5, você podia usar o padrão enum de tipo seguro (consulte java.sun.com/developer/Books/shiftintojava/page1.html ).
Dan Dyer