Reputation: 1098
I want to hide the implementation (concrete class) of a service (interface) from the API user. The implementation is provided to the user by a factory which uses the Java API ServiceLoader mechanism. This Loader requires the implementation class to have public visibility. This is OK as long as the implementation is hidden in a different JAR (apart from the API JAR) the user is not directly depending on.
However, for easy distribution the content of the JAR with the default implementation is packed into the API JAR. So effectively the user depends on this prepacked JAR where the default implementation class is available with public visibility. Nothing keeps people from directly instantiating the implementation. I don't want that to be possible.
My bad ideas:
What do you think is the right way? Any compromise?
Using OSGi or other heavy machinery is out of question.
Upvotes: 6
Views: 2394
Reputation: 21408
In C# you can create an internal interface that is visible only to relevant friend assemblies, and then explicitly implement the interface. I'm not sure how you would do this in Java, but conceptually this is how I've been able to work around this problem in the .NET world.
EDIT I just looked it up, from what I can tell Java doesn't have explicit implementation for interfaces. As an FYI, an explicit implementation of an interface requires you to make a function call through an interface member, you can't call it as a member of the implementing class. This is why my technique works for me, at least in the .NET world.
Alternatively, I would suggest putting the interface in another JAR, and don't distribute it with the API
Upvotes: 1
Reputation: 1019
I prefer your first approach, making the implementation class package private, and using a ServiceLoctor to retrieve a specific service. In my project, the package structure is like this:
-package
|-ServiceInterface(public)
|-ServiceImplementation(default,package private)
|-ServiceLocator(public)
The serviceLocator is like this:
public class ServiceLocator{
private static final Map<String,Service> services;
static {
services=new HashMap<String,Service>();
services.put("default",new ServiceImplementation());//hide implementation
}
public Service getService(String name){
return services.get(name);
}
public registerService(Service service, String name){
services.put(name,service)
}
}
The end user does not have access to the implementation class, and in later time, you can easily change to use a different implementation class.
Upvotes: 0
Reputation: 109532
Disappointing maybe but:
<scope>runtime</scope>
.The only other way would be the have @Deprecated
in the javadoc, with as comment how to use the corresponding class using the java service API.
Upvotes: 3
Reputation: 272207
You can't easily prevent people from instantiating/accessing any shipped class.
As such I would perhaps simply wrap the ServiceLoader
mechanism that you're using and provide the instantiated class referenced through a Java interface (rather than as a concrete implementation reference).
Upvotes: 0