Let me start by abstractly formulating the problem: I have two public interface types. One of them contains a method which receives at least two instances of the other interface type. The implementation of the method depends on the implementation of the passed objects.
Consider the following public API, which consists of two interfaces:
public interface Node {
}
public interface Tree {
void connect(Node parent, Node child);
}
Now, I want to implement that API, like so:
public class NodeImpl implements Node {
private final Wrapped wrapped;
public NodeImpl(Wrapped wrapped) {
this.wrapped = wrapped;
}
public Wrapped getWrapped() {
return wrapped;
}
}
public class TreeImpl implements Tree {
@Override
public void connect(Node parent, Node child) {
// connect parent and child using the wrapped object
}
}
public class Wrapped {
// wrapped object which actually represents the node internally
}
I need to access the wrapped objects in the connect method, which is impossible, because the getWrapped method is not part of the API. It is an implementation detail.
So the question is: How can I implement the connect method without leaking implementation detail to the API?
Here is what I tried so far:
Put the
connectmethod in theNodeinterface and callparent.connect(child). This gives me access to the wrapped object of the parent, however the wrapped object of the child is still not available.Just assume the passed
Nodeis of typeNodeImpland use a downcast. This seems wrong to me. There might be otherNodeimplementations.Don't put the wrapped object in the node, but use a map in the
TreeImplthat mapsNode's toWrappedobjects. This is basically the same as above. It breaks down as soon as aNodeinstance is passed to theconnectmethod, which has no associated mapping.
Please note, that the Node interface might contain methods. However, this is unimportant for this question.
Also, please note that I am in control of both: The interface declaration as well as the implementation.
Another attempt to solve this is to convert the connect method to a addChild method in the Node interface and to make the Node interface generic:
public interface Node<T extends Node<T>> {
void addChild(Node<T> child);
}
public class NodeImpl implements Node<NodeImpl> {
private final Wrapped wrapped;
public NodeImpl(Wrapped wrapped) {
this.wrapped = wrapped;
}
public Wrapped getWrapped() {
return wrapped;
}
@Override
public void addChild(Node<NodeImpl> child) {
}
}
public class Wrapped {
// wrapped object which actually represents the node internally
}
public Node<NodeImpl> createNode() {
return new NodeImpl(new Wrapped());
}
private void run() {
Node<NodeImpl> parent = createNode();
Node<NodeImpl> child = createNode();
parent.addChild(child);
}
Node and createNode are part of the public API. NodeImpl and Wrapped should be hidden. run is the client code. As you can see, NodeImpl has to be visible to the client, so this is still a leaking abstraction.