Reputation: 1631
I'm working on a Java (RCP) project in which I need to convert different source file formats (let's call them SF-1..N) to another two different destination formats (DF-A, DF-B) Right now the input files can be CSVs, XLSXs and XMLs (with different schemas). The application should find the right converter and it's necessary to read the file content to do that. Each converter normalizes the content of the file and creates 1..N instances of an DF objects that get converted to either DF-A or DF-B record.
So I have a few converters that get the file content and return a collection of DFs objects. Each converter should also be able to tell if the file content is the one it supports.
The only strategy I can think of is asking each converter if it supports the content of the file and if it doesn't try the next one. If there no converter suitable, return an error to the user.
It's also likely I'll be asked to add support from/to new formats and I'd like to be able to add more converters without modifying unnecessary code.
I'm thinking about registering each converter in a service-locator object but I'm not sure how to do that with minimal coupling. I thought about registering to the service locator in the static initializer but that gets called only after loading the class.
How can I do that? Is there a better approach for what I want to accomplish?
Upvotes: 2
Views: 690
Reputation: 25873
A factory design looks appropriate in this case
public abstract class DF {
// Default implementation and abstract methods for each subtype
}
public class DF_A extends DF {
}
public class DF_B extends DF {
}
public final enum FileType {
XML, CSV, XLSX;
}
public interface FileConverter {
DF convert(final URI fileLocation);
}
class XMLConverter implements FileConverter {
@Override
public DF convert(final URI fileLocation) {
// Convert file to DF and return DF
}
}
// And so on for all the file type converters
public class FileConverterFactory {
private static Map<FileType, FileConverter> FILE_CONVERTER_MAP = new HashMap<>();
static {
FILE_CONVERTER_MAP.put(FileType.XML, new XMLConverter());
// And so on for all file types
// You can make the map unmodifiable
FILE_CONVERTER_MAP = Collections.unmodifiableMap(FILE_CONVERTER_MAP);
}
public static final DF convert(final FileType fileType, final URI fileLocation) {
return FILE_CONVERTER_MAP.get(fileType).convert(fileLocation);
}
}
In case client doesn't know the file type, the factory could be modified as follows:
public class FileConverterFactory {
private static Map<FileType, FileConverter> FILE_CONVERTER_MAP = new HashMap<>();
static {
FILE_CONVERTER_MAP.put(FileType.XML, new XMLConverter());
// And so on for all file types
// You can make the map unmodifiable
FILE_CONVERTER_MAP = Collections.unmodifiableMap(FILE_CONVERTER_MAP);
}
private static FileType getFileType(final URI fileLocation) throws UnsupportedFileFormatException {
// Check file type and return enum as appropriate
}
// Client knows file type
public static final DF convert(final FileType fileType, final URI fileLocation) {
return FILE_CONVERTER_MAP.get(fileType).convert(fileLocation);
}
// Client doesn't know file type, let factory decide
public static final DF convert(final URI fileLocation) throws UnsupportedFileFormatException {
return convert(getFileType(fileLocation), fileLocation);
}
}
Upvotes: 1
Reputation: 32936
M0skit0's answer is good but given that the client doesn't know the type of file I would have the converter interface implement a CanConvert method.
Then you can simply loop through all of the converters asking if then can convert the file and use the first one that can.
This also had the advantage that you can add new converters dynamically without needing to change the enum and so they could be added with out recompiling the application.
Upvotes: 2