2010-09-08 25 views
5

Supposons que j'ai une hiérarchie de classes en Java:modèle de conception de répartition?

interface Item { ... }; 
class MusicBox implements Item { ... }; 
class TypeWriter implements Item { ... }; 
class SoccerBall implements Item { ... }; 

et j'ai une autre classe dans le même package:

class SpecialItemProcessor { 
    public void add(Item item) 
    { 
     /* X */ 
    } 
} 

où je veux faire quelque chose de différent pour chaque type d'élément, mais je ne ne veulent pas définir cette action dans les différentes classes Item (MusicBox, TypeWriter, SoccerBall).

Une façon de gérer est:

class SpecialItemProcessor { 
    public void add(Item item) 
    { 
     if (item instanceof MusicBox) 
     { 
      MusicBox musicbox = (MusicBox)item; 
      ... do something ... 
     } 
     else if (item instanceof MusicBox) 
     { 
      TypeWriter typewriter = (TypeWriter)item; 
      ... do something ... 
     } 
     else if (item instanceof SoccerBall) 
     { 
      SoccerBall soccerball = (SoccerBall)item; 
      ... do something ... 
     } 
     else 
     { 
      ... do something by default ... 
     } 
    } 
} 

Cela fonctionne, mais il semble vraiment maladroit. Y a-t-il une meilleure façon de faire cela, quand je connais des cas spéciaux? (évidemment si Item contient une méthode doSomethingSpecial alors je peux simplement appeler la méthode de cet article sans se soucier de quel type il est, mais si je ne veux pas que cette différenciation se produise dans l'article lui-même, comment traiter?)

+0

Pas une réponse Je sais, mais dans ActionScript 3 apparemment, vous pouvez instancier une classe à partir d'une chaîne (ie, 'com.djw.MusicBox' peut instancier un MusicBox), est ce genre de chose possible en Java peut-être? Juste une suggestion! – danjah

+0

@dan: 'Classe # forName()'. Je doute cependant de la valeur ici. – BalusC

Répondre

2

Je pense que je vais utiliser l'idée de l'inversion du contrôle et de la visitor pattern:

interface Item { 
    public void accept(Visitor visitor); 
    ... 

    public interface Visitor { 
     public void visit(Item item); 
    } 
} 


class MusicBox implements Item { 
    public interface Visitor extends Item.Visitor { 
     public void visitMusicBox(MusicBox item); 
    } 
    ... 
    @Override public accept(Item.Visitor visitor) 
    { 
     if (visitor instanceof MusicBox.Visitor) 
     { 
      ((MusicBox.Visitor)visitor).visitMusicBox(this); 
     } 
    } 
} 

class TypeWriter implements Item { 
    public interface Visitor extends Item.Visitor { 
     public void visitTypeWriter(TypeWriter item); 
    } 
    ... 
    @Override public accept(Item.Visitor visitor) 
    { 
     if (visitor instanceof TypeWriter.Visitor) 
     { 
      ((TypeWriter.Visitor)visitor).visitTypeWriter(this); 
     } 
    } 
} 

class SoccerBall implements Item { 
    public interface Visitor extends Item.Visitorr { 
     public void visitSoccerBall(SoccerBall item); 
    } 
    ... 
    @Override public accept(Item.Visitor visitor) 
    { 
     if (visitor instanceof SoccerBall.Visitor) 
     { 
      ((SoccerBall.Visitor)visitor).visitSoccerBall(this); 
     } 
    } 
} 

puis effectuez les opérations suivantes, qui réduit au moins la instanceof à un chèque par add() appel :

class SpecialItemProcessor 
    implements 
     MusicBox.Visitor, 
     TypeWriter.Visitor, 
     SoccerBall.Visitor, 
     Item.Visitor 
{ 
    public void add(Item item) 
    { 
     item.accept(this); 
    } 
    @Override public void visitMusicBox(MusicBox item) 
    { 
     ... 
    } 
    @Override public void visitTypeWriter(TypeWriter item) 
    { 
     ... 
    } 
    @Override public void visitSoccerBall(SoccerBall item) 
    { 
     ... 
    } 
    @Override public void visit(Item item) 
    { 
     /* not sure what if anything I should do here */ 
    } 
} 
0

Vous pouvez créer un modèle de pont pour Item, dans lequel l'autre côté était les processus associés à faire lorsque add() est appelé. Vous pouvez également ajouter une méthode d'usine au mélange.

class SpecialItemProcessor { 
    public void add(Item item) 
    { 
    Process p = Item.createCorrespondingProcessor(p); 
    p.doWhenAddin(); 
    } 
} 

Espérons que cela aide.

+0

Quelle est cette syntaxe? Et, comment utilisez-vous 'p' avant de l'assigner initialement? – jjnguy

+0

Ce n'est pas PHP ... Version correcte: 'p.doWhenAddin()' – TheLQ

+0

Désolé, j'ai finalement ajouté une syntaxe C++. Ce code source est Java. La méthode de création create correspondante est une méthode usine qui crée la classe appropriée correspondant à la hiérarchie de classe des processeurs, liée à la hiérarchie de classes d'éléments. – Baltasarq

7

En Java, vous pouvez effectuer plusieurs répartitions avec un modèle visiteur (similaire). Les implémentations Item n'ont pas besoin de contenir la logique de traitement, elles ont juste besoin d'un type de méthode accept().

public interface Item { 
/** stuff **/ 

void processMe(ItemProcessor processor); 

} 

public interface ItemProcessor { 

void process(MusicBox box); 

void process(SoccerBall ball); 

//etc 

} 

public class MusicBox implements Item { 

    @Override 
    public void processMe(ItemProcessor processor) { 
    processor.process(this); 
    } 

} 

public class ItemAddingProcessor implements ItemProcessor { 

    public void add(Item item) { 
    item.processMe(this); 
    } 

    @Override 
    public void process(MusicBox box) { 
    //code for handling MusicBoxes 
    //what would have been inside if (item instanceof MusicBox) {} 
    } 

//etc 
} 
+0

Hmm. Donne-moi quelques idées, mais dans votre code l'élément d'interface a maintenant une dépendance sur ItemProcessor qui a une dépendance sur les classes MusicBox et SoccerBall (donc pas sûr si cela compilerait même) –

+0

Il compilerait, vous pouvez avoir des dépendances croisées entre cours en Java. Cependant, vous ne pouvez pas avoir de dépendances de corss entre .jars, donc cela ne fonctionnera pas si Item et ItemProcessor sont dans des .jars différents. – amorfis

+0

@Jason: oui, il compile :) C'est juste une façon de renverser les choses afin qu'au lieu d'avoir une méthode qui essaie de comprendre comment gérer le type particulier d'élément, vous avez les différents types d'éléments qui se passent à une méthode qui a leur manipulation. Le couplage de compilation peut être un avantage, si vous avez ce type d'instance if-else dans beaucoup d'endroits autour de l'application, il est beaucoup plus facile de manquer une mise à jour lorsque vous créez un nouveau type d'élément, que lorsque tous implémentent une interface cela a changé. – Affe

0

Pourquoi ne pas définir une fonction de rappel sur l'interface Item? Ensuite, dans chaque classe qui implémente Item, tel que MusicBox, elle doit implémenter la fonction de rappel. Puis vous pouvez créer un répartiteur, que vous nommez "SpecialItemProcessor".

public SpecialItemProcessor { 
    private final Item _item; 

    public SpecialItemProcessor(Item item) { 
    _item = item; 
    } 

    public dispatch() { 
    _item.onCallBack() 
    } 
} 

Et puis, dans la classe Client qui contient le SpecialItemProcessor pourrait simplement appeler la méthode, comme:

public void XXXX() { 
    .... 
    SpecialItemProcessor specialItemProcessor = new SpecialItemProcessor(new MusicBox()); 
    specialItemProcessor.dispatch(); 
    .... 
} 

En fait, en C++, c'est la liaison dynamique. Et c'est pourquoi la classe abstraite pure existe ...

+1

Lisez la question: "où je veux faire quelque chose de différent pour chaque type d'élément, ** mais je ne veux pas définir cette action dans les différentes classes d'objets ** (MusicBox, TypeWriter, SoccerBall). " –