如何处理类中的多态性

时间:2013-08-05 17:35:06

标签: class polymorphism dynamic-typing

在具有动态类型的语言中,使用多态可能会在超类上触发错误。

我将尝试用一个简单的例子来解释我的问题: 假设一种动态类型的语言(如ECMAScript)和以下类结构:

diagram

class A{
    private idA;
    public A(){
        idA=0;
    }
    public foo(){
        update();
        if (this.idA!=3) Throws new Exception(" What is happening? ");
    }
    private update(){
        this.idA = 3;
    }
}
class B extends A{
    private idB;
    public B(){
        super();
        idB=0;
    }
    public foo(){
        super.foo();
        // Any operation between A::update and B::update()
        if (this.idB!=0) Throws new Exception("hmmm, that could not happend!");
        update();
    }
    private update(){
        this.idB = 5;
    }
}

在这个非常简单的例子中,当我创建一个B类的对象时,B :: foo()调用父A :: foo(),它调用“update”。该对象是B的实例,因此调用的“更新”函数是B :: update,之后,在B :: foo中,再次调用更新函数(B :: update)。最终结果是永远不会调用A :: update,而idA仍为0。

A类在单独使用时工作正常,但在用B扩展后,函数foo()失败。

这个问题的正确解决方法是什么:

1)强制A类调用A :: update,这意味着每次调用自己的函数时都会看到一个丑陋的代码(保护超类):

A::foo(){
    A::update();
    if (this.idA!=3) Throws new Exception(" What is happening? "); 
}

2)B :: update是A :: update的扩展,所以B :: update必须将自己称为父函数(准备子类,并处理问题):

B::foo(){
    super.foo();
    ... // Any operation that must be performed between A::update and B::update
}
B::update(){
    super.update();
    this.idB = 5;
}

但在这种情况下是调用更新的A :: foo,而不是B :: foo。这意味着其他问题。

3)任何其他解决方案。

总结:

如何保护超类代码不受多态影响?

  • 为超级班级添加保护。
  • 处理创建子类
  • 的这些问题
  • 语言必须这样做! (不知道动态类型语言是否可行)

我正在寻找这个问题的理论/规范解决方案。

编辑:从构造函数中解决问题并澄清一些观点。

4 个答案:

答案 0 :(得分:8)

通常认为非常糟糕的做法从构造函数中完全调用实例方法,,尤其是virtual 实例方法出于这个原因(但也是因为尚未完成对象的“初始化”)。

  

3)任何其他解决方案。

Doc,我这样做会很痛。

然后不要那样做!

说真的,如果您需要在IdA的构造函数中设置A,请不要通过调用update来执行此操作,通过显式设置来执行此操作IdA的构造函数中A的值。

答案 1 :(得分:3)

基类应该保护自己免受有害的覆盖。为了与open/close principle保持一致,它应该对扩展开放,但不能修改。覆盖update是对基类预期行为的有害修改。在您的示例中,覆盖update没有任何好处,因为A::updateB::update都是处理私有变量的私有方法。根据{{​​1}}中的例外,甚至没有期望它们应该一起执行。如果B::foo的名称不同,则您的实施不会出现任何问题。无论如何它可能会好的:因为我所知道的语言不会覆盖私有方法,B::update可以隐藏B::update而不是覆盖它。

根据语言,您可以限制可以以不同方式覆盖哪些方法。有些语言需要一个指示符(通常是一个关键字或属性),可以覆盖一个方法,其他语言则表明它不能。私有方法通常不会被覆盖,但并非所有语言都具有访问修饰符,并且所有内容都是公开的。在这种情况下,您必须使用@PoByBolek建议的某种约定。

tl;博士:孩子与父母的私生子无关。

答案 2 :(得分:2)

你可能不会喜欢我的答案,但是:惯例和纪律

建立

的约定
  • 如果子类在不调用父类实现的情况下覆盖方法是安全的,
  • 当子类必须调用重写方法的父类实现时,
  • 当子类不能覆盖父类方法时。

记录这些惯例并坚持下去。它们应该是您代码的一部分;无论是注释形式还是命名约定(无论对你有用)。我能想到这样的事情:

/*
 * @final
 */
function shouldNotBeOverridden() {
}

/*
 * @overridable
 * @call-super
 */
function canBeOverriddenButShouldBeCalledFromChildClasses() {
}

/*
 * @overridable
 */
function canBeOverridenWithoutBeingCalledFromChildClasses() {
}

这可以帮助读取您的代码的人找出他可能或不可以覆盖的方法。

如果某人仍然覆盖了您的@final方法,那么您希望能够进行全面测试;)


我喜欢this answer关于python的一些类似问题:

  

你可以在那里发表评论:

# We'll fire you if you override this method.

答案 3 :(得分:1)

如果语言允许一个类以这种方式调用另一个类的私有方法,那么程序员必须理解并使用它。如果我理解你的目标,应该覆盖foo和更新,并且应该保护更新。然后,他们会在必要时调用父类中的方法。派生的foo方法不需要调用update,因为在父类中调用foo会处理这个问题。代码可以像这样工作:

class A{
    private idA;
    public A(){
        idA=0;
    }
    public foo(){
        update();
        if (this.idA!=3) Throws new Exception("idA not set by update");
    }
    protected update(){
        this.idA = 3;
    }
}
class B extends A{
    private idB;
    public B(){
        super();
        idB=0;
    }
    @Override
    public foo(){
        super.foo();
        // Any operation between A::update and B::update()
        if (this.idB!=5) Throws new Exception("idB not set by super.foo");
    }
    @Override
    protected update(){
        super.Update()
        this.idB = 5;
    }
}

我更改了例外以符合期望。