Java - é uma má idéia ter classes totalmente estáticas?


16

Estou trabalhando em um projeto solo maior e agora, e tenho várias classes nas quais não vejo nenhum motivo para criar uma instância do.

Minha classe de dados agora, por exemplo, armazena todos os seus dados estaticamente e todos os seus métodos também são estáticos. Não preciso inicializá-lo porque, quando quero jogar os dados e obter um novo valor, apenas uso Dice.roll().

Eu tenho várias classes semelhantes que possuem apenas uma função principal como essa e estou prestes a começar a trabalhar em uma espécie de classe "controller" que será responsável por todos os eventos (como quando um jogador se move e qual a virada atual) é) e descobri que poderia seguir a mesma ideia para esta aula. Eu nunca planejo criar vários objetos para essas classes específicas, então seria uma má idéia torná-las totalmente estáticas?

Fiquei me perguntando se isso é considerado "má prática" quando se trata de Java. Pelo que vi, a comunidade parece meio que dividida sobre esse tópico? Enfim, eu adoraria alguma discussão sobre isso e os links para recursos também seriam ótimos!


1
Se o seu programa é totalmente processual. Por que você escolheu Java?
LAIV

12
Tente escrever testes de unidade para essas classes e você descobrirá por que as pessoas não gostam de métodos estáticos que acessam o estado estático.
Joeri Sebrechts 11/04/19

@ Laiv Eu ainda sou um pouco iniciante em programação, cerca de um ano em C ++, estou participando de uma aula de Java neste semestre e estou começando a gostar muito mais de Java, principalmente das bibliotecas gráficas.
precisa saber é o seguinte


5
Em relação às classes estáticas. Se os métodos estáticos forem puros (não mantenha um estado, tenha argumentos de entrada e um tipo de retorno). Não há nada com que se preocupar.
Laiv 11/04/19

Respostas:


20

Não há nada errado com as classes estáticas que são realmente estáticas . Ou seja, não há um estado interno para se falar, o que faria com que a saída dos métodos mudasse.

Se Dice.roll()simplesmente retornar um novo número aleatório de 1 a 6, não está mudando de estado. Concedido, você pode estar compartilhando uma Randominstância, mas eu não consideraria que uma mudança de estado, por definição, a saída sempre será boa, aleatória. Também é seguro para threads, portanto não há problemas aqui.

Você verá frequentemente "Helper" final ou outras classes de utilitário que têm um construtor privado e membros estáticos. O construtor privado não contém lógica e serve apenas para impedir que alguém instancia a classe. O modificador final apenas traz essa ideia de que essa não é uma classe da qual você deseja derivar. É apenas uma classe de utilidade. Se feito corretamente, não deve haver singleton ou outros membros da classe que não sejam eles mesmos estáticos e finais.

Contanto que você siga estas diretrizes e não faça singletons, não há absolutamente nada de errado nisso. Você mencionou uma classe de controlador, e isso quase certamente exigirá alterações de estado; portanto, desaconselho o uso apenas de métodos estáticos. Você pode confiar fortemente em uma classe de utilitário estático, mas não pode torná- lo uma classe de utilitário estático.


O que é considerado uma mudança de estado para uma classe? Bem, vamos excluir números aleatórios por um segundo, pois eles não são determinísticos por definição e, portanto, o valor de retorno muda frequentemente.

Uma função pura é aquela que é determinística, ou seja, para uma determinada entrada, você obterá uma e exatamente uma saída. Você deseja que métodos estáticos sejam funções puras. Em Java, existem maneiras de ajustar o comportamento de métodos estáticos para manter o estado, mas quase nunca são boas idéias. Quando você declara um método como estático , o programador típico assume imediatamente que é uma função pura. Desviar-se do comportamento esperado é como você tende a criar bugs no seu programa, geralmente falando e deve ser evitado.

Um singleton é uma classe que contém métodos estáticos tão opostos à "função pura" quanto possível. Um único membro privado estático é mantido internamente na classe que é usada para garantir que exista exatamente uma instância. Esta não é uma prática recomendada e pode causar problemas mais tarde por vários motivos. Para saber do que estamos falando, aqui está um exemplo simples de um singleton:

// DON'T DO THIS!
class Singleton {
  private String name; 
  private static Singleton instance = null;

  private Singleton(String name) {
    this.name = name;
  }

  public static Singleton getInstance() {
    if(instance == null) {
      instance = new Singleton("George");
    }
    return instance;
  }

  public getName() {
    return name;
  }
}

assert Singleton.getInstance().getName() == "George"

7
Se eu quiser testar o que acontece quando um seis duplo é rolado, eu estou preso aqui porque você tem uma classe estática com um gerador estático de números aleatórios. Portanto, não, Dice.roll()não é uma exceção válida à regra "nenhum estado global".
David Arno

1
@HexTeke Resposta atualizada.
1111 Neil

1
@DavidArno True, mas suponho que nesse momento estaremos realmente com problemas se estivermos testando uma única chamada para random.nextInt(6) + 1. ;)
Neil

3
@ Neil, desculpas, não me expliquei muito bem. Não estamos testando a sequência numérica aleatória, estamos afetando essa sequência para ajudar outros testes. Se estamos testando que, por exemplo, RollAndMove()rola novamente quando fornecemos um seis duplo, a maneira mais fácil e robusta de fazer isso é zombar de um Diceou de um gerador de números aleatórios. Portanto, Dicenão deseja ser uma classe estática usando um gerador aleatório estático.
David Arno #

12
Sua atualização atinge o problema na cabeça: métodos estáticos devem ser determinísticos; eles não devem ter efeitos colaterais.
11268 David Arno

9

Para dar um exemplo das limitações de uma staticclasse, e se alguns de seus jogadores quiserem receber um pequeno bônus em suas jogadas de dados? E eles estão dispostos a pagar muito dinheiro! :-)

Sim, você pode adicionar outro parâmetro, então Dice.roll(bonus),

Mais tarde, você precisa de D20s.

Dice.roll(bonus, sides)

Sim, mas alguns jogadores têm o talento "supremamente capaz" para que nunca possam "atrapalhar" (rolar 1).

Dice.roll(bonus, sides, isFumbleAllowed).

Isso está ficando confuso, não é?


isso parece ser ortogonal à pergunta, está ficando confuso se são métodos estáticos ou normais
jk.

4
@ jk, acho que entendi o ponto dele. Não faz sentido ter uma classe estática de dados quando você pensa em mais tipos de dados. Nesse caso, podemos ter objetos de dados diferentes, modelando-os com boas práticas de POO.
Dherik

1
@ TimothyTruckle Não estou contestando isso, tudo o que estou dizendo é que essa resposta não responde à pergunta, porque esse tipo de oscilação de escopo não tem nada a ver com o método ser estático ou não.
Nvoigt 11/0418

2
@nvoigt "Não estou contestando isso" - Bem, sim. A característica mais poderosa de uma linguagem OO é o polimorfismo . E o acesso estático nos impede efetivamente de usá-lo. E você está certo: new Dice().roll(...)deve ser considerado acesso estático e também Dice.roll(...). Só nos beneficiamos se injetarmos essa dependência.
precisa saber é o seguinte

2
@jk a diferença é que a staticconfusão ocorre em todas as chamadas e o conhecimento necessário é necessário em todas as chamadas, para que seja espalhado globalmente em toda a sua aplicação. Em uma estrutura OOP, apenas o construtor / fábrica precisa ser confuso e os detalhes desse dado são encapsulados. Depois disso, use polimorfismo e apenas ligue roll(). Eu posso editar esta resposta para esclarecer.
user949300

3

No caso particular de uma classe Dice, acho que usar métodos de instância em vez de estática tornará o teste significativamente mais fácil.

Se você deseja testar uma unidade de algo que usa uma instância de Dados (por exemplo, uma classe de Jogo), a partir de seus testes, você pode injetar alguma forma de teste dupla de Dados que sempre retorna uma sequência fixa de valores. Seu teste pode verificar se o jogo tem o resultado certo para essas jogadas de dados.

Não sou desenvolvedor de Java, mas acho que seria significativamente mais difícil fazer isso com uma classe Dice totalmente estática. Consulte /programming/4482315/why-does-mockito-not-mock-static-methods


Apenas para os registros: em Java, temos o PowerMock para substituir dependências estáticas manipulando o código de bytes. Mas usá-lo é apenas uma rendição de um mau desenho ...
Timothy Truckle

0

Isso é realmente conhecido como o padrão Monostate , em que cada instância (e até uma "não instância") está compartilhando seu status. Todo membro é um membro da classe (ou seja, nenhum membro da instância). Eles são comumente usados ​​para implementar classes "toolkit" que agrupam um conjunto de métodos e constantes relacionados a uma única responsabilidade ou requisito, mas não precisam de um estado para funcionar (eles são puramente funcionais). De fato, o Java vem com alguns deles empacotados (por exemplo, Math ).

Um pouco fora do tópico: raramente concordo com a nomeação de palavras-chave no VisualBasic, mas, neste caso, acho que sharedé certamente mais claro e semanticamente melhor (é compartilhado entre a própria classe e todas as suas instâncias) do que static(permanece após o ciclo de vida do escopo em que está declarado).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.