The impact of declaring a utility class depends first of all on the question whether there are states involved. If all the methods of the utilitarian class do not need context, there is no problem in replacing them with an installed service. I give a simplified example:
Case 1 - class without status
public class Conv{
private static final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
public static String formatDato(Date d){
return sdf.format(d);
}
public static Date dateDeString(String dato) throws ParseException {
return sdf.parse(dato);
}
}
// uso
String fecha = Conv.formatDato(new Date());
This class has no status, so its use can be re-invoiced to:
public class Conv implements IConv{
public static final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
public Conv(){};
public String formatDato(Date d){
return sdf.format(d);
}
public Date dateDeString(String dato) throws ParseException {
return sdf.parse(dato);
}
}
// uso
String fecha = new Conv().formatDato(new Date());
Assuming that we define the interface IConv
with the methods formatDato
and dateDeString
we can create a mock and exchange the classes.
Case 2 - class with status:
Let's imagine the same kind of utility, but with one more method that makes it have a status:
public class Conv{
private static final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
public static String formatDato(Date d){
return sdf.format(d);
}
public static Date dateDeString(String dato) throws ParseException {
return sdf.parse(dato);
}
// aquí existe la posibilidad que el formato y así el estado cambia
public static void nuevoFormato(String formato)
throws NullpointerException, IllegalArgumentException {
sdf.applyPattern(format);
}
}
// uso
String fecha = Conv.formatDato(new Date());
Because the class has a state, you are not allowed to apply the same pattern as in the other example. In an ideal world you realize that it is very unwise to manage states in classes that provide static methods.
The recommended solution
You already noticed how two "best practices" in the case of "test driven development" cause problems, then remember that there are no best practices because yes, there are only bad and good strategies for a context.
In your case one way to get rid of all the headaches is to follow some simple rules in the design:
- design all the services as an interface.
- we implement the services according to context (test, debug, release)
- we create a
Context
that has methods to obtain the services.
- instead of using static utilities we deliver utility classes such as singleton
The Context
for example could look like this:
public class Context {
public static final int TEST = 0;
public static final int DEBUG = 1;
public static final int RELEASE = 2;
private static Context testContext = null;
private static Context debugContext = null;
private static Context releaseContext = null;
private IDataBaseService dataBaseService=null;
private IFileService fileService=null;
private ITcpService tcpService=null;
private int build=0;
// método fabrica para obtener el contexto para el build
// un singleton a demanda.
public static Context getContext(Integer build){
switch(build){
case TEST:
return (null==testContext) ? testContext = new Context(TEST) : testContext;
case DEBUG:
return (null==debugContext) ? debugContext = new Context(DEBUG) : debugContext;
case RELEASE:
default:
return (null==releaseContext) ? releaseContext = new Context(RELEASE) : releaseContext;
}
}
// los métodos de instancia de contexto también pueden entregar singleton según necesidad
private Context(int build){ this.build = build; }
public IFileService getFileService(){
if (null==fileService){
switch(build){
case TEST:
FileService = new MockFileService();
case DEBUG:
fileService = new DebugFileService();
case RELEASE:
default:
fileService = new FileService();
}
}
return dataBaseService;
}
// ....
}
With this design we can create and obtain the corresponding context in any build variant with for example Context context=Context.getContext(Context.TEST);
and then the services simply with IFileService fs = context.getFileService()
for the appropriate context.