2010-05-25 6 views
4

J'écris un ensemble de classes de collection pour différents types d'arbres. Je fais cela en tant qu'exercice d'apprentissage et j'espère aussi que cela s'avérera utile. Je veux vraiment le faire de la bonne façon et donc j'ai lu Effective Java et j'ai aussi regardé la façon dont Joshua Bloch a implémenté les classes de collection en regardant la source. Je semble avoir une bonne idée de ce qui se fait, mais j'ai encore quelques choses à régler.Question de mise en œuvre impliquant l'implémentation d'une interface

J'ai une interface Node<T> et une classe AbstractNode<T> qui implémente l'interface Node. J'ai ensuite créé un GenericNode<T> (un nœud pouvant avoir 0 à n enfants, et cela fait partie d'une classe n -ary tree) qui étend AbstractNode<T> et implémente Node<T>. Cette partie était facile.

Ensuite, j'ai créé une interface Tree<T> et une classe AbstractTree<T> qui implémente l'interface Tree<T>. Après cela, j'ai commencé à écrire une classe GenericTree<T> qui s'étend AbstractTree<T> et implémente Tree<T>. C'est là que j'ai commencé à avoir des problèmes. En ce qui concerne la conception, un GenericTree<T> ne peut être composé que de nœuds de type GenericTreeNode<T>. Cela inclut la racine. Dans mon interface Tree<T> je:

public interface Tree<T> { 

    void setRoot(Node<T> root); 

    Node<T> getRoot(); 

    List<Node<T>> postOrder(); 

    ... rest omitted ... 
} 

Et, AbstractTree<T> implémente cette interface:

public abstract class AbstractTree<T> implements Tree<T> { 

    protected Node<T> root; 

    protected AbstractTree() { 
    } 

    protected AbstractTree(Node<T> root) { 
     this.root = root; 
    } 

    public void setRoot(Node<T> root) { 
     this.root = root; 
    } 

    public Node<T> getRoot() { 
     return this.root; 
    } 

    ... rest omitted ... 
} 

En GenericTree<T>, je peux avoir:

public GenericTree(Node<T> root) { 
    super(root); 
} 

Mais ce que cela signifie est que vous pouvez créer un arbre générique en utilisant un sous-type de Node<T>. Vous pouvez également définir la racine d'un arbre à n'importe quel sous-type de Node<T>. Je veux pouvoir limiter le type du noeud au type de l'arbre qu'il peut représenter. Pour résoudre ce problème, je peux le faire:

public GenericTree(GenericNode<T> root) { 
    super(root); 
} 

Cependant, setRoot accepte encore un paramètre de type Node<T>. Ce qui signifie qu'un utilisateur peut toujours créer un arbre avec le mauvais type de nœud racine. Comment puis-je appliquer cette contrainte? La seule façon que je peux penser à faire est soit:

  • Faire un qui limite la vérification à l'exécution. Je ne suis pas un grand fan de ça.
  • Supprimez setRoot de l'interface et demandez à la classe de base d'implémenter cette méthode. Cela signifie que cela ne fait pas partie du contrat et que quiconque veut créer un nouveau type d'arbre doit se rappeler de mettre en œuvre cette méthode.

Y a-t-il un meilleur moyen?

La deuxième question que j'ai concerne le type de retour de postOrder qui est List<Node<T>>. Cela signifie que si un utilisateur fonctionne sur un objet GenericTree<T> et appelle postOrder, il reçoit une liste composée d'objets Node<T>.Cela signifie que lors de l'itération (à l'aide d'une construction foreach), ils auraient effectué une conversion explicite en GenericNode<T> s'ils voulaient utiliser des méthodes définies uniquement dans cette classe. Je n'aime pas avoir à imposer ce fardeau à l'utilisateur. Quelles sont mes options dans ce cas? Je ne peux penser à supprimer la méthode de l'interface et que la sous-classe implémente cette méthode en s'assurant qu'elle renvoie une liste de sous-type approprié de Node<T>. Cependant, ceci le supprime encore du contrat et c'est n'importe qui qui veut créer un nouveau type d'arbre doit se rappeler d'implémenter cette méthode. Y a-t-il un meilleur moyen?

+0

Etes-vous sûr que vous avez besoin 'méthode setRoot' du tout? Je me souviens qu'un conseil d'Effective Java est de "favoriser l'immuabilité". –

+0

@Alexander C'est vrai. C'est en fait une autre idée à laquelle je pensais - si 'setRoot' est vraiment nécessaire. Je commence à m'y pencher * pas * étant nécessaire. Qu'en est-il de la deuxième question? –

Répondre

4

Je pense que vous mettez un chariot avant un cheval. Implémentez quelques instances concrètes de Tree<T> et Node<T>. Seulement après cela analyser quelle implémentation ils ont en commun et seulement après cela implémentez vos classes Abstract si elles ont encore du sens à ce moment-là.

EDIT

Pour répondre à votre deuxième question:

Si simple, Node<T> ne coupe pas dans votre interface Tree, alors vous avez pas le choix, mais de déclarer un second paramètre à l'interface générique et mettre une frontière là-dessus, comme celui-ci

public interface Tree< 
    T, 
    TNode extends Node<T> 
> 
{ 

    void setRoot(TNode root); 

    TNode getRoot(); 

    List<TNode> postOrder(); 

    ... rest omitted ... 
} 

Puis AbstractTree

public abstract class AbstractTree< 
    T, 
    TNode extends Node<T> 
> implements Tree<T, TNode> { 

    protected TNode root; 

    protected AbstractTree(TNode root) { 
    this.root = root; 
    } 

    ... 
} 

Puis GenericTree

public class GenericTree<T> 
    extends AbstractTree< T, GenericNode<T> > 
{ 

    public GenericTree (GenericNode<T> root) 
    { 
    super(root); 
    } 

    @Override 
    public List< GenericNode<T> > postOrder () 
    { 
    ... 
    } 
    ... 
} 
+0

C'est ce que j'ai fait en premier, et j'ai identifié les méthodes communes indépendantes de l'implémentation. Tout ce qui me reste sont les cas dont je parle plus haut - à savoir essayer de comprendre comment retourner la liste des nœuds. –

+0

@Vivin. J'ai mis à jour mon message pour répondre à votre question –

+0

Parfait. C'est exactement ce que je cherchais. Merci beaucoup! –