2010-06-28 6 views
1

La question est un peu hypothétique. Disons que j'ai une application qui dessine un arbre. Mon contrôleur de structure implémente l'interface ITree qui décrit un arbre normal avec des méthodes comme: getChildren(), getParent() ... Certaines parties de l'application ont seulement besoin de savoir que la structure implémente ITree, donc elle peut chercher ses enfants ou ses parents . Ainsi, getParent() est suffisamment élevé pour renvoyer un objet typé ITree. Ainsi, getChildren() peut renvoyer une collection d'objets ITree.Devrions-nous utiliser des méthodes d'interface si nous le pouvons?

Mais :)

si dans la classe TreeNode hypothétique que je veux faire quelque chose de spécifique avec les enfants d'un nœud. Si j'utilise la fonction getChildren() de ITree, je dois leur attribuer à TreeNode pour pouvoir appeler les fonctions d'instance appropriées (qui ne sont pas définies par ITree). Ou je peux utiliser mon propre itérateur pour parcourir les nœuds enfants et faire ce que je peux (parce que de cette façon, ce sont vraiment des TreeNodes). La question est donc: est-il suggéré d'utiliser des fonctions d'interface chaque fois que c'est possible, ou des actions spécifiques à un objet ont parfois un sens?

Mise à jour: [illustration ajouté] (. Sa syntaxe AS3, mais le point est la structure)

Voici donc iTree:

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

Et voici ma classe 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(); 
    } 

} 

Lequel est le meilleur: version 1 ou 2?

Merci

+0

Il est difficile de visualiser ce dont vous parlez. Pouvez-vous poster du code/pseudo-code illustrant de quoi vous parlez? – Oded

Répondre

1

Vous devez utiliser l'interface iTree partout où vous pouvez, pour réduire le couplage. Si vous avez besoin de fonctionnalités que votre interface ne fournit pas, utilisez l'objet TreeNode, mais essayez d'éviter l'opération de conversion.

+0

Je suis d'accord pour réduire le couplage et éviter le moulage, c'est pourquoi ce n'est pas une question évidente, du moins pour moi. Je suppose qu'il devrait y avoir une troisième façon de le résoudre, s'il y a une façon générale de le faire. – itarato

+0

Si vous devez utiliser la fonction foo(), alors la version 2 est préférable ou ajoutez cette fonction à l'interface. L'interface suppose que n'importe quel objet pourrait l'implémenter, donc si quelqu'un ajoutera CustomTreeNode, cela causera des erreurs. –

+0

Je ne suis pas sûr d'ajouter ces fonctions à l'interface. Par exemple, les méthodes protégées ne doivent pas être visibles bien qu'elles effectuent des tâches internes importantes. – itarato

1

Une fois que vous avez oublié le type réel d'un objet en le passant comme l'un de ses super-types il n'y a pas un moyen sûr de le récupérer parce que:

subtypeInstance est un supertypeInstance

mais

supertype exemple est pas un subtypeIn stance

donc, logiquement, vous ne pouvez pas revenir d'une instance de super-type à une instance de sous-type - au moins sans garantie de sécurité. La seule façon de contourner ceci est de se souvenir du sous-type pour quand vous devez faire des opérations de sous-type. Si votre langue les prend en charge, les types de retour covariants peuvent être utilisés pour améliorer les définitions d'interface de telle sorte que le sous-type ne soit pas perdu inutilement:

In ITree: 
    public function getParent():ITree 

In TreeNode: 
    public function getParent():TreeNode 

De cette façon, lorsque vous appelez getParent() sur un TreeNode, vous obtenez un TreeNode, pas un ITree. Bien sûr, si vous appelez getParent() sur exactement la même instance déclarée comme ITree, vous récupérez un ITree.Une fois que vous avez oublié les informations de type, il est trop tard.

Dans une classe, vous pouvez choisir le type réel. Vous pouvez toujours parler au monde extérieur dans l'interface (en perdant délibérément les informations de super-type) mais l'implémentation peut traiter des types réels au lieu des interfaces. J'expérimentais du code en Java. Si vous êtes intéressé, la voici:

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

Merci pour l'extrait détaillé. Malheureusement, dans AS3, il n'y a aucun moyen de faire des types génériques comme Java, C#, etc. Vous ne pouvez pas non plus utiliser des fonctions surchargées qui utilisent des sous-types. – itarato

1

Que pensez-vous de faire des interfaces héritées personnalisées, comme:

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

Et si j'aurais une classe CustomTreeNode (qui implémente ICustomTreeNode) puis je crée une nouvelle sous-interface ITree:

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

De cette façon, je pourrais utiliser des objets correctement typés.

Questions connexes