Должны ли мы использовать методы интерфейса, если мы можем? - PullRequest
1 голос
/ 28 июня 2010

Вопрос немного гипотетический. Допустим, у меня есть приложение, которое рисует дерево. Мой контроллер структуры реализует интерфейс ITree, который описывает нормальное дерево с помощью таких методов, как: getChildren (), getParent () ... Некоторым частям приложения нужно только знать, что структура реализует ITree, поэтому он может искать своих детей или родителей , Таким образом, getParent () достаточно высок, чтобы вернуть типизированный объект ITree. И поэтому getChildren () может возвращать коллекцию объектов ITree.

Но:)

Что если в гипотетическом классе TreeNode я хочу сделать что-то конкретное с дочерними узлами. Если я использую функцию getChildren () ITree, я должен привести их к TreeNode, чтобы иметь возможность вызывать соответствующие функции экземпляра (которые не определены ITree). Или я могу использовать свой собственный итератор для обхода дочерних узлов и делать все, что могу (потому что в этом случае это действительно TreeNodes).

Таким образом, вопрос заключается в следующем: предлагается ли использовать интерфейсные функции всякий раз, когда это возможно, или определенные действия для объекта иногда имеют смысл?

Обновлено: [добавлена ​​иллюстрация] (Это синтаксис AS3, но дело в структуре.)

Итак, вот оно:

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 ]

1 голос
/ 28 июня 2010

Как только вы забыли реальный тип объекта, передав его в качестве одного из его супертипов, нет безопасного способа вернуть его обратно, потому что:

экземпляр subtype - это экземпляр supertype

но

экземпляр супертипа не является экземпляром подтипа

логично, что вы не можете вернуться от экземпляра супертипа к экземпляру подтипа - по крайней мере, без какой-либо гарантии безопасности.

Единственный способ обойти это - помнить подтип, когда вам нужно выполнять операции с подтипами. Если ваш язык поддерживает их, ковариантные типы возвращаемых данных можно использовать для улучшения определений интерфейса, чтобы подтип не терялся без необходимости:

In ITree:
    public function getParent():ITree

In TreeNode:
    public function getParent():TreeNode

Таким образом, когда вы вызываете getParent () для TreeNode, вы получаете TreeNode, а не ITree. Конечно, если вы вызываете getParent () точно в том же экземпляре, объявленном как ITree, вы получаете обратно 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);
    }
}
1 голос
/ 29 июня 2010

Что вы думаете о создании настраиваемых унаследованных интерфейсов, например:

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

И если бы у меня был класс CustomTreeNode (, который реализует ICustomTreeNode ), тогда я создаю новый подинтерфейс ITree:

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

Таким образом, я мог бы использовать правильно типизированные объекты.

1 голос
/ 28 июня 2010

Вам необходимо использовать интерфейс ITree везде, где вы можете, чтобы уменьшить связь. Если вам нужна функциональность, которую не обеспечивает ваш интерфейс, используйте объект TreeNode, но старайтесь избегать операции приведения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...