2

我现在正在为树结构重写一个游标 API。我有这样的方法:

/**
 * Move cursor to parent node of currently selected node.
 * 
 * @return the cursor instance
 */
INodeCursor moveToParent();

/**
 * Move cursor to first child node of currently selected node.
 * 
 * @return the cursor instance
 */
INodeCursor moveToFirstChild();

以及相应的布尔 hasParent() 和 hasFirstChild()... 方法。到目前为止,moveToX() 方法还返回了一个布尔值,但我认为是这样的:

trx.moveToParent().moveToParent().insertFirstChild(...).moveToRightSibling()...如果您知道自己在做什么,那就更有趣了。但是,我不确定如果失败该怎么办(返回 null 以立即生成 NPE?)。也许最好返回游标实例,调用者必须知道游标可能根本没有移动。

感谢您的任何建议。

编辑:也许这也是 Google Guava Optional 的一个用例?这样我就可以从两者中得到最好的了?

trx.moveToRightSibling().get().moveToRightSibling().get().insertTextAsFirstChild("foo")

if (trx.moveToRightSibling.isPresent()) && trx.moveToRightSibling.isPresent() && "foo".equals(rtx.getValue) { ... }

加上额外的 trx.hasRightSibling()... 方法。所以也许是一个自写的简单包装器,具有大致相同的语义但名称不同。

if (trx.moveToRightSibling().didSucceed() && ...)trx.moveToRightSibling().get().moveToRightSibling().get() ...)

编辑2:例如:

/**
 * Determines if the {@link INodeCursor} moved to a node or not. Based on the
 * idea of providing a wrapper just like in Google Guava's {@link Optional}
 * class.
 * 
 * @author Johannes Lichtenberger
 * 
 * @param <T>
 *          type parameter, the cursor
 */
public abstract class Move<T extends INodeCursor> {
  /**
   * Returns a {@link Moved} instance with no contained reference.
   */
  @SuppressWarnings("unchecked")
  public static <T extends INodeCursor> Move<T> notMoved() {
    return (Move<T>) NotMoved.INSTANCE;
  }

  /**
   * Returns a {@code Moved} instance containing the given non-null reference.
   */
  public static <T extends INodeCursor> Moved<T> moved(final @Nonnull T pMoved) {
    return new Moved<T>(checkNotNull(pMoved));
  }

  /**
   * Determines if the cursor has moved.
   * 
   * @return {@code true} if it has moved, {@code false} otherwise
   */
  public abstract boolean hasMoved();

  /**
   * Get the cursor reference.
   * 
   * @return cursor reference
   */
  public abstract T get();
}

编辑3:和我的moveTo(long)方法,所有其他moveToX()方法都基于:

@Override
public Move<? extends INodeCursor> moveTo(final long pNodeKey) {
    assertNotClosed();
    if (pNodeKey == EFixed.NULL_NODE_KEY.getStandardProperty()) {
        return Move.notMoved();
    }

    // Remember old node and fetch new one.
    final INode oldNode = mCurrentNode;
    Optional<? extends INodeBase> newNode;
    try {
        // Immediately return node from item list if node key negative.
        if (pNodeKey < 0) {
            if (mItemList.size() > 0) {
                newNode = mItemList.getItem(pNodeKey);
            } else {
                newNode = Optional.absent();
            }
        } else {
            final Optional<? extends INodeBase> node = mPageReadTrx.getNode(
                    pNodeKey, EPage.NODEPAGE);
            newNode = node;
        }
    } catch (final SirixIOException e) {
        newNode = Optional.absent();
    }

    if (newNode.isPresent()) {
        mCurrentNode = (INode) newNode.get();
        return Move.moved(this);
    } else {
        mCurrentNode = oldNode;
        return Move.notMoved();
    }
}
4

4 回答 4

1

就个人而言,在这种情况下,我会创建自己的例外:

public class NoSuchNodeException extends RuntimeException {
  ...
}

此异常将从您的游标方法中引发。它允许客户端响应一个有意义的条件(他们请求移动到一个不存在的节点),而不是一个通用的 NPE,这可能意味着任何事情。是否检查异常取决于您的要求。不过,在这种情况下,我发现未经检查的异常要友好得多。

public INodeCursor moveToParent() {
   if (currentNode.parent == null) {
       throw new NoSuchNodeException("Node has no parent", currentNode);
   }
}
于 2012-09-28T03:02:19.617 回答
1

你可以很聪明地创建一个 NullNode 实例。当您 moveToChild 并且没有子节点时,返回 NullNode 并将其父节点设置为您的实际节点。然后,如果你得到一个空节点,你可以检查 isNullNode,你可以用“printPath”方法或其他方法将它带回你从树上掉下来的地方。

如果假设您的分支通常是正确的,那么它会使您的方法链接更加清晰,因为理想情况下它们很少会失败。

于 2012-09-28T05:21:07.903 回答
1

根据以上评论:

问题归结为:1)我应该使用布尔返回还是实例

无论哪种方式都需要错误检查:

if (trx.moveToParent()) {
    if (trx.moveToParent()) {
        trx.doSomething();

对比

try {
    trx.moveToParent().moveToParent().doSomething();
}
catch(NPE ex) {

第一种方式稍微明显一点,就是修改了trx,但是使用起来有点不方便(比如报错需要一个elsefor每个条件[设置一个标志,然后根据这个标志做一个单一的报错])

不改变节点的想法与返回布尔值相同,但更丑陋

trx.noveToParent();
if (trx.didMove()) {
    trx.moveToParent();
    if (trx.didMove()) {
        trx.doSomething();

如果是我,我会返回节点并抛出 NPE——您确实需要确保您的变量已正确更新。即 trx.moveToParent().moveToParent() 实际上修改了 trx 两次,而不是一次更改为 trx 和一次更改为某个匿名副本。好的单元测试在这里会有所帮助。

于 2012-09-28T01:59:47.467 回答
0

我建议立即抛出一个自己的异常(可能是 NPE 的一个子类),所以它是快速失败的。lenient()有时您可能希望通过返回具有所有移动方法但没有变异方法的不同类的方法来使事情变得更容易。宽松类的移动方法从不抛出;如果发生错误,它们会返回失败的实例。通过使用strict(),您可以返回原始类或获取异常,以防路径上的某处出现错误。

通常,你会做类似的事情

trx.up().right().right().firstChild().addChild(....);

并在出现问题时立即获得异常(是的,我会简化方法名称,“moveToRight”如何比“right”更好?)。“宽松”的使用可能就像

ILenientNodeCursor c1 = trx.lenient().up().right().right().firstChild();
if (c1.failed()) c1 = trx.right().right().firstChild();
if (!c1.failed()) c1.strict().addChild(...);

我不太确定“宽松”,但我很确定正常的行为应该是严格的,尽可能快地投掷。这肯定比其他任何东西都不容易出错,而且大多数时候更容易使用。

于 2012-09-28T13:04:32.543 回答