方法重载和继承——如何找到正确的方法?

Posted

技术标签:

【中文标题】方法重载和继承——如何找到正确的方法?【英文标题】:Method overloading & inheritance - how to address to the right method? 【发布时间】:2022-01-13 08:33:43 【问题描述】:

我有以下工厂:

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;
         
    

每当我通过ChildType 以生成Child 时,我都会返回正确的孩子实例:

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

似乎在这一点上,child 是正确的类型(比如说BoyChild)。 现在我想使用方法重载分别动态验证每个子类型。界面如下:

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

但是每当我尝试在上面的方法中使用它时,我都会收到错误CS1503: Cannot convert from 'Child' to 'BoyChild'

我想我应该以不同的方式声明 child,说“它可以是任何子类型”,但我不知道该怎么做。

提前致谢。

【问题讨论】:

我不知道你的整个代码,但我认为你可能会更好地将验证逻辑放在子类中:public abstract class Child public abstract ValidationResult Validate(); 这可以帮助你,例如开闭原则。 谢谢。我实际上已经按照您建议的方式实现了它,但是在模型内部包含业务逻辑我感到不舒服。 @SomeBody 请您详细说明一下,这样的实现(将验证逻辑作为模型的一部分)对代码的开放性和封闭性有何贡献? @noamyg 您可以添加新类型的Child,而无需更新您的IChildValidator 我认为任何基于一组固定方法重载的解决方案都不尊重使用接口和可扩展性的选择。您不能添加实现该接口的新子类型,而不会错过处理它的重载。所以我的观点是方法重载不是要走的路。 【参考方案1】:

Overload Resolution 发生在编译时。 C# 编译器使用参数的静态类型来查找最佳重载。由于您使用Child 类型的参数调用该方法,因此编译器找不到匹配的重载。这三个重载都没有Child 类型的参数。没有从ChildBoyChildGirlChildUndecidedChild 的隐式转换。您可以将派生类分配给基类,但不能反过来。

要走的路,是使用多态,即在(抽象的)Child 类中添加一个抽象的Validate 方法(除非您可以在基类中提供标准实现)。然后派生类必须重写此方法并提供实现。在运行时,Dynamic dispatch 将调用正确的实现。

【讨论】:

【参考方案2】:

根据您寻求的分离级别,您可以尝试这样的方法来将验证逻辑保留在您的子模型之外(这只是一个示例,有几种方法可以调整它,例如设置验证器的方式) :

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);

如果您喜欢使用特定类而不是基类的“准备覆盖”类/函数,您可以改进前面的示例。它有时对验证覆盖很有用,因为无论如何您都可能会强制转换 Child。同样,这只是一个简单的示例,Validate(Child child) 函数可能会在传递给 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);

public class BoyValidator: ChildValidator<BoyValidator>

    protected override ValidationResult TypedValidate(BoyValidator child)
    
        ...
    

【讨论】:

【参考方案3】:

要解决您的问题,您可以为您的子验证器使用抽象类而不是接口:

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);

但我不推荐这种解决方案。如果您检查变量的类型,这通常(并非总是)表明您做错了。相反,我建议将验证逻辑放在您的子类中:

public abstract class Child

    public abstract ValidationResult Validate();

这有几个优点:如果您添加一个新的子子类,您不必记住还要在您的ChildValidator 及其所有子类中添加这个类。这使得扩展代码的行为变得更容易,这也称为开闭原则。此外,我猜您的验证确实检查了您孩子的某些属性。如果您添加一个属性,您可能还必须更改您的验证方法。使用第二种方法,这些变化将集中在一个地方。

【讨论】:

如果 noamyg 不需要将验证逻辑从模型中分离出来,我完全同意你的看法。

以上是关于方法重载和继承——如何找到正确的方法?的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 中如何实现类的继承,方法重载及重写?

使用继承的 C# 重载方法调用 [重复]

c#如何重载ToString方法...

覆写(Override)和重载(Overload)的比较

封装继承和多态,重写重载等基础复习

Java中的(构造方法方法重载final修饰符使用及继承和抽象)