我们应该使用接口方法吗?

时间:2010-06-28 08:07:36

标签: oop interface

这个问题有点假设。假设我有一个绘制树的应用程序。我的结构控制器使用以下方法实现描述普通树的ITree接口:getChildren(),getParent()...应用程序的某些部分只需知道结构实现了ITree,因此它可以查找它的子项或父项。因此,getParent()足以返回一个ITree类型的对象。因此getChildren()可以返回一组ITree对象。

但:)

如果在假设的TreeNode类中我想要对节点的子节点执行特定操作,该怎么办?如果我使用ITree的getChildren()函数,我必须强制它们到TreeNode,以便能够调用适当的实例函数(未由ITree定义)。或者我可以使用我自己的迭代器来遍历子节点并尽我所能(因为这样那些真的是TreeNodes)。

所以问题是:它是否建议每次可能使用接口函数,或者有时对象特定的行为有意义?

更新:[插图已添加] (这是AS3语法,但重点是结构。)

所以这是ITree:

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

这是我的TreeNode类:

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();
  }

}

哪一个更好:版本1还是2?

由于

3 个答案:

答案 0 :(得分:1)

您需要尽可能使用ITree界面,以减少耦合。如果您需要接口未提供的功能,请使用TreeNode对象,但请尝试避免强制转换操作。

答案 1 :(得分:1)

一旦你忘记了对象的真实类型,将它作为一个超类型传递,就没有一种安全的方法可以将它恢复原状,因为:

  

subtypeInstance supertypeInstance

  

超类型实例不是 subtypeInstance

所以逻辑上你不能从超类型实例返回到子类型实例 - 至少不能保证安全。

解决这个问题的唯一方法是在需要进行子类型操作时记住子类型。如果您的语言支持它们,则可以使用协变返回类型来改进接口定义,以便不会不必要地丢失子类型:

In ITree:
    public function getParent():ITree

In TreeNode:
    public function getParent():TreeNode

这样,当您在TreeNode上调用getParent()时,您将获得TreeNode,而不是ITree。当然,如果你在声明为ITree的同一个实例上调用getParent(),你就会得到一个ITree。一旦忘记了类型信息,就太晚了。

在类中,您可以选择处理实际类型。您仍然可以在界面中与外部世界交谈(故意丢失超类型信息)但实现可以处理实际类型而不是接口。

我正在尝试使用Java中的一些代码。如果您有兴趣,请点击这里:

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);
    }
}

答案 2 :(得分:1)

您如何看待自定义的继承接口,例如:

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

如果我有一个CustomTreeNode类(实现ICustomTreeNode ),那么我创建一个新的ITree子接口:

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

通过这种方式,我可以使用正确输入的对象。