tags:

views:

79

answers:

3

Hi, The question is a bit hypothetical. Let's say I have an application that draws a tree. My structure controller implements the ITree interface that describes a normal tree with methods like: getChildren(), getParent() ... Some parts of the app only needs to know that the structure implements ITree, so it can look for it's children or parent. Thus getParent() is highly enough to return an ITree typed object. And so getChildren() can return a collection of ITree objects.

But:)

What if in the hypothetical TreeNode class I want to do something specific with a node's children. If I use the getChildren() function of ITree, I have to cast them to TreeNode to be able to call the appropriate instance functions (that are not defined by ITree). Or I can use my own iterator to walk through the child nodes and do whatever I can (because this way those are really TreeNodes).

So the question is: is it suggested to use interface functions every time it's possible, or object specific actions make sense sometimes?

Updated: [illustration added] (It's AS3 syntax but the point is the structure.)

So here is ITree:

public interface ITree {
  function getParent():ITree;
}

And here is my TreeNode class:

public class TreeNode implements ITree {
  private var parent:TreeNode

  public function getParent():ITree {
    return parent;
  }

  function foo():void {}

  function bar():void {
    // Version 1 - using the interface
    (getParent() as TreeNode).foo();

    // Version 2 - using custom object accessor
    parent.foo();
  }

}

Which one is better: version 1 or 2?

Thanks

+1  A: 

You need to use ITree interface everywhere you can, to reduce coupling. If you need functionality that your interface do not provides, use TreeNode object, but try to avoid cast operation.

Orsol
I agree with reducing coupling and avoid casting, that's why it's not an obvious question, at least for me. I guess there should be a third way to solve it, if it has a general way to do.
itarato
If you need to use function foo(), then Version 2 is preferable or add this function to interface. Interface suppose that any object could implement it, so if somebody will adds CustomTreeNode it will cause errors.
Orsol
I'm not sure about adding these functions to the interface. For example protected methods shouldn't be visible although they're doing inner tasks that are important.
itarato
+1  A: 

Once you have forgotten the real type of an object by passing it as one of its super-types there isn't a safe way to get it back because:

subtypeInstance is a supertypeInstance

but

supertype instance is not a subtypeInstance

so logically you can't go back from a super-type instance to a sub-type instance - at least not with any guarantee of safety.

The only way around this is to remember the sub-type for when you need to do sub-type operations. If your language supports them, covariant return types can be used to improve interface definitions so that the sub-type isn't lost unnecessarily:

In ITree:
    public function getParent():ITree

In TreeNode:
    public function getParent():TreeNode

This way when you call getParent() on a TreeNode, you get a TreeNode, not an ITree. Of course if you call getParent() on exactly the same instance declared as an ITree, you get back an ITree. Once you've forgotten type information, it's too late.

Inside a class you can choose to deal with the real type.You can still talk to the outside world in interface (deliberately losing the super-type information) but the implementation can deal with real types instead of interfaces.

I was experimenting with some code in Java. If you're interested, here it is:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.junit.Assert;
import org.junit.Test;

public class TestTree {
    @Test
    public void testGetDepth() {
        ITreeNode bigTree = TreeNode.create(null, 10);       
        ITreeNode left    = bigTree.getChildren().get(0);

        Assert.assertEquals(10, bigTree.getHeight());
        Assert.assertEquals(9,  left.getHeight());

        Assert.assertEquals(0, bigTree.getDepth());
        Assert.assertEquals(1, left.getDepth());
    }
}

interface ITree {
    List<? extends ITree> getChildren();
    ITree                 getParent();
}

interface ITreeNode extends ITree {
    @Override
    List<? extends ITreeNode> getChildren();

    int getDepth();
    int getHeight();
}

class TreeNode implements ITreeNode {
    private ITreeNode m_parent;

    private final TreeNode m_left;
    private final TreeNode m_right;
    private final List<ITreeNode> m_children;

    public static ITreeNode create(ITreeNode parent, int depth) { 
        TreeNode node = createDescendants(depth);

        node.setParents(parent);

        return node;
    }

    private static TreeNode createDescendants(int depth) { 
        if (depth == 0) {
            return new TreeNode(null, null, null);
        }
        else {
            return new TreeNode(null, TreeNode.createDescendants(depth - 1), TreeNode.createDescendants(depth - 1));
        }
    }

    private TreeNode(ITreeNode parent, TreeNode left, TreeNode right) {
        m_parent = parent;
        m_left   = left;
        m_right  = right;

        List<ITreeNode> children = new ArrayList<ITreeNode>();
        children.add(left);
        children.add(right);
        m_children = Collections.unmodifiableList(children);
    }

    private void setParents(ITreeNode parent)
    {
        m_parent = parent;

        if (m_left != null)
            (m_left).setParents(this);

        if (m_right != null)
            m_right.setParents(this);
    }

    public List<? extends ITreeNode> getChildren() {
        return m_children;
    }

    public ITreeNode getParent() {
        return m_parent;
    }

    public int getDepth() {
        int depth = 0;

        if (m_parent != null) {
            depth = m_parent.getDepth() + 1;
        }

        return depth;
    }

    public int getHeight() {

        int leftHeight  = (m_left == null)  ? 0 : m_left.getHeight() + 1;
        int rightHeight = (m_right == null) ? 0 : m_right.getHeight() + 1;

        return Math.max(leftHeight, rightHeight);
    }
}
richj
Thanks for the detailed snippet. Unfortunately in AS3 there is no way to do generic types like in Java, C#, etc. Also can't use overridden functions that are using subtypes.
itarato
+1  A: 

What do you think about making customized inherited interfaces, like:

public interface ITree {
  function getParent():ITree;
}

And if I would have a CustomTreeNode class (that implements ICustomTreeNode) then I create a new ITree subinterface:

public interface ICustomTree extends ITree {
  function getCustomParent():CustomTreeNode;
}

In that way I could use properly typed objects.

itarato