这个问题有点假设。假设我有一个绘制树的应用程序。我的结构控制器使用以下方法实现描述普通树的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?
由于
答案 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;
}
通过这种方式,我可以使用正确输入的对象。