【问题标题】:Best practice for enforcing type safety in polymorphic inheritance hierarchies [closed]在多态继承层次结构中强制类型安全的最佳实践 [关闭]
【发布时间】:2012-03-21 14:37:13
【问题描述】:

我似乎经常遇到这种情况,但还没有找到我认为可以接受的解决方案。

我经常会有并行继承层次结构,其中一个层次结构中的方法从另一个层次结构中传递匹配的类作为参数。

这是一个可能更好地解释这一点的示例。

abstract class Animal
{
    public virtual void Eat(Food f)
    {
    }
}

abstract class Food
{
}

class LionFood : Food
{
}

class ElephantFood : Food
{
}

class Lion : Animal
{
    public override void Eat(Food f)
    {
        // It is only ever valid to pass LionFood here as the parameter.
        // passing any other type of Food is invalid and should be prevented
        // or at least throw an exception if it does happen.
    }
}

过去,我通常将基类设为泛型,以允许实现的具体类定义类型如下..

abstract class Animal<T> where T : Food
{
    public abstract void Eat(T f);
}

class Lion : Animal<LionFood>
{
    public override void Eat(LionFood f)
    {
    }
}

起初这似乎是一个很好的解决方案,因为它提供了编译时类型安全。但是我用得越多,我就越开始认为以这种方式使用泛型实际上是anti-pattern。问题是 Animal 基类不能以多态方式使用。例如,你不能轻易地编写一个方法来处理任何类型的 Animal,而不管它的实际具体类型如何。

每次我使用这个泛型解决方案时,我似乎总是在各处使用协变和逆变接口,只是为了尝试提供我想要的多态行为。这很快就会失控,并且仅仅因为无法提供正确的界面而无法实现某些功能。

当然,另一种选择是不使用泛型并在 Eat 方法中执行运行时类型检查,如下所示:

    public override void Eat(Food f)
    {
        if (f.GetType() != typeof(LionFood))
        {
            throw new Exception();
        }
    }

我想这总比没有好,但我不是它的忠实粉丝,仅仅是因为缺乏编译时类型安全性。

毕竟……我的问题是……提供多态行为同时确保某种类型安全的最佳实践是什么?

是否有一些我缺少的 OO 设计技巧或模式可以让我一起避免并行继承层次结构?

我很欣赏这个问题有些主观,但每个贡献的人都有积分,我会选择最佳答案作为答案。

感谢收看。

编辑:

考虑到这一点,我意识到我给出的示例并没有真正的意义。当然,不可能以多态方式使用 Animal,因为传递给 Eat 的类型将始终取决于 Animal 的实际底层类型(多态调用的发起者不会知道)!我需要想一个更好的例子来说明我的实际情况。

【问题讨论】:

  • 您能否举一个您认为设计存在问题的代码示例?
  • 为什么不只创建泛型方法而不是泛型类?
  • @VictorSorokin 我已经尽可能地概括了这个,所以我不确定我可以提供哪些进一步的代码示例。我设计的问题是泛型解决方案不允许多态行为(不会变得非常混乱),并且类型检查不提供任何编译时安全性。
  • 我不明白你到底想要什么。你想让动物吃一些共同的东西,但是,你又不希望它们能吃任何共同的东西?
  • @enobayram 是的,我认为你在暗示我做错了什么。我只需要弄清楚什么是正确的方法。狮子只会吃LionFood,大象只会吃ElephantFood。但在 Food 中,有一些常见的 Eat 功能是所有 Animals 共有的。

标签: oop design-patterns polymorphism software-design


【解决方案1】:

我认为常识和领域的要求将决定正确的方法。使用这个例子,我会这样做

 public class Lion:Animal
 {
       public override void Eat(Food f)
        {
           Eat(f as LionFood);
         }
       public void Eat(LionFood food)
        {
         //check for null food  
         //actually consume it
       }
  }

编辑

我认为在这种情况下不适合使用泛型,因为如果动物可以玩玩具、猎杀特定的动物等等。您可以拥有许多带有实现抽象的参数的方法,每次有一个带有使用多态的参数的方法时都使用泛型是很尴尬的。

【讨论】:

  • 这是什么语言?当您尝试将草喂给狮子时,这会导致编译时错误吗?
  • 那是 C#,可能也适用于 java(不确定语法)。没有编译错误,除非 Grass 没有继承 Food。但这就是您使用多态性来针对抽象进行编程的原因(Food 可以是一个接口)。
  • @MikeSW 正是 Mike,您的编辑解释了为什么泛型路线会变得非常混乱。起初这似乎是一个不错的选择,但结果通常不是很好。我认为通常最好牺牲编译时间检查,而是将类型检查放入代码中,就像您在答案示例中所做的那样。
【解决方案2】:

好的,这就是你想要的吗?有时,当你无法清楚地表达你想要做什么时,你想要的实际上是一个 mixin。

template<typename base>
class Eater: public base {

template<typename T>
Eat(T (extends Food) food) { // you can do that extends thing in C++ but I won't bother
    // register what's eaten, or do whatever
    base::Eat(food);
}

}

class Lion {
    Eat(LionFood food) {
        cout<<"Hmm delicious!";
    }
}

int main() {
    Eater<Lion> lion;
    lion.eat(LionFood());
    return 0;
}

如果你试图给狮子喂草,这会给你一个很好的编译器错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-12
    • 1970-01-01
    • 2015-01-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多