I have a simple factory class (FileResources
) with static factory methods providing a default implementation (DefaultFileResource
).
public final class FileResources {
private FileResources() {}
public static FileResource get(Path path) {
return new DefaultFileResource(path);
}
}
The problem with this class is that it is tightly coupled with the default implementation (DefaultFileResource
).
Given a new requirement that classes part of the API (FileResources
) cannot directly depend on classes not part of the API (DefaultFileResource
) I figured that the only solution is inversion of control, either dependency injection or a service provider service locator.
Is it possible to decouple FileResources from the default implementation (DefaultFileResource
)?
As far as I know this is only doable by injecting the dependency into FileResources through a constructor, field or setter method.
The main problem is that the constructor of FileResources is private, furthermore it only has static factory methods.
The alternative is the factory method pattern. There would be an abstract factory (an API class), and there would be concrete factories (non-API classes) that could be even injected to decouple the API and non-API classes.
Either way a dependency container is necessary to handle the wiring.
What do you think? Am I overthinking this?
4
No, an IOC container is not necessary to decouple the API and non-API classes. Because they’re not really coupled.
FileResources is coupled to Path and to FileResource, but not to DefaultFileResource. That is, if you change Path or FileResource, there is a risk of breaking FileResources. Albeit not a large one. Those are pretty thin layers; that’s an acceptable coupling.
However, if you change DefaultFileResource, as long as it still conforms to FileResource, you cannot break FileResources.
You wouldn’t even break any tests for FileResorces. They test only that a DefaultFileResource is returned, under the default conditions, and that’s fine.
Even more importantly, your calling code is not coupled to any particular implementation of FileResource either. That’s good. In fact it’s the whole point of a Factory Method. (And this is a Factory Method. There is no Factory Class distinction, in the context you’ve used it; it doesn’t matter whether the Factory Method exists in another class.)
However, your calling code IS coupled to FileResources. Most notably, if you change the implementation of FileResources, there is every chance you would break any unit tests that are testing your calling code, because the get method cannot be stubbed out.
That is a much bigger concern than any knowledge FileResources has about DefaultFileResource.
The only reason you would use an IOC container for the factory is if you want to build up DefaultFileResource with its dependencies. I would avoid that unless you need it and, if you should need it, I would take a look around at the Service Locator pattern and why is should be avoided, before I decided on the use of that IOC container.
2
What to do really depends on how far you want to go into abstractions. A simple way to take is to add a static .getInstance(Path path)
method to the abstract FileResource
class (if it’s an interface, you would change it into an abstract class), which would then return a default instance. This may not exactly meet your needs (FileResource
would now be dependent on the default implementation instead), and your app may run into inheritance issues if you change an interface into an abstract class.
The second option is to create a FileResourceFactory
. It can either be a class or an interface with implementing classes. A single implementing class is simpler, but creates dependencies on implementations, while an interface means more complexity but potentially less coupling.
Since this is a static class, dependency injection through the constructor is not possible, but you can create a setter method for a static FileResourceFactory
, and inject it when you need to. Two warnings arise here, though:
- You cannot guarantee that the static factory has been set before your get method is called
- You are wandering dangerously close to global variable territory with a static member on a class like this
I also want to point out that, at some point, someone eventually needs to grab something concrete. With the dependency injection approach, you can push this pretty far out, but eventually someone will be dependent on getting a concrete instance somewhere.