I'm currently working on a codebase where there are a few classes of variable, like database paths, which are simply represented as Strings. Most of the operations on these (non-)types are defined in a utility class.
I have created a new class to represent a database, with operations defined as instance methods, in traditional OOP style. However it is quite laborious to go through the large codebase and refactor it to use the new types. Does anyone have any advice as to how to do this quickly and effectively?
Migrate the utility class to use your new class. Then the utility class methods should only contain two statements. One for creating your class and the other is invoking your class. After that, you can inline the utility class methods thereby eliminating the need for it.
When you are finished with that, you need to look for a way to not instantiate your new class over and over again. This should be done by refactoring the local variable to an instance field which is initialized at construction time.
Database paths sound like they should be Strings to me. What else makes sense? And they should be externalized, either in configuration files or a database. That's the least of your problems.
Persistence has been so many times over (e.g. Hibernate, Spring JDBC, iBatis, etc.) that I'd wonder how you could possibly improve on them. If you have to go to the trouble of refactoring - and you must - I'd advise using anything other than what you've done.
If you must write something, Google for "generic DAO". You'll get stuff like this:
http://www.ibm.com/developerworks/java/library/j-genericdao/index.html
If your work isn't patterned after something like that, throw it away and re-think things.
A technique I've used in C# (and just ported to Java - apologies if I've made an error, I'm new to Java) is to create StringlyTyped classes, e.g. a base class
public abstract class StringlyTyped {
private String value;
public StringlyTyped (String value){
if (value == null){
throw new IllegalArgumentException("value must not be null");
}
this.value = value;
}
public String getValue() { return value; }
@Override
public boolean equals(Object other){
if (other == this) {
return true;
}
if (other == null || !this.getClass().equals(other.getClass())){
return false;
}
StringlyTyped o = (StringlyTyped)other;
return o.getValue().equals(this.getValue());
}
@Override
public int hashCode(){ return this.getValue().hashCode(); }
@Override
public String toString() { return this.getValue(); }
}
Then derived class
public class ProviderName extends StringlyTyped {
public ProviderName(String value) {
super(value);
}
}
And usage
public void Foo(ProviderName provider) {
}
It makes sense when you have methods with many String parametrers, e.g. you can avoid
public void Foo(String username, String password, String db, String table, String constraint)
and instead have code that is strongly typed like this:
public void Foo(UserName username, Password password, DatabasePath db, TableName table...)
etc...
I generally try to isolate the strings at the limit of the application/process boundary, such as when they are retrieved from a database or received via a web operation.
At that application/process boundary is often the ideal place to map/convert/deserialize the strings into a more proper object model, as supported by whatever language you are using.
Similarly, the object model can be mapped/converted/serialized back into string form as it exits your application/process boundary.
It is worth noting that this stringly typing can be somewhat subtle. I commonly see xml intruding into application and domain layers. A similar example from the .NET space would be failing to map ADO.NET DataTables (with their string column names and untyped field values) into classes/objects pretty much as soon as they are received. I have no doubt that there are many similar equivalents in the Java world. Stringly Typing is not just limited to date values, as the joke goes.