How can I replace instanceof in this case?

I am trying to compare compareCriteria. Simple ones such as "between" and "inArray" or "largeThan". I am using polymorphism for these classes. One of the methods they share with the compareCriteria interface is "matchCompareCriteria".

What I am trying to avoid is checking each class for the compareCriteria type they should match. For example, the inArray object checks if the matchCompareCriteria passed the inArray object, if it doesn't return false, in case it knows how to compare.

Perhaps instanceof is completely finished in this case (objects know about themselves), but still I'm looking at possible ways to avoid it. Any ideas?

Example pseudocode:

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

      

if X and Y are greater than Z, it will return true.

edit:

1) instanceof is what I see at the moment, as needed in the matchCompareCriteria method. I would like to get rid of him.

2) matchCompareCritera checks if compareCriteria contains another one. If all possible values ​​of one are contained by the other, it returns true. For many combinations of compareCriteria, it doesn't even make sense to compare them so that they return false (for example, between Alfa and between Num it would be incompatible).

+2


source to share


5 answers


The problem you are describing is called double dispatch . The name comes from the fact that you have to decide which bit of code to execute (send) based on two types of objects (hence: double).

Usually there is a single dispatch in OO - calling a method on an object causes the object's implementation of the method to execute.

In your case, you have two objects, and the executable implementation depends on the types of both objects. In essence, this is because it "feels wrong" when you've only dealt with standard OO situations before. But this is not entirely true - it is only slightly outside the problem area that basic OO functions are directly appropriate for solving.

If you are using a dynamic language (or a typed-type language with reflection that is dynamic enough for this purpose), you can implement it using a dispatcher method in the base class. In pseudocode:

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

        return comparisonMethod(other);
    }
}

      

Here I imagine that the language has a built-in method in each class called GetMethod

that allows me to search for a method by name, as well as a TypeName property for each object that gets me the name of the object type. Therefore, if the other class is GreaterThan

, and the derived class has a matchCompareCriteriaGreaterThan method, we will call this method:

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

      



So, you just need to write a method with the correct name and dispatch.

In a statically typed language that supports method overloading on the type of arguments, we can avoid having to invent a concatenated naming convention - for example, here it is in 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));
    }
}

      

If you want to add a new type to your model, you will need to look at each previous type and ask yourself if they need to interact with the new type in some way. Therefore, each type must define a way to interact with any other type - even if the interaction is really simple by default (for example, "do nothing but return true

"). Even this simple default is a deliberate choice that you must make. This is masked by the convenience of not explicitly writing code for the most common case.

Hence, it might make more sense to capture relationships between all types in an outer table, rather than scattering them around all objects. The value of centralization will be that you can see at a glance if you have missed any important interactions between types.

This way, you can have a dictionary / map / hash table (whatever it is called in your language) that maps the type to another dictionary. The second dictionary maps the second type to the right comparison function for the two types. The generic CompareWith function will use this data structure to find the correct comparison function to call.

Which approach is correct will depend on how many types you are most likely to end up in your model.

+8


source


Since you link to instanceof

, I am assuming we are working in Java here. This may allow you to use overload. Consider an interface called SomeInterface

, which has one method:

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

      

We now define two (cleverly named) classes that implement SomeInterface

: Some1

and Some2

. Some2

boring: test

always returns false. But Some1 overrides the function test

when given Some2

:

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

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

      

This allows us to escape the line after the line of if statements for type checking. But there is a caveat. Consider this 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

      

See what's the third test? Even if it inter

has a type Some2

, it is considered SomeInterface

. Overload resolution is determined at compile time in Java, which can render it completely useless to you.

This brings you back to the square: using instanceof

(which is evaluated at runtime). Even if you do, it's still bad design. Each of your classes should be aware of everyone else. If you decide to add another one, you must revert to all existing ones to add functionality to handle the new class. This becomes terribly unnoticeable in a rush, which is a good sign that the design is bad.

The redesign is okay, but without more information, I can't give you a particularly good nudge in the right direction.

+5


source


You need to create a superclass or interface called Criteria. Then each specific subclass will implement the Criteria interface. between, more, etc. are the criteria.

The Criteria class will specify a matchCompareCriteria method that accepts the criteria. The actual logic will be in subclasses.

You are looking for a strategy template template or a template template template.

+1


source


If I understand well your method uses type checking. This is difficult to avoid, and polymorphism does not solve the problem. In your inArray example, you need to check the type of the parameter, because the behavior of the method depends on it. You cannot do this with polymorphism, that is, you cannot use a polymorphic method on your classes to handle this case. This is because your matchCompareCriteria depends on the type of the parameter, not its behavior.

The no-use rule is in instanceof

effect when you check the type of an object to choose what behavior you want it to have. It is clear that this behavior belongs to the various objects whose type you are checking. But in this case, the behavior of the objectdepends on the type of object you passed in and belongs to the caller, not the callables as before. The case is similar to when you override equals()

. You type-check that the passed object is the same type as the object this

, and then implement your behavior: if the test fails, return false; otherwise, run equality tests.

Conclusion: Usage instanceof

in this case is fine.

Here is a longer article from Steve Yegge that explains, better I think, using a simple and simple example. I think this is great for your problem.

Remember, polymorphism is good, except. :)

+1


source


The Smalltalk approach would be to introduce more levels into the hierarchy. So in between and the big step would be subclasses of rangedCompareCriteria (or whatever), and rangeCompareCriteria :: matchCompareCriteria will return true when asked if two instances of itself are comparable.

That being said, you probably want to rename "matchCompareCriteria" to something that expresses intent a little better.

+1


source







All Articles