2017-01-25 3 views
3

Ceci est probablement un mauvais exemple, mais s'il vous plaît, travaillez avec. J'ai une super classe Cake et deux sous-classes: ExampleCake et SaleCake. J'ai aussi un Baker qui peut faire de l'ingénierie inverse d'un gâteau pour en faire une copie, et sait quoi en faire par la suite.Comment faire une classe d'utilité gérer les différences dans les sous-classes?

(Tout le code pseudo)

public class Cake{ 
    radius; 
    height; 
    ingredients; 
    color; 
    name; 
} 

public class ExampleCake extends Cake{ 
    shelfLocation; 
} 

public class SaleCake extends Cake{ 
    owner; 
} 

public class Baker{ 
    Cake bakeCake(Cake); 
    Cake bakeSaleCake(Cake, Owner); 
    Cake bakeExampleCake(Cake, Location); 

    void handleCake(Cake); 
} 

Le Baker a besoin de savoir comment gérer gâteaux génériques, ExampleCakes et SaleCakes. Donc, ses fonctions bakeCake ressembler à quelque chose comme ceci:

Cake bakeCake(cake){ 
    newCake = cake.copy(); 
    mixIngredients(newCake); 
    putCakeInOven(newCake); 
    putIcing(newCake); 
    return cake; 
} 

Cake bakeSaleCake(cake, owner){ 
    newCake = bakeCake(cake); 
    newCake.setOwner(owner); 
    return newCake; 
} 

Cake bakeExampleCake(cake, location){ 
    newCake = bakeCake(cake); 
    newCake.setLocation(location); 
    return newCake; 
} 

void handleCake(cake){ 
    if(cake instanceof ExampleCake) 
     putOnShelf((ExampleCake)cake); 

    else if(cake instanceof SaleCake) 
     giveToCustomer((SaleCake)cake); 

    else 
     putOnTable(cake); 
} 

Mon problème est que le Baker est codé en dur pour ne traiter certains types de gâteaux. Si de nouveaux types de gâteaux arrivent, il ne peut pas les manipuler. Y a-t-il une façon propre et générique de gérer cela, ou est-ce que le pseudo-code que j'ai ci-dessus est assez "bon" (comme dans, cela ne blesse pas vos yeux ou votre cœur)?

Merci.

+3

On dirait que le modèle clonable, il suffit de laisser les classes 'Cake' implémenter la méthode pour se copier. –

+0

Cela a du sens en ce qui concerne la création de gâteaux. J'ai oublié d'ajouter la partie "handleCake", que j'ai ajouté à la fin du pseudo-code. – GuitarStrum

+0

La partie handleCake pourrait probablement être refactorisée en utilisant le modèle de visiteur. – Calculator

Répondre

1

Pour la copie des gâteaux, vous pouvez utiliser des constructeurs de copie. De cette façon, il appartient au gâteau spécifique comment il se copie. Pour gérer des gâteaux arbitraires, vous pouvez utiliser le modèle de visiteur. Via un visiteur, vous pouvez "demander" un gâteau sur son type au lieu de vérifier le type avec instanceof.

Le code pourrait ressembler à ceci (Vous voulez avoir d'autres constructeurs de gâteau, en plus de la copie-constructeurs):

// classe gâteau

public class Cake{ 
    int radius; 
    int height; 

    public Cake(Cake cake){ 
     this.radius = cake.radius; 
     this.height = cake.height; 
    } 

    <R> R accept(CakeVisitor<? extends R> visitor){ 
     return visitor.visit(this); 
    } 

} 

// ExampleCake classe

public class ExampleCake extends Cake{ 
    String shelfLocation; 

    public ExampleCake(Cake cake, String shelfLocation){ 
     super(cake); 
     this.shelfLocation = shelfLocation; 
    } 

    <R> R accept(CakeVisitor<? extends R> visitor){ 
     return visitor.visit(this); 
    } 

} 

// SaleCake classe

public class SaleCake extends Cake{ 
    String owner; 

    public SaleCake(Cake cake, String owner){ 
     super(cake); 
     this.owner = owner; 
    } 

    <R> R accept(CakeVisitor<? extends R> visitor){ 
     return visitor.visit(this); 
    } 

} 

// Interface visiteur

public interface CakeVisitor<R> { 

    R visit(Cake cake); 
    R visit(SaleCake cake); 
    R visit(ExampleCake cake); 

} 

// Baker classe

public class Baker { 
    private final CakeVisitor<Void> cakeHandler = new CakeVisitor<Void>(){ 

     @Override 
     public Void visit(Cake cake) { 
      putOnTable(cake); 
      return null; 
     } 

     @Override 
     public Void visit(SaleCake cake) { 
      giveToCustomer(cake); 
      return null; 
     } 

     @Override 
     public Void visit(ExampleCake cake) { 
      putOnShelf(cake); 
      return null; 
     } 

    }; 

    Cake bakeCake(Cake cake){ 
     return processedCake(new Cake(cake)); 
    } 

    SaleCake bakeSaleCake(Cake cake, String owner){ 
     return processedCake(new SaleCake(cake, owner)); 
    } 

    ExampleCake bakeExampleCake(Cake cake, String location){ 
     return processedCake(new ExampleCake(cake, location)); 
    } 

    void handleCake(Cake cake){ 
     cake.accept(cakeHandler); 
    } 

    private <C extends Cake> C processedCake(C cake){ 
     mixIngredients(cake); 
     putCakeInOven(cake); 
     putIcing(cake); 
     return cake; 
    } 
} 

Maintenant, si vous voulez gérer un nouveau type de gâteau avec votre Baker, il sera mis sur la table par défaut, à moins vous fournissez une méthode de visite qui accepte spécifiquement les objets du nouveau type de gâteau. Je pense que vous ne pouvez pas vraiment le rendre plus générique, parce que quand vous avez un nouveau type de gâteau, vous voulez le gérer différemment des autres types de gâteaux et le nouveau code qui le gère doit aller quelque part. Un code comme ne devrait pas être placé à l'intérieur d'une classe de gâteau, parce qu'un gâteau ne sait pas comment il est mis sur une table - le boulanger sait -> donc le modèle de visiteur.

+0

C'est plutôt joli. Cependant, il ne compile pas à la méthode privée C traitéeCake (gâteau C). J'ai créé mixIngrediant() et dit "se référant au type C manquant". Qu'est-ce que la classe "C" ou quelle syntaxe exactement? Merci d'avoir écrit ça. –

+0

@NickZiebert La méthode 'mixIngredients' et les autres méthodes, que je n'ai pas incluses, doivent accepter un gâteau de type' Cake'. Ensuite, il compile puisque le paramètre de type générique C est défini comme étant un descendant du gâteau (les descendants de 'Cake' peuvent être utilisés lorsque la superclasse' Cake' est requise). 'processedCake' renvoie le gâteau qui lui est passé en préservant son type statique. J'ai essayé d'enregistrer quelques lignes dans les méthodes de cuisson. – Calculator

0

Une bonne conception consiste à identifier les comportements qui changent et ceux qui restent les mêmes, puis à séparer ces deux groupes. Ce qui change ici, c'est le comportement du Baker pour chaque gâteau. Nous voulons donc créer une nouvelle classe qui puisse encapsuler son comportement changeant. C'est comme ça qu'on fait. Créez une interface appelée BakerBehavior avec une seule méthode appelée doBehavior(). Ensuite, créez une classe appelée PutOnShelfBehavior et une autre appelée GiveToCustomerBehavior, les deux implémentant BakerBehavior. Remplacer la méthode doBehavior(). Maintenant, dans chacune de vos classes de gâteau, vous pouvez créer un objet PutOnShelfBehavior ou un objet GiveToCustomerBehavior. Votre superclasse gâteau aura une méthode acte() qui ressemblera à ceci:

//Constructor { 
myBehaviorObject=new PutOnShelfBehavior(); 
} 

public void act() { 
    myBehaviorObject.doBehavior(); 
} 

Maintenant, dans votre classe Baker, c'est ce que votre méthode de handleCake ressemblera:

private void handleCake(Cake cake) { 
    cake.act(); 
} 

Je ne Je ne vois pas d'autre moyen de le faire. Cela a l'avantage de A) Ne pas avoir à coder en dur les types de gâteaux dans cette classe handleCake, B) Vous n'avez pas besoin de répéter de code (Peut-être avez-vous deux types de gâteaux différents, par exemple). C) Chaque gâteau ne sait rien du Baker, il sait juste qu'il va être mis sur une étagère ou autre chose. J'espère que quelqu'un peut me dire s'il y a une meilleure façon de le faire, je suis intrigué par cette question.