Domesticar as classes 'funções utilitárias'

15

Na nossa base de código Java, continuo vendo o seguinte padrão:

/**
 This is a stateless utility class
 that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
  public int foo(...) {...}
  public void bar(...) {...}
}

/**
 This class does applied foo-related things.
*/
class FooSomething {
  int DoBusinessWithFoo(FooUtil fooUtil, ...) {
    if (fooUtil.foo(...)) fooUtil.bar(...);
  }
}

O que me incomoda é que eu tenho que passar por uma instância de FooUtiltodos os lugares, porque teste.

  • Não consigo tornar FooUtilos métodos estáticos, porque não poderei zombar deles para testar as FooUtilclasses de ambos e de seus clientes.
  • Não consigo criar uma instância FooUtilno local de consumo com a new, novamente, porque não poderei zombar dela para teste.

Suponho que minha melhor aposta é usar a injeção (e eu uso), mas ela adiciona seu próprio conjunto de aborrecimentos. Além disso, a passagem de várias instâncias de utilidade aumenta o tamanho das listas de parâmetros do método.

Existe uma maneira de lidar com isso melhor que eu não estou vendo?

Atualização: como as classes de utilitário são sem estado, eu provavelmente poderia adicionar um INSTANCEmembro estático singleton ou um getInstance()método estático , mantendo a capacidade de atualizar o campo estático subjacente da classe para teste. Também não parece super limpo.

9000
fonte
4
E sua suposição de que todas as dependências precisam ser ridicularizadas para realizar testes de unidade com eficácia não está correta (embora eu ainda esteja procurando pela pergunta que discute isso).
Telastyn
4
Por que esses métodos não são membros da classe cujos dados eles manipulam?
Jules
3
Tenha um conjunto separado de Junit que chame o fns estático se FooUtility. Então você pode usá-lo sem zombar. Se é algo que você precisa zombar, desconfio que não seja apenas um utilitário, mas que faça alguma lógica de IO ou de negócios e, de fato, seja uma referência de membro da classe e inicializada via método construtor, init ou setter.
Tgkprog
2
+1 no comentário de Jules. As classes Util são um cheiro de código. Dependendo da semântica, deve haver uma solução melhor. Talvez os FooUtilmétodos devam ser colocados FooSomething, talvez haja propriedades FooSomethingque devam ser alteradas / estendidas para que os FooUtilmétodos não sejam mais necessários. Por favor, dê-nos um exemplo mais concreto do que FooSomethingnos ajudará a fornecer uma boa resposta para essas perguntas.
valenterry
13
@valenterry: A única razão pela qual as classes util é um cheiro de código é porque o Java não fornece uma boa maneira de ter funções independentes. Existem boas razões para ter funções que não são específicas para classes.
Robert Harvey

Respostas:

16

Antes de mais, deixe-me dizer que existem diferentes abordagens de programação para diferentes problemas. Para o meu trabalho, prefiro um projeto OO forte para organização e manutenção, e minha resposta reflete isso. Uma abordagem funcional teria uma resposta muito diferente.

Costumo descobrir que a maioria das classes de utilidade são criadas porque o objeto em que os métodos devem estar não existe. Isso quase sempre vem de um dos dois casos: alguém está passando coleções ou alguém está passando beans (geralmente chamados POJOs, mas acredito que um monte de setters e getters é melhor descrito como bean).

Quando você tem uma coleção que você está distribuindo, deve haver um código que queira fazer parte dessa coleção, e isso acaba sendo espalhado por todo o sistema e, eventualmente, coletado em classes de utilidade.

Minha sugestão é agrupar suas coleções (ou beans) com outros dados intimamente associados em novas classes e colocar o código "utilitário" em objetos reais.

Você pode estender a coleção e adicionar os métodos lá, mas perde o controle do estado do seu objeto dessa maneira. Sugiro que você o encapsule completamente e exponha apenas os métodos lógicos de negócios necessários (Pense em "Não peça dados a seus objetos, dê comandos aos seus objetos e deixe-os agir com seus dados privados ", um importante inquilino de OO e que, quando você pensa sobre isso, requer a abordagem que estou sugerindo aqui).

Esse "empacotamento" é útil para qualquer objeto de biblioteca de uso geral que contém dados, mas você não pode adicionar código a - Colocar código com os dados que ele manipula é outro conceito importante no design do OO.

Existem muitos estilos de codificação nos quais esse conceito de agrupamento seria uma má idéia - quem quer escrever uma classe inteira ao implementar um script ou teste único? Mas acho que o encapsulamento de quaisquer tipos / coleções básicos que você não pode adicionar código a si mesmo é obrigatório para o OO.

Bill K
fonte
Esta resposta é tão incrível. Eu tive esse problema (obviamente), li esta resposta com alguma duvidosa, voltei e olhei para o meu código e perguntei "que objeto está faltando que deveria ter esses métodos". Eis aqui a solução elegante encontrada e a lacuna de modelo de objeto preenchida.
GreenAsJade 30/04/19
@Duplicator Em 40 anos de programação, raramente (nunca?) Vi o caso em que fui bloqueado por muitos níveis de indireção (além da briga ocasional com meu IDE para pular para uma implementação em vez de uma interface), mas eu ' Muitas vezes (com muita frequência) vi o caso em que as pessoas escreviam códigos horríveis em vez de criar uma classe claramente necessária. Embora eu acredite que muita indireção possa ter mordido algumas pessoas, eu erro ao lado dos princípios adequados de OO, como cada classe fazendo uma coisa bem, contenção adequada, etc. #
K Bill Bill
@ BillK Geralmente, uma boa ideia. Mas sem uma saída de emergência, o encapsulamento pode realmente atrapalhar. E existe uma certa beleza em liberar algoritmos que você pode corresponder a qualquer tipo que você quiser, apesar de admitir que em linguagens estritamente OO qualquer coisa, exceto OO, é bastante incômoda.
Deduplicator
@Duplicador Ao escrever Funções 'utilitárias' que devem ser escritas fora dos requisitos comerciais específicos, você está correto. As classes de utilitário cheias de funções (como coleções) são simplesmente estranhas no OO porque não estão associadas aos dados. estes são os "Escape Hatch" que as pessoas que não se sentem confortáveis ​​com o OO (também os fornecedores de ferramentas devem usá-los para uma funcionalidade muito genérica). Minha sugestão acima foi agrupar esses hacks em classes quando você estiver trabalhando na lógica de negócios para poder associar novamente seu código aos seus dados.
Bill K
1

Você pode usar um padrão de provedor e injetar FooSomethingum FooUtilProviderobjeto no seu (pode estar em um construtor; apenas uma vez).

FooUtilProviderteria apenas um método: FooUtils get();. A classe usaria qualquer FooUtilsinstância que ela fornecer.

Agora, isso está a um passo de usar uma estrutura de Injeção de Dependência, quando você só precisa conectar o porta-moedas DI duas vezes: para código de produção e para o seu conjunto de testes. Você apenas liga a FooUtilsinterface a um RealFooUtilsou a MockedFooUtils, e o resto acontece automaticamente. Todo objeto que depende FooUtilsProviderobtém a versão correta do FooUtils.

Konrad Morawski
fonte