Remplacer conditionnel par polymorphisme-nice en théorie mais pas pratique

"remplacer conditionnel par polymorphisme" est élégant seulement quand le type d'objet que vous faites commutateur/si l'instruction pour est déjà sélectionnée pour vous. Par exemple, j'ai une application web qui lit un paramètre de chaîne de requête appelé "action". L'Action peut avoir "vue", "modifier", "trier", et etc. valeur. Comment puis-je mettre en œuvre cela avec le polymorphisme? Je peux créer une classe abstraite appelée BaseAction, et en dériver ViewAction, EditAction, et SortAction. Mais je n'ai pas besoin d'un conditionnel pour décider quelle saveur de type BaseAction instantiate? Je ne vois pas comment vous pouvez remplacer entièrement les conditionnels par du polymorphisme. En fait, les conditionnels sont juste poussés au sommet de la chaîne.

EDIT:

public abstract class BaseAction
{
    public abstract void doSomething();
}

public class ViewAction : BaseAction
{
    public override void doSomething() { // perform a view action here... }
}

public class EditAction : BaseAction
{
    public override void doSomething() { // perform an edit action here... }
}

public class SortAction : BaseAction
{
    public override void doSomething() { // perform a sort action here... }
}


string action = "view";  // suppose user can pass either "view", "edit", or "sort" strings to you.
BaseAction theAction = null;

switch (action)
{
    case "view":
        theAction = new ViewAction();
        break;

    case "edit":
        theAction = new EditAction();
        break;

    case "sort":
        theAction = new SortAction();
        break;
}

theAction.doSomething();    // So I don't need conditionals here, but I still need it to decide which BaseAction type to instantiate first. There's no way to completely get rid of the conditionals.
22
demandé sur thkala 2011-02-01 22:08:45

9 réponses

vous avez raison - "les conditionnels sont poussés vers le haut de la chaîne" - mais il n'y a pas de "juste" à ce sujet. C'est très puissant. Comme le dit @thkala, vous faites juste le choix une fois; à partir de là, l'objet sait comment faire ses affaires. L'approche que vous décrivez - BaseAction, ViewAction et le reste - est une bonne façon de procéder. Essayez-le et voyez à quel point votre code devient plus propre.

quand vous avez une méthode d'usine qui prend un chaîne comme "View" et renvoie une Action, et vous appelez cela, vous avez isolé votre conditionnalité. C'est très bien. Et vous ne pouvez pas apprécier le pouvoir tant que vous n'avez pas essayé - alors tentez votre chance!

25
répondu Carl Manaster 2011-02-01 19:29:01

même si la dernière réponse date d'un an, j'aimerais faire quelques commentaires sur ce sujet.

Réponses D'Examen

je suis d'accord avec @CarlManaster codage switch déclaration Une fois pour éviter tous les problèmes bien connus de traitement de code dupliqué, dans ce cas impliquant des conditionnels (certains d'entre eux mentionnés par @thkala).

Je ne crois pas que l'approche proposée par @KonradSzałwiński ou @AlexanderKogtenkov convienne ce scénario pour deux raisons:

tout d'Abord, le problème que vous avez décrit, vous n'avez pas besoin de modifier dynamiquement la correspondance entre le nom d'une action et l'exemple d'une action qui le manipule.

remarquez que ces solutions permettent de faire cela (en assignant simplement un nom d'action à une nouvelle instance d'action), alors que la solution statique basée sur switch ne le fait pas (les mappings sont codés). Aussi, vous aurez toujours besoin d'une conditionnelle pour vérifier si une clé est définie dans le table de mappage, si non une action devrait être prise (le default partie d'une instruction switch).

Deuxièmement, dans cet exemple, dictionaries sont vraiment des implémentations cachées de l'instruction switch. De plus, il pourrait être plus facile de lire/comprendre l'instruction switch avec la clause par défaut que de devoir exécuter mentalement le code qui renvoie l'objet handling à partir de la table de correspondance, y compris la manipulation d'une clé non définie.

il y a un moyen de vous débarrasser de toutes les conditions, y compris la déclaration de changement:

Suppression de l'instruction switch (n'utilisez pas le conditionnel)

comment créer le bon objet d'action à partir du nom d'action?

je vais être indépendant de la langue, de sorte que cette réponse n'a pas obtenir de l' depuis longtemps, mais l'astuce consiste à réaliser les classes sont des objets trop.

si vous avez déjà défini une hiérarchie polimorphique, cela n'a aucun sens de faire référence à une sous-classe concrète de BaseAction: pourquoi ne pas lui demander de renvoyer la bonne instance traitant une action par son nom?

qui est habituellement implémentée par la même instruction de commutation que vous avez écrite (par exemple, une méthode d'usine)... mais que penser de ceci:

public class BaseAction {

    //I'm using this notation to write a class method
    public static handlingByName(anActionName) {
        subclasses = this.concreteSubclasses()

        handlingClass = subclasses.detect(x => x.handlesByName(anActionName));

        return new handlingClass();
    }
}

alors, que fait cette méthode?

tout d'Abord, récupère toutes les sous-classes concrètes de ce (qui pointe vers BaseAction). Dans votre exemple, vous obtiendriez une collection avec ViewAction,EditAction et SortAction.

notez que j'ai dit des sous-classes de béton, pas toutes les sous-classes. Si la hiérarchie est plus profonde, les sous-classes concrètes seront toujours celles du bas de la hiérarchie (leaf). C'est parce qu'ils sont les seuls censés ne pas être abstraits et fournir une mise en œuvre réelle.

Deuxièmement, obtenez la première sous-classe qui répond si oui ou non il peut gérer une action par son nom (j'utilise une notation lambda/closure flavored). Échantillon la mise en œuvre de l' handlesByName méthode de classe pour ViewAction ressemble à:

public static class ViewAction {

    public static bool handlesByName(anActionName) {
        return anActionName == 'view'
    }

}

Troisièmement, nous envoyons le message nouveau à la classe qui gère l'action, en créant effectivement une instance de celle-ci.

bien sûr, vous devez traiter avec le cas où aucune de la sous-classe ne traite l'action par son nom. De nombreux langages de programmation, y compris Smalltalk et Ruby, permet de passer la méthode detect une deuxième lambda/fermeture qui ne sera évaluée que si aucun des les sous-classes correspondent aux critères. En outre, vous devrez traiter le cas plus d'une sous-classe traite l'action par son nom (probablement, l'une de ces méthodes a été codée de la mauvaise manière).

Conclusion

un avantage de cette approche est que les nouvelles actions peuvent être supportées en écrivant (et non en modifiant) le code existant: il suffit de créer une nouvelle sous-classe de BaseAction et la mise en oeuvre du handlesByName méthode de classe correctement. Il soutient efficacement l'ajout d'un nouvelle fonctionnalité en ajoutant un nouveau concept, sans modifier l'impémentation existante. Il est clair que, si la nouvelle caractéristique nécessite une nouvelle méthode polimorphique pour être ajoutée à la hiérarchie, des changements seront nécessaires.

en outre, vous pouvez fournir aux développeurs en utilisant votre système feedback: "l'action fournie n'est pas traitée par une sous-classe de BaseAction, s'il vous plaît créer une nouvelle sous-classe et mettre en œuvre les méthodes abstraites". Pour moi, le fait que le modèle lui-même vous dit ce qui ne va pas (au lieu de essayer d'exécuter mentalement une table de recherche) ajoute de la valeur et des directives claires sur ce qui doit être fait.

Oui, cela peut sembler la conception du projet. S'il vous plaît garder l'esprit ouvert et se rendre compte que si une solution est trop conçu ou non doit faire, entre autres choses, avec la culture de développement du langage de programmation particulier que vous utilisez. Par exemple, les gars de .NET ne l'utiliseront probablement pas parce que le .NET ne vous permet pas de traiter les classes comme des objets réels, alors que dans l'autre main, cette solution est utilisée dans les cultures Smalltalk/Ruby.

enfin, utilisez le bon sens et le goût pour déterminer à l'avance si une technique particulière résout vraiment votre problème avant de l'utiliser. C'est tentant, Oui, mais tous les compromis (culture, ancienneté des développeurs, résistance au changement, ouverture d'esprit, etc.) doivent être évalués.

8
répondu nick2083 2014-02-15 05:41:53

quelques choses à considérer:

  • vous n'instanciez chaque objet que une fois. Une fois que vous faites cela, plus de conditionnels devraient être nécessaires en ce qui concerne son type.

  • même dans des cas ponctuels, combien de conditionnels vous débarrasseriez-vous si vous utilisiez des sous-classes? Le Code utilisant des conditionnels comme celui-ci est tout à fait enclin à être exactement la même conditionnel, encore et encore et encore...

  • Ce qui se passe quand vous avez besoin d'un foo valeur de l'Action dans le futur? Combien de places devrez-vous modifier?

  • Que faire si vous avez besoin d'un bar qui n'est que légèrement différent de foo? Avec les cours, vous héritez juste BarActionFooAction, en remplaçant la seule chose que vous devez modifier.

à long terme, le code orienté objet est généralement plus facile à maintenir que le code de procédure - les gourous n'aurez pas de problème avec, mais pour le reste d'entre nous, il une différence.

7
répondu thkala 2011-02-01 19:26:21

Votre exemple ne nécessite pas de polymorphisme, et il ne peut être conseillé. L'idée originale de remplacer la logique conditionnelle par l'expédition polymorphe est cependant valable.

Voici la différence: dans votre exemple, vous avez un petit ensemble fixe (et prédéterminé) d'actions. En outre, les actions ne sont pas fortement liées dans le sens où les actions "trier" et "éditer" ont peu en commun. Le polymorphisme est en train de surconstruire votre solution.

d'autre part, si vous ont beaucoup d'objets avec des comportements pour une notion commune, le polymorphisme est exactement ce que vous voulez. Par exemple, dans un jeu, il peut y avoir beaucoup d'objets que le joueur peut "activer", mais chacun réagit différemment. Vous pourriez mettre en œuvre cela avec des conditions complexes (ou plus probablement une déclaration de commutateur), mais le polymorphisme serait mieux. Le polymorphisme vous permet d'introduire de nouveaux objets et comportements qui ne faisaient pas partie de votre conception originale (mais s'inscrivent dans son éthos).

dans votre exemple, in serait encore une bonne idée d'abstraire sur les objets qui soutiennent les actions view/edit/sort, mais peut-être pas abstraire ces actions elles-mêmes. Voici un test: voudriez-vous jamais mettre ces actions dans une collection? Probablement pas, mais vous pourriez avoir une liste des objets qui les soutiennent.

3
répondu Kevin A. Naudé 2011-02-01 19:44:23

il existe plusieurs façons de traduire une chaîne de caractères à un objet d'un type donné et un conditionnel en est certainement une. En fonction du langage d'implémentation, il peut également être possible d'utiliser une instruction switch qui permet de spécifier les chaînes attendues comme index et de créer ou de récupérer un objet du type correspondant. Il y a toujours une meilleure façon de le faire.

une table de recherche peut être utilisée pour mapper les chaînes d'entrée objets:

action = table.lookup (action_name); // Retrieve an action by its name
if (action == null) ...              // No matching action is found

le code d'initialisation s'occuperait de créer les objets requis, par exemple

table ["edit"] = new EditAction ();
table ["view"] = new ViewAction ();
...

C'est le schéma de base qui peut être étendu pour couvrir plus de détails, tels que les arguments supplémentaires des objets d'action, la normalisation des noms d'action avant de les utiliser pour la recherche de table, le remplacement d'une table par un tableau simple en utilisant des entiers au lieu de chaînes pour identifier les actions demandées, etc.

3
répondu Alexander Kogtenkov 2011-02-01 19:58:40

j'ai pensé à ce problème probablement plus que les autres développeurs que j'ai rencontrés. La plupart d'entre eux sont totalement inconscients des coûts de maintien longtemps imbriqués If-else statement ou switch cases. Je comprends parfaitement votre problème dans l'application de la solution appelée "remplacer conditionnel avec polymorphisme" dans votre cas. Vous avez remarqué avec succès que le polymorphisme fonctionne aussi longtemps que l'objet est déjà sélectionné. Il a également été dit dans ce traité que ce problème peut être réduit à l'association [clé] -> [classe.] Voici par exemple AS3 implémentation de la solution.

private var _mapping:Dictionary;
private function map():void
{
   _mapping["view"] = new ViewAction();
   _mapping["edit"] = new EditAction();
   _mapping["sort"] = new SortAction();
}

private function getAction(key:String):BaseAction
{
    return _mapping[key] as BaseAction;
} 

en cours d'Exécution qui vous ressemble:

public function run(action:String):void
{
   var selectedAction:BaseAction = _mapping[action];
   selectedAction.apply();
}

En ActionScript3 il y a une fonction globale appelée getDefinitionByName(key:String):Classe. L'idée est d'utiliser votre clé de valeurs pour faire correspondre les noms des classes qui représentent la solution à votre condition. Dans votre cas, vous devez changer "view" en "ViewAction", "edit" en "EditAction" et "sort"en " Triation". Il n'y a pas besoin de mémoriser tout ce qui utilise des tables de recherche. La fonction exécuter ressemblera à ceci:

public function run(action:Script):void
{
   var class:Class = getDefintionByName(action);
   var selectedAction:BaseAction = new class();
   selectedAction.apply();
}

malheureusement vous perdez la vérification de compilation avec cette solution, mais vous obtenez la flexibilité pour ajouter de nouvelles actions. Si vous créez une nouvelle clé, la seule chose que vous devez faire est de créer une classe qui va la traiter.

veuillez laisser un commentaire même si vous n'êtes pas d'accord avec moi.

2
répondu Konrad Szałwiński 2012-06-14 15:30:22
public abstract class BaseAction
{
    public abstract void doSomething();
}

public class ViewAction : BaseAction
{
    public override void doSomething() { // perform a view action here... }
}

public class EditAction : BaseAction
{
    public override void doSomething() { // perform an edit action here... }
}

public class SortAction : BaseAction
{
    public override void doSomething() { // perform a sort action here... }
}


string action = "view"; // suppose user can pass either
                        // "view", "edit", or "sort" strings to you.
BaseAction theAction = null;

switch (action)
{
    case "view":
        theAction = new ViewAction();
        break;

    case "edit":
        theAction = new EditAction();
        break;

    case "sort":
        theAction = new SortAction();
        break;
}

theAction.doSomething();

donc je n'ai pas besoin de conditionnels ici, mais j'en ai encore besoin pour décider quel type de BaseAction instancier en premier. Il n'y a aucun moyen de se débarrasser complètement des conditionnels.

1
répondu Charles 2011-02-01 22:20:38

le Polymorphisme est une méthode de liaison. C'est un cas particulier de chose connue sous le nom de "modèle D'objet". Les modèles d'objets sont utilisés pour manipuler des systèmes complexes, comme le circuit ou le dessin. Considérez quelque chose de stocké/marshalled it format texte: item "A", connecté à l'item "B" et "C". Maintenant, vous devez savoir ce qui est connecté à A. Un gars peut dire que je ne vais pas créer un modèle objet pour cela parce que je peux le Compter en passant, simple-pass. Dans ce cas, vous avez peut-être droit, vous pouvez sortir sans modèle d'objet. Mais que faire si vous avez besoin de faire beaucoup de manipulations complexes avec un design importé? Le manipulerez-vous au format texte ou en envoyant des messages en invoquant des méthodes java et en référençant des objets java est plus pratique? C'est pourquoi il a été mentionné que vous ne devez faire la traduction qu'une seule fois.

0
répondu Val 2012-06-02 13:13:57

Vous pouvez stocker la chaîne de caractères et le type d'action correspondant quelque part dans hash map.

public abstract class BaseAction
{
    public abstract void doSomething();
}

public class ViewAction : BaseAction
{
    public override void doSomething() { // perform a view action here... }
}

public class EditAction : BaseAction
{
    public override void doSomething() { // perform an edit action here... }
}

public class SortAction : BaseAction
{
    public override void doSomething() { // perform a sort action here... }
}


string action = "view"; // suppose user can pass either
                        // "view", "edit", or "sort" strings to you.
BaseAction theAction = null;

theAction = actionMap.get(action); // decide at runtime, no conditions
theAction.doSomething();
0
répondu Usman Riaz 2016-03-21 05:20:23