6

In languages with dynamic typing, the use of polymorphism may trigger errors on a super-class.

I will try to explain my question with a simple example: Supposing a language with dynamic typing (like ECMAScript) and the following class structure:

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

In this very simple example, when i create an object of the class B, B::foo() call the parent A::foo(), which call "update". The object is an instance of B, so the "update" functions called is B::update, after that, in B::foo, the update function is again called (B::update). The final result is that A::update is never called, and idA still 0.

The class A work correctly when used alone, but after to extend it with B, the function foo() fail.

What is the correct solution this problem:

1) Force the class A to call A::update , that mean an ugly code every call to his own function (protect the super-class):

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

2) B::update is an extension of A::update, so B::update must call itself the parent function (prepare the sub-class, and deal with problems):

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

But in this case is the A::foo which call update, not the B::foo. That mean others problems.

3) Any other solution.

As a summary:

How to protect the super-class code against polymorphism?

  • Add protections into the super-class.
  • Deal with these problem creating the child-class
  • The language must do that! (do not know if it is possible with dynamically typed languages)

I am looking for a very theoretical /canonical solution to this question.

EDITED: to take the problem out of the constructor and clarify some points.

4

4 回答 4

8

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

3) 任何其他解决方案。

医生,我这样做的时候很痛。

那就不要那样做!

说真的,如果你需要IdA在 的构造函数中设置A,不要通过调用来update进行,而是通过在构造函数中显式设置 的值来进行。IdAA

于 2013-08-05T17:53:03.013 回答
3

基类应该保护自己免受有害的覆盖。遵循开闭原则,对扩展开放,对修改关闭。覆盖update是对基类预期行为的有害修改。在您的示例中,覆盖没有任何好处,update因为A::updateB::update都是处理私有变量的私有方法。根据您在B::foo. 如果B::update命名不同,那么您的实现不会有任何问题。无论如何它可能没问题:因为我所知道的任何语言都不会让您覆盖私有方法,B::update可以隐藏A::update而不是覆盖它。

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

tl; dr:孩子与父母的私人无关。

于 2013-08-15T11:04:02.457 回答
2

你可能不会喜欢我的回答,但是:convention and disciplin

建立公约

  • 当子类可以安全地覆盖方法而不调用父类实现时,
  • 当子类必须调用重写方法的父类实现时,
  • 当子类不能覆盖父类方法时。

记录这些约定并遵守它们。它们可能应该是您代码的一部分;以注释或命名约定的形式(无论对您有用)。我可以想到这样的事情:

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

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

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

这可以帮助阅读您的代码的人弄清楚他可能会或可能不会覆盖哪些方法。

如果有人仍然覆盖你的@final方法,你希望有彻底的测试;)


我喜欢这个关于python的类似问题的答案:

您可以在其中发表评论,大意是:

# We'll fire you if you override this method.
于 2013-08-13T21:03:51.520 回答
1

If the language allows one class to call a private method of another class this way, the programmer has to understand and live with that. If I'm understanding your objective, foo and update should be overridden and update should be protected. They would then call the method in the parent class, when necessary. The derived foo method wouldn't need to call update, because calling foo in the parent class would take care of that. The code could work like this:

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

I changed the exceptions to match expectations.

于 2013-08-14T17:54:58.877 回答