views:

240

answers:

3

Ok, I'm trying to do the following:

        protected bool ValidAdvert(Base item)
        {
            throw ThisIsAnAbstractClassException();
        }

        protected bool ValidAdvert(Derived1 item)
        {
            return ADerived1SpecificPredicate;
        }

        protected bool ValidAdvert(Derived2 item)
        {
            return ADerived2SpecificPredicate;
        }    

And have the Derived class versions of the method be called, when a Base class is passed to the method. The base class is abstract so this should, in theory, be possible?

Before someone says something about overloading the method on the classes themselves, the logic inside the methods relies on a large number of different conditions, none of which are related, and none of which related to the Base/Derived class directly (such as login status, etc.)

+4  A: 

A perfect place to use double dispatch:

class Base
{
    public abstract bool IsValid(IAdvertValidator validator);
}

class Derived1 : Base
{
    public bool IsValid(IAdvertValidator validator)
    {
        return validator.ValidAdvert(this);
    }
}

class Derived2 : Base
{
    public bool IsValid(IAdvertValidator validator)
    {
        return validator.ValidAdvert(this);
    }
}

interface IAdvertValidator
{
    bool ValidAdvert(Derived1 d1);
    bool ValidAdvert(Derived2 d2);
}
Anton Gogolev
Clever, I like it.
Ed Woodcock
A reasonable use of the pattern. An alternative would be to expose validation methods from the validator and make use them inside an implementation of IsValid, keeping the actual validation flow of control in the class.
tvanfosson
A: 

I'm not sure exactly how you will ever have an instance of the base class that isn't actually an instance of a derived class. Since the base class is abstract, it can't be instantiated. I suppose it would match any derived class that doesn't have a specific signature, but that doesn't seem to be what you're going for.

tvanfosson
Yes, it is always one of the derived classes, however, it's also technically the base class, meaning the base class method gets called when I do (List<Base>)i.Where(ValidateAdvert), for example.
Ed Woodcock
Ah -- I was reading your code sample, not your question. You don't actually want it to throw an exception, you want it to call one of the other methods instead. In that case I'd go with @Anton's suggestion, or something similar. Note that you could provide the validator in a constructor so that you don't need to pass it as an argument. I've also used a static validation class that validates simple properties removing the need to keep a reference to it. You need to be careful with these, though, as they can make testing harder if you're not careful.
tvanfosson
The validator is very much a page- and status-specific class, and so wouldn't work nicely as a static, however I think it'd go quite nicely in the reflection method, it's a shame this isn't supported by default though!
Ed Woodcock
+2  A: 

If you find the double dispatch too intrusive, you can call the method by reflection and resolve the proper overload dynamically.

protected bool ValidAdvert(Base item)
{
    if (item.GetType() == typeof(Base))
        throw new ThisIsAnAbstractClassException();

    Type type = typeof(CurrentClass);

    MethodInfo method = type.GetMethod("ValidAdvert",
                                       BindingFlags.Instance | BindingFlags.NonPublic,
                                       null,
                                       new Type[] { item.GetType() },
                                       null);
    return (bool)method.Invoke(this, new object[] { item });
}

protected bool ValidAdvert(Derived1 item)
{
    return ADerived1SpecificPredicate;
}

protected bool ValidAdvert(Derived2 item)
{
    return ADerived2SpecificPredicate;
}

This pattern is called MultipleDispatch, while calling a method by reflection is slower than calling the method directly (it won't be an overhead if the method is called less than few hundred times per second), it has the benefit of not requiring to modify your class hierarchy like in the Double Dispatch pattern.

This will be even easier when c# 4.0 comes out with the dynamic keyword:

protected bool ValidAdvert(Base item)
{
    if (item.GetType() == typeof(Base))
        throw new ThisIsAnAbstractClassException();

    dynamic dynamicThis = this;

    return (bool)dynamicThis.ValidAdvert(item);
}
SelflessCoder
Also clever, and avoids messing with the various derived class (I wish there were only 2!).
Ed Woodcock
quite notes: you need to cast to bool on the return, and method.Invoke takes an [] of objects, and so craps out if you try and pass just the 1 :) Thanks though!
Ed Woodcock
@Ed -- I think keeping the predicate with the derived class is actually the preferred method. Storing derived class-specific logic in a base class creates a reverse coupling between the two. I don't see this as a good thing at all. The base class shouldn't need to know anything about its derived classes.
tvanfosson
@Ed Thanks for pointing out these mistakes, I updated the answer.
SelflessCoder
@tvanfosson If that logic is really specific to the object hierarchy, I agree that it would be cleaner to have an abstract implementation of ValidAdvert in the base class and overriding it in the derived classes. But if that logic is not strongly coupled with these objects, it can be cleaner to use the multiple dispatch and have all your logic in the same file rather than splitting it in multiple classes.
SelflessCoder
The logic is not coupled to the objects, it's related to the page context and other bits of unrelated member-information, thus, it makes little sense to tie this to the derived classes.
Ed Woodcock
@Ed, @Jeff - In this case I guess I agree, but not as a general rule. I think what's throwing me off is the word `valid`. I generally think of this as whether the object state satisfies the business rules. This seems to be more the case of whether the object is allowable in a particular context, where it's intersection of the context and the object's properties that define whether the object is permitted. Aside -- I don't think the check for objects of the base type is needed, especially with reflection, since the base class is abstract and cannot be instantiated. All objects are derived.
tvanfosson
+1 for the demonstration of the `dynamic` keyword, that helped with my problem :)
wsanville