Alexander
Alexander

Reputation: 1098

Best practice to hide a service implementation

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

Answers (4)

codewario
codewario

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

ltebean
ltebean

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

Joop Eggen
Joop Eggen

Reputation: 109532

Disappointing maybe but:

  • Make a separate jar and put that with the other third party jars.
  • Let the installer/the deployment process handle nice packaging of all.
  • Do not make this jar available during compile time; maven: <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

Brian Agnew
Brian Agnew

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

Related Questions