Question

J'essaie de comparer les critères de comparaison. Celles simples comme «entre» et «inArray» ou «plus grand que». J'utilise polymorphisme pour ces classes. Une méthode commune à l’interface de compareCriteria est 'matchCompareCriteria'.

Ce que j'essaie d'éviter, c'est de demander à chaque classe de vérifier le type de critère de comparaison auquel ils doivent correspondre. Par exemple, l’objet inArray vérifiera si matchCompareCriteria reçoit un objet inArray, sinon il renverra false, dans le cas où il saura comparer.

Peut-être que instanceof est parfaitement légitime dans ce cas (les objets se connaissent eux-mêmes) mais je cherche quand même des moyens de l'éviter. Des idées?

exemple de pseudo-code:

betweenXandY = create new between class(x, y)
greaterThanZ = create new greaterThan class(z)
greaterThanZ.matchCompareCriteria(betweenXandY)

si X et Y sont supérieurs à Z, la valeur renvoyée sera vraie.

modifier:

1) instanceof est ce que je vois, pour le moment, comme le requiert la méthode matchCompareCriteria. Je voudrais m'en débarrasser

2) matchCompareCritera vérifie si un critère de comparaison est contenu par un autre. Si toutes les valeurs possibles de l'une sont contenues par l'autre, la valeur renvoyée est vraie. Pour de nombreuses combinaisons de critères de comparaison, il n’a même pas de sens de les comparer pour qu’elles renvoient une valeur fausse (comme entre Alpha et entre Num, ce serait incompatible).

Était-ce utile?

La solution

Le problème que vous décrivez s'appelle double envoi . Le nom vient du fait que vous devez choisir le bit de code à exécuter (dispatch) en fonction du type de deux objets (d'où: double).

Normalement, dans OO, il y a une seule répartition - l'appel d'une méthode sur un objet entraîne l'exécution de l'implémentation de la méthode de cet objet.

Dans votre cas, vous avez deux objets et l'implémentation à exécuter dépend du type des deux objets. Fondamentalement, il y a un couplage impliqué par ceci qui "se sent mal" lorsque vous avez précédemment traité uniquement avec des situations OO standard. Mais ce n’est pas vraiment faux: c’est un peu en dehors du problème des fonctionnalités de base d’OO qui sont directement adaptées à la résolution.

Si vous utilisez un langage dynamique (ou un langage de type statique avec réflexion, suffisamment dynamique pour cela), vous pouvez l'implémenter avec une méthode de répartition dans une classe de base. En pseudo-code:

class OperatorBase
{
    bool matchCompareCriteria(var other)
    {
        var comparisonMethod = this.GetMethod("matchCompareCriteria" + other.TypeName);
        if (comparisonMethod == null)
            return false;

        return comparisonMethod(other);
    }
}

Ici, j'imagine que le langage a une méthode intégrée dans chaque classe appelée GetMethod qui me permet de rechercher une méthode par son nom, ainsi qu'une propriété TypeName sur chaque objet obtenu. moi le nom du type de l'objet. Donc, si l'autre classe est un GreaterThan et que la classe dérivée a une méthode appelée matchCompareCriteriaGreaterThan, nous appellerons cette méthode:

class SomeOperator : Base
{
    bool matchCompareCriteriaGreaterThan(var other)
    {
        // 'other' is definitely a GreaterThan, no need to check
    }
}

Il vous suffit donc d'écrire une méthode avec le nom correct et l'envoi se produit.

Dans un langage de type statique qui prend en charge la surcharge de méthodes par type d'argument, nous pouvons éviter d'avoir à inventer une convention de dénomination concaténée - par exemple, la voici en C #:

class OperatorBase
{
    public bool CompareWith(object other)
    {
        var compare = GetType().GetMethod("CompareWithType", new[] { other.GetType() });
        if (compare == null)
            return false;

        return (bool)compare.Invoke(this, new[] { other });
    }
}

class GreaterThan : OperatorBase { }
class LessThan : OperatorBase { }

class WithinRange : OperatorBase
{
    // Just write whatever versions of CompareWithType you need.

    public bool CompareWithType(GreaterThan gt)
    {
        return true;
    }

    public bool CompareWithType(LessThan gt)
    {
        return true;
    }
}

class Program
{
    static void Main(string[] args)
    {
        GreaterThan gt = new GreaterThan();
        WithinRange wr = new WithinRange();

        Console.WriteLine(wr.CompareWith(gt));
    }
}

Si vous deviez ajouter un nouveau type à votre modèle, vous devriez examiner chaque type précédent et vous demander s'il devait interagir avec le nouveau type. Par conséquent, chaque type doit définir une manière d'interagir avec tous les autres types - même si l'interaction correspond à une valeur par défaut très simple (telle que "ne rien faire sauf renvoyer true "). Même ce simple défaut représente un choix délibéré que vous devez faire. C’est dissimulé par le fait qu’il n’est pas nécessaire d’écrire explicitement un code pour le cas le plus courant.

Par conséquent, il peut être plus judicieux de capturer les relations entre tous les types dans une table externe, au lieu de les disperser autour de tous les objets. En centralisant, vous pourrez voir instantanément si vous avez oublié des interactions importantes entre les types.

Ainsi, vous pourriez avoir un dictionnaire / map / hashtable (quel que soit son nom dans votre langue) qui mappe un type sur un autre dictionnaire. Le deuxième dictionnaire mappe un deuxième type à la fonction de comparaison appropriée pour ces deux types. La fonction générale CompareWith utiliserait cette structure de données pour rechercher la fonction de comparaison appropriée à appeler.

La bonne approche dépend du nombre de types dans votre modèle.

Autres conseils

Puisque vous faites référence à instance of , je suppose que nous travaillons en Java ici. Cela pourrait vous laisser utiliser la surcharge. Considérons une interface appelée SomeInterface , qui possède une seule méthode:

public interface SomeInterface {
    public boolean test (SomeInterface s);
}

Maintenant, nous définissons deux classes (nommés intelligemment) qui implémentent SomeInterface : Some1 et Some2 . Some2 est ennuyeux: test renvoie toujours la valeur false. Mais Some1 annule la fonction test lorsqu'un Some2 :

public class Some1 implements SomeInterface {
    public boolean test (SomeInterface s) {
        return false;
    }

    public boolean test (Some2 s) {
        return true;
    }
}

Cela nous évite d’avoir une ligne après l’autre des instructions if à vérifier. Mais il y a une mise en garde. Considérez ce code:

Some1 s1 = new Some1 ();
Some2 s2 = new Some2 ();
SomeInterface inter = new Some2 ();

System.out.println(s1.test(s2));     // true
System.out.println(s2.test(s1));     // false
System.out.println(s1.test(inter));  // false

Voir ce troisième test? Même si inter est de type Some2 , il est traité comme un SomeInterface . La résolution de la surcharge est déterminée lors de la compilation en Java, ce qui pourrait la rendre complètement inutile.

Cela vous ramène à la case départ: utilisation de instanceof (qui est évalué au moment de l'exécution). Même si vous le faites de cette façon, c'est toujours un mauvais design. Chacune de vos classes doit connaître toutes les autres. Si vous décidez d’en ajouter un autre, vous devez revenir à tous ceux existants pour ajouter des fonctionnalités permettant de gérer la nouvelle classe. Cela devient horriblement impossible à maintenir, ce qui est un bon signe que la conception est mauvaise.

Une refonte s’impose, mais sans beaucoup d’informations, je ne peux pas vous donner un bon coup de pouce dans la bonne direction.

Vous devez créer une super classe ou une interface appelée Criteria. Ensuite, chaque sous-classe concrète implémentera l'interface Criteria. entre, plus que etc. sont des critères.

la classe Criteria spécifiera la méthode matchCompareCriteria qui accepte un critère. La logique réelle résidera dans les sous-classes.

Vous recherchez le modèle de stratégie de conception ou le modèle de conception de modèle.

Si je comprends bien, votre méthode repose sur la vérification de type. C'est assez difficile à éviter et le polymorphisme ne résout pas le problème. D'après votre exemple, inArray a besoin de pour vérifier le type du paramètre, car le comportement de la méthode en dépend . Vous ne pouvez pas faire cela par polymorphisme, ce qui signifie que vous ne pouvez pas mettre une méthode polymorphe sur vos classes pour gérer ce cas. En effet, votre matchCompareCriteria dépend du type du paramètre plutôt que de son comportement .

La règle de ne pas utiliser instanceof est valide lorsque vous vérifiez le type d'un objet pour choisir le comportement à adopter. Clairement, ce comportement appartient aux différents objets dont vous vérifiez le type. Mais dans ce cas, le comportement de votre objet dépend du type d'objet que vous avez transmis et qui appartient à l'objet appelant, et non à l'objet appelé comme auparavant. Le cas est similaire à celui où vous remplacez equals () . Vous effectuez une vérification de type pour que l'objet transmis soit du même type que cet objet , puis vous implémentez votre comportement: si le test échoue, retourne false; sinon, faites les tests d'égalité.

Conclusion: utiliser instanceof est acceptable dans ce cas.

Voici un article plus long, rédigé par Steve Yegge, qui explique mieux les choses, Je pense, en utilisant un exemple simple et direct. Je pense que cela correspond parfaitement à votre problème.

N'oubliez pas: Le polymorphisme est bon, sauf si ce n'est pas . :)

L’approche Smalltalk consisterait à introduire davantage de couches dans la hiérarchie. Ainsi, entre et supérieurThan seraient des sous-classes de rangeedCompareCriteria (ou quelque chose) et rangeCompareCriteria :: matchCompareCriteria renverrait strong> true à la question de savoir si deux instances de celle-ci étaient comparables.

À propos, vous voudrez probablement renommer " matchCompareCriteria " à quelque chose qui exprime un peu mieux l'intention.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top