Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practices for using interfaces in code, but hiding internal methods from end user

Review the following model:

interface Context {
    BeanFactory getBeanFactory(); // public method
    void refresh(); // public method
    void destroy(); // public method
}

interface BeanFactory {
    <T> T getBean(String id); // public method
    void destroyBeans(); // should be private method for user, but visible for Context
}

class ContextImpl implements Context {
    private BeanFactory beanFactory;

    @Override
    public void destroy() {
        beanFactory.destroyBeans();
    }
}

ContextImpl uses BeanFactory interface, that's why method destroyBeans() is placed there. But I don't want it to be there, because it is internal API and should be hidden from user.

I thought of using AbstractBeanFactory reference with protected destroyBeans() method inside Context. That will solve the problem of exposing method to end user, but will replace the interface with abstract class.

Another variant is to make another interface, that will extend end-user interface, and use it inside Context. This will break the ability for user to create his own BeanFactory implentations.

I wanted to know if there is a well-known solution for the problem or just see another alternatives.

like image 404
AdamSkywalker Avatar asked Sep 28 '22 10:09

AdamSkywalker


1 Answers

You can separate your user-facing methods into a user-facing interface, the rest in another one.

interface Context {
    BeanFactory getBeanFactory(); // public method
    void refresh(); // public method
    void destroy(); // public method
}

interface BeanFactory {
    <T> T getBean(String id); // public method
}

interface DestroyableBeanFactory extends BeanFactory {
    void destroyBeans(); // should be private method for user, but visible for Context
}

class ContextImpl implements Context {
    private DestroyableBeanFactory beanFactory;

    // internally we demand a DestroyableBeanFactory but we only
    // expose it as BeanFactory
    public BeanFactory getBeanFactory() {
        return beanFactory;
    }
    @Override
    public void destroy() {
        beanFactory.destroyBeans();
    }
}

Update: If you're worried about a caller casting your BeanFactory to DestroyableBeanFactory and calling destroyBeans() on it, you can return a read-only view instead:

class ContextImpl implements Context {
    private DestroyableBeanFactory beanFactory;

    // to be extra safe, we create a read-only wrapper
    // for our bean factory
    public BeanFactory getBeanFactory() {
        return new BeanFactory() { //written as an anon inner class for brevity, ideally you should cache this read-only wrapper instance
             public <T> T getBean(String id) { 
                 return beanFactory.getBean(id);
             }
        };
    }
    ...
  }

With this the only way to access the value of the beanFactory field is through reflection (or, optionally, serialization). But if you only want to defend against naughty developers cutting corners and not a malicious attacker, you should be fine.

like image 196
biziclop Avatar answered Oct 13 '22 01:10

biziclop