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 FooUtil
todos os lugares, porque teste.
- Não consigo tornar
FooUtil
os métodos estáticos, porque não poderei zombar deles para testar asFooUtil
classes de ambos e de seus clientes. - Não consigo criar uma instância
FooUtil
no local de consumo com anew
, 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 INSTANCE
membro 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.
FooUtil
métodos devam ser colocadosFooSomething
, talvez haja propriedadesFooSomething
que devam ser alteradas / estendidas para que osFooUtil
métodos não sejam mais necessários. Por favor, dê-nos um exemplo mais concreto do queFooSomething
nos ajudará a fornecer uma boa resposta para essas perguntas.Respostas:
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.
fonte
Você pode usar um padrão de provedor e injetar
FooSomething
umFooUtilProvider
objeto no seu (pode estar em um construtor; apenas uma vez).FooUtilProvider
teria apenas um método:FooUtils get();
. A classe usaria qualquerFooUtils
instâ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
FooUtils
interface a umRealFooUtils
ou aMockedFooUtils
, e o resto acontece automaticamente. Todo objeto que dependeFooUtilsProvider
obtém a versão correta doFooUtils
.fonte