2018年11月12日

Fragile Base Class

這是物件導向程式語言中,類別繼承所造成的問題,當有類別被其他類別繼承時,常會遇到一些 method 被子類別覆寫 override 的狀況,但覆寫 method 後,卻可能會因為不了解父類別實作的細節,導致程式出錯。

以下是實際的例子

Super 提供兩個 method,但 inc1 會在method 裡面呼叫 inc2,Sub 繼承了 Super,但並不知道 inc1 的實作內容,在 Sub 裡面,覆寫了 inc2,將 inc2 改成呼叫 inc1,因此在使用 Sub 的 inc2 時,會一直重複呼叫函數,最後導致發生 java.lang.StackOverflowError

class Super {

  private int counter = 0;

  void inc1() {
    inc2();
  }

  void inc2() {
    counter++;
  }

}

class Sub extends Super {

  @Override
  void inc2() {
    inc1();
  }

  public static void main(String[] args) {
    Sub sub = new Sub();
    sub.inc2();
  }
}
$ java Sub
Exception in thread "main" java.lang.StackOverflowError
    at Super.inc1(Super.java:6)
    at Sub.inc2(Super.java:21)

這跟上面的例子類似,一樣是會發生重複呼叫函數的問題,最後導致發生 java.lang.StackOverflowError

class Base{
    protected int x;
    protected void m(){
        x++;
    }

    protected void n(){
        x++;      // <- defect
        m();
    }

}

class SubBase extends Base{
    protected void m(){
        n();
    }
    public static void main(String[] args) {
        SubBase sub = new SubBase();
        sub.m();
    }
}
$ java SubBase
Exception in thread "main" java.lang.StackOverflowError
    at Base.n(Base.java:9)
    at SubBase.m(Base.java:16)
    at Base.n(Base.java:9)

Java 提供一種解決方式,就是將無法被覆寫的 method 加上 final。

class Super {

  private int counter = 0;

  void inc1() {
    inc2();
  }

  final void inc2() {
    counter++;
  }
}

class Sub extends Super {

  @Override
  void inc2() {
    inc1();
  }
  public static void main(String[] args) {
    Sub sub = new Sub();
    sub.inc2();
  }
}

在編譯時,就會因為 final 的關係,造成編譯錯誤。

$ javac Super.java
Super.java:20: error: inc2() in Sub cannot override inc2() in Super
  void inc2() {
       ^
  overridden method is final
1 error

Kotlin 提供更積極的解決方案,所有 method 在不加上任何 modifier 的狀況下,預設都是有 final 特性的,如果可以讓子類別覆寫的 method,必須要加上 open 這個 modifier。

這樣的調整,可避免 method 任意被覆寫的問題,但相對的,programmer 要承擔更多責任,判斷什麼時候該加上 open,這有時候會造成一些困擾,就是不知道什麼時候要加上 open,就變成不寫 open。

open class Super {
    open fun v() {}
    fun nv() {}
}
class Sub() : Super() {
    override fun v() {}
}

最後只能說,沒有兩全其美的解決方案,就看程式語言的設計者,認定哪一種想法比較重要。

References

Fragile base class wiki

What is the fragile base class problem?