Home > Mobile >  Method overloading & inheritance - how to address to the right method?
Method overloading & inheritance - how to address to the right method?

Time:12-08

I have the following Factory:

public class ChildFactory : IChildFactory {
    public Child? GetChild(ChildType childType)
    {
        switch (childType)
        {
            case ChildType.Boy:
                return new BoyChild() { };
            case ChildType.Girl:
                return new GirlChild() { };
            case ChildType.Undecided:
                return new UndecidedChild() { };
            default:
                return null;
         }
    }
}

Whenever I pass through a ChildType in order to generate a Child, I get back the right instance of a child:

Child? child = _childFactory.GetChild((ChildType)Enum.Parse(typeof(ChildType), childDto.Type));

Seems like at this point, child is of the correct type (Let's say BoyChild). Now I want to dynamically validate each of the child types separately using method overloading. Here's the interface:

public interface IChildValidator
    {
        public ValidationResult Validate(BoyChild child);
        public ValidationResult Validate(GirlChild child);
        public ValidationResult Validate(UndecidedChild policy);
    }

But whenever I try to use it in the method above, I get the error CS1503: Cannot convert from 'Child' to 'BoyChild'.

I guess I should somehow declare child differently, saying "It can be any of the child types", but I don't know how to do that.

Thanks in advance.

CodePudding user response:

Method overloading happens at compile time. The C# compiler uses the static type of an argument to find the best overload. Since you are calling the method with an argument of type Child, the compiler does not find a matching overload. None of the tree overloads has a parameter of type Child. There is also no implicit conversion from Child to BoyChild, GirlChild or UndecidedChild.

The way to go, is to use polymorphy, i.e., to add an abstract Validate method to the Child class (which must also be abstract). The derived classes then must override this method and provide an implementation. At runtime, Dynamic dispatch will then call the right implementation.

CodePudding user response:

To solve your problem, you can use use an abstract class for your child validator instead of an interface:

public abstract class ChildValidator
{
    public ValidationResult Validate(Child child)
    {
       switch(child)
       {
         case BoyChild boyChild:
            return Validate(boyChild);
         case GirlChild girlChild:
            return Validate(girlChild);
         case UndecidedChild undecided:
            return Validate(undecided);
         default:
            throw new Exception(child.GetType()   " is not supported");
       }
    }

    protected abstract ValidationResult Validate(BoyChild child);
    protected abstract ValidationResult Validate(GirlChild child);
    protected abstract ValidationResult Validate(UndecidedChild policy);
}

But I don't recommend this solution. If you check the type of a variable, this is often (not always) the sign you are making something wrong. Instead, I recommend to put the validation logic inside your child class:

public abstract class Child
{
    public abstract ValidationResult Validate();
}

This has several advantages: If you add a new child subclass, you don't have to remember to also add this class at your ChildValidator and all its subclasses. This makes it easier to extend the behaviour of your code, which is also known as the open-closed principle. Moreover, I guess your validation does check some properties of your child. If you add a property, you will likely also have to change your validate method. With the second approach, these changes will be at one place.

CodePudding user response:

Depending on the level of separation you are seeking you might try something like this to keep the validation logic outside your child model (It is just an example, there is several ways to adjust this, the place the validator is set for instance) :

public class ChildFactory : IChildFactory
{
    public Child? GetChild(ChildType childType)
    {
        switch (childType)
        {
            case ChildType.Boy:
                return new BoyChild(new BoyValidator()) { };
            case ChildType.Girl:
                return new GirlChild(new GirlValidator()) { };
            case ChildType.Undecided:
                return new UndecidedChild(new UndecidedValidator()) { };
            default:
                return null;
        }
    }
}


public interface IChildValidator
{
    ValidationResult Validate(Child child);
}

public interface IChildValidatable
{
    ValidationResult Validate();
}


public class BoyValidator: IChildValidator
{
    ...
}


public class Child: IChildValidatable
{
    public Child(IChildValidator validator)
    {
        Validator = validator;
    }

    protected IChildValidator Validator { get; }


    public ValidationResult Validate() => Validator(this);
}

You can add this if you like kind of "ready to override" classes / functions with specific classes instead of the base class. Sometimes useful for validation since you might cast the Child anyway. Again, its just a light example, the Validate(Child child) function might perform some generic checks and an additonal class check before passing to TypedValidate.

public abstract class ChildValidator<T>: IChildValidator where T: Child
{
    public ValidationResult Validate(Child child) => TypedValidate(child as T)

    protected abstract ValidationResult TypedValidate(T child);
}

and

public class BoyValidator: ChildValidator<BoyValidator>
{
    protected override ValidationResult TypedValidate(BoyValidator child)
    {
        ...
    }
}

CodePudding user response:

Define an interface IChild and use that instead of the base class.

public class ChildFactory : IChildFactory {
    public IChild? GetChild(ChildType childType)
    {
        switch (childType)
        {
            case ChildType.Boy:
                return new BoyChild() { };
            case ChildType.Girl:
                return new GirlChild() { };
            case ChildType.Undecided:
                return new UndecidedChild() { };
            default:
                return null;
         }
    }
}
  • Related