Essa estrutura de código é benéfica de alguma forma?

8

Recentemente, fui lançado em um projeto de aplicativo da web Java e deparei-me com várias classes que seguem esse tipo de formato:

public class MyThingy {
   private final int p1;
   private final String p2;
   

   public MyThingy (int p1, String p2, …) {
      this.p1 = p1;
      this.p2 = p2;
      
   }

   public static void doSomething(int p1, String p2, …) throws Throwable     {
      final MyThingy myThingy = new MyThingy(p1, p2, …);
      myThingy.execute();
   }

   private void execute() throws Throwable {
      //do stuff 
   }
}

Parece que isso poderia ser realizado com o código a seguir, que para mim parece muito mais fácil de ler.

public class MyThingy {

   public static void doSomething (int p1, String p2, …) throws Throwable {
      //do stuff 
   }

}

O único benefício possível que vejo ao fazê-lo da primeira maneira é que, se você tivesse que dividir execute () em partes menores, todos eles poderiam compartilhar os parâmetros iniciais sem precisar explicitamente explicá-los. Mas isso talvez beneficie apenas o codificador lento, pois fica difícil para o leitor dizer quais métodos precisam de quais parâmetros e quando os valores podem ser alterados (semelhante a variáveis ​​globais).

Tem algo que estou perdendo? Rosqueamento, desempenho?

Edit: Eu deveria ter mencionado, embora o construtor seja público, não é chamado. O único uso é assim:

MyThingy.doSomething(p1, p2...);

Além de ser problemático para o teste, não vejo motivo para não colocar a lógica de execute () diretamente em doSomething (). Mesmo se nos livrássemos da função estática, o construtor ainda não faz sentido para mim; Eu acho que os parâmetros devem ser passados ​​diretamente para o método que os usará.

Mishelle
fonte
Suponho que o padrão da empresa não diga nada sobre como evitar funções estáticas? Como alternativa, que tipo de teste automatizado está sendo realizado com esse código? A zombaria se torna quase impossível quando as funções estáticas são introduzidas.
KChaloux
3
Apenas como uma observação: throws Throwableé uma prática ruim, deve ser pelo menos throws Exceptionou algo mais específico, se possível. E, como as práticas ruins geralmente se reúnem, eu diria que esse modelo de código é apenas outra prática ruim.
Script #
O método estático é apenas um auxiliar para executar new(...)+ execute()em uma chamada.
Kasey Speakman
Você pode mudar de qualquer maneira: esta é uma solução feia. Seria melhor separar a criação de objetos do consumo.
Thomas Junk
1
Observe também que o autor obviamente pretendia que o objeto construído fosse imutável (os dados são final). O método estático é a única API no objeto, pois todos os membros são privados. Em apenas o código que você compartilhou, eu não vejo nenhum benefício para este sobre tornando executeum método estático tomar p1, p2, ....
Kasey Speakman

Respostas:

2

Penso que a sua intuição sobre esta questão está certa.

  • O que temos aqui é um método simples (neste caso, estático) cuja implementação foi "dividida" em uma classe.
  • A técnica "método como classe" reduz o número de variáveis ​​que você precisa transmitir explicitamente, enquanto na verdade obscurece como os dados fluem usando o que são, em espírito, variáveis ​​"globais".
  • Nada disso está realmente no espírito da OO, já que a instância é descartável e seu estado é efêmero.
  • E, no entanto, esse é um padrão comum, especialmente nos livros introdutórios sobre Java, e pode estar na mente dos designers de Java (que introduziram várias maneiras de declarar classes, aparentemente para ajudar nesse uso).

Então, você deveria usá-lo ?

  • Claro, algumas vezes.

De fato, a questão não é apenas "métodos como classes" puros. Nas classes normais, você pode ser tentado a criar campos que não mantêm o estado entre invocações de métodos públicos. Eles são usados ​​apenas para evitar a passagem de parâmetros entre métodos privados. No passado, tentei evitá-lo a todo custo, mas depois percebi que longas listas de parâmetros também eram ruins.

Eu tento evitar pura "classes de métodos" , pois acho que elas vêm com muita bagagem mental (muito potencial em potencial). (Eu também me apaixono por campos descartáveis ​​nas aulas regulares.) Mas se houver uma tonelada de variáveis ​​para distribuir, a vitória na limpeza do código pode valer a pena.

Eu tento usar as classes quando há um ponto, e na verdade geralmente é fácil imaginar uma. Talvez no futuro você estenda a classe com implementações adicionais. Talvez a instância possa ser pensada como um objeto de função que você deseja inicializar e distribuir. E então, a classe não é mais uma "classe de método" pseudo-OO.

Aleksandr Dubinsky
fonte
1
Eu gosto do que você está sugerindo no último parágrafo aqui. Se o método execute () fosse público, eu poderia criar (e inicializar) o objeto, e depois executar algo. Isso pode fazer sentido e também melhoraria a testabilidade. Nesse caso, é realmente a função estática que é estranha.
Mishelle #
Esta é a resposta que esta pergunta está esperando. 1
cbojar 5/11
6

A indireção extra por meio da chamada de método estático separa as preocupações de como um objeto é criado do código que usa o objeto. O que você tem aqui é algo muito semelhante a um método simples de fábrica (na verdade, ele não retorna o objeto, mas o indireto da criação do objeto é o mesmo).

Isso pode ser útil se você precisar controlar o tipo de objeto criado. Neste exemplo simples, não há decisão a ser tomada, mas seria fácil adicionar essa lógica.

O que o código estático significa para os chamadores é "preciso fazer algo com esse estado, mas não sei como delegar". O método estático pode delegar para a implementação adequada com base no objeto fornecido.

Talvez ao executar um teste de unidade exista um objeto falso sendo usado. Talvez com base nos parâmetros em que um objeto diferente possa ser trocado, implementando um algoritmo diferente para fazer o que for necessário. Ao localizar esse código em um local, a decisão pode ser tomada em um local e não arbitrariamente em muitos.


Dada a forma como sua pergunta está formulada e o fato de que nenhuma decisão de construção está sendo tomada nos métodos estáticos, sinto que isso pode ser apenas um caso de excesso de engenharia. A maioria dos códigos não precisa delegar para uma fábrica. O fato de você continuar encontrando um código como esse que não toma nenhuma decisão, como eu esperaria que um método estático semelhante a uma fábrica fizesse, me diz que alguém leu o livro do GoF e ficou louco.


fonte
1
Onde você vê um método de fábrica? Nenhum dos exemplos do OP retorna nada.
9788 Robert
@RobertHarvey: É efetivamente um método de fábrica embrulhado. Só porque não está exposto ao local da chamada não significa que não é uma fábrica!
Lightness Races in Orbit
1
@RobertHarvey Acho que é uma questão de escolha de palavras. Ele se comporta de maneira semelhante a um método de fábrica, mas na verdade não retorna nada. Por outro lado, nunca afirmei que sim, apenas que delegou, o que ainda é uma declaração precisa.
Eu concordo com @RobertHarvey. Um objeto construído e usado inteiramente dentro de um método é um detalhe de implementação, não um produto de fábrica.
Cbojar
Interessante. Modifiquei a terminologia na minha resposta, mas o argumento que fiz foi idêntico. Estranho como isso aconteceu ...
0

Bem, se o seu segundo exemplo (mostrando o código completo ) se parecer com o seguinte:

public static void doSomething(int p1, String p2, …) throws Throwable     {
      final MyThingy myThingy = new MyThingy(p1, p2, …);
      myThingy.execute();
   }

então você ainda precisará do construtor

public MyThingy (int p1, String p2, …) {
      this.p1 = p1;
      this.p2 = p2;
      
   }

que por sua vez define

   private final int p1;
   private final String p2;

e agora você está de volta onde começou.

Obviamente, você poderia simplesmente eliminar o construtor e escolher getters e setters

private final int p1;
private final String p2;

public void SetP1(int p1)
{
    this.p1 = p1;
}

public int GetP1()
{
    return p1;
}
// repeat for p2

... mas isso complicará sua chamada de método estático, já que agora você precisará de várias linhas de código para chamar os setters antes de chamar seu método estático, onde uma única linha que utilizasse a chamada de construtor seria suficiente.

Obviamente, isso só funciona se suas variáveis ​​de membro não estiverem final. Portanto, você ainda precisará do construtor.

Robert Harvey
fonte
Não faz sentido ter um setter público em uma finalvariável. Só pode ser inicializado uma vez.
Kasey Speakman
1
@KaseySpeakman: Portanto, você ainda precisa de um construtor.
Robert Harvey
Eu não sou inteiramente certo que essa resposta fala com a questão ...
cbojar
Além do mais, não é válido (tem um erro de compilação): ideone.com/0nDRgY --- o valor final deve poder provar que não pode ser definido em nenhum outro lugar. Ter um levantador torna isso impossível e, portanto, um erro de compilação.
@ MichaelT: Eu já disse isso na minha resposta (no último parágrafo).
9788 Robert
0

Normalmente, uma classe possui muitos métodos de instância e, uma vez que o objeto é criado, a implementação de execute pode tirar proveito de todos esses métodos de instância. Se você se recusar a criar um objeto, todos esses métodos de instância serão inúteis.

gnasher729
fonte
Eu acho que o OP está dizendo que não existem outros métodos de instância execute. Assim, perguntando por que não alinhar a coisa toda.
Cbojar
Sim, essa é exatamente a minha pergunta!
Mishelle #