脆弱な基本クラスの問題は、オブジェクト指向プログラミングシステムの基本的なアーキテクチャ上の問題です。基本クラス (スーパークラス) は「脆弱」であると見なされます。これは、基本クラスへの一見安全な変更が派生クラスに継承されると、派生クラスが誤動作する可能性があるためです。プログラマーは、基本クラスのメソッドを個別に調べるだけでは、基本クラスの変更が安全かどうかを判断できません。
考えられる解決策の 1 つは、インスタンス変数を定義クラスにプライベートにして、サブクラスがアクセサを使用してスーパークラスの状態を変更するように強制することです。言語によって、サブクラスが継承されたメソッドを公開するかどうかを制御できるようにすることもできます。これらの変更により、サブクラスがスーパークラスの実装の詳細に依存することがなくなり、サブクラスは自身に適用可能なスーパークラスのメソッドのみを公開できるようになります。
別の解決策としては、スーパークラスの代わりにインターフェースを使用することです。
脆弱な基底クラスの問題は、オープン再帰( 上のメソッドの動的ディスパッチ)のせいであるthisとされ、 上のメソッドの呼び出しはthisデフォルトでオープン再帰(動的ディスパッチ、遅延バインディング)ではなくクローズド再帰(静的ディスパッチ、早期バインディング)になり、オープン再帰は特に要求された場合にのみ使用され、外部呼び出し( を使用しないthis)は通常どおり動的ディスパッチされるという提案がなされている。[1] [2]
Javaの例
次の簡単な例はJava プログラミング言語で書かれており、一見安全そうな基本クラスの変更が、無限再帰に入り込んでスタックオーバーフローを引き起こし、継承サブクラスの機能不全を引き起こす可能性があることを示しています。
クラス Super { private int counter = 0 ;
void inc1 () {カウンター++ ; }
void inc2 () {カウンター++ ; } }
クラス Sub はSuperを拡張します{ @Override void inc2 () { inc1 (); } }
Subのインスタンスで動的にバインドされたメソッドinc2()を呼び出すと、フィールドカウンターが 1 つ正しく増加します。ただし、スーパークラスのコードが次のように変更された場合:
クラス スーパー{
プライベートintカウンター= 0 ;
void inc1 () { inc2 (); }
void inc2 () {カウンター++ ; } }
Subのインスタンスで動的にバインドされたメソッドinc2()を呼び出すと、それ自体とスーパークラスのメソッドinc1()の間で無限再帰が発生し、最終的にスタック オーバーフローが発生します。この問題は、スーパークラスのメソッドをfinalとして宣言し、サブクラスがメソッドをオーバーライドできないようにすることで回避できます。ただし、これは常に望ましい、または可能であるとは限りません。したがって、スーパークラスでは動的にバインドされたメソッドの呼び出しを変更しないようにすることをお勧めします。
ソリューション
- Objective-C には、脆弱でないインスタンス変数だけでなくカテゴリもあります。
- コンポーネント Pascal ではスーパークラスの呼び出しは非推奨です。
- Java、C++ (C++11 以降)、およびD では、クラスまたはメソッドの宣言にそれぞれキーワード「
final」を付けることで、クラス メソッドの継承またはオーバーライドを禁止できます。著書「 Effective Java 」の中で、著者のJoshua Bloch は(項目 17)、プログラマーは「継承を考慮して設計および文書化する、または継承を禁止する」必要があると書いています。 - C#とVB.NETにはJavaと同様に、継承を禁止する「
sealed」と「Not Inheritable」クラス宣言キーワードがあり、サブクラスでoverrideはオーバーライドメソッドにキーワード「」を使用する必要があります。 [3]これは後にScalaでも採用された同じ解決策です。 - Scala では、親クラスのメソッドをオーバーライドするために、サブクラスでキーワード "
override" を明示的に使用する必要があります。書籍「Programming in Scala, 2nd Edition」で、著者は次のように書いています (ここでは変更あり)。メソッド f() がない場合、クライアントのメソッド f() の元の実装にはオーバーライド修飾子を指定できませんでした。ライブラリ クラスの 2 番目のバージョンに f() メソッドを追加すると、クライアント コードを再コンパイルすると、誤った動作ではなくコンパイル エラーが発生します。 - Kotlinでは、クラスとメソッドはデフォルトで final です。クラスの継承を有効にするには、クラスを 修飾子でマークする必要があります。同様に、メソッドをオーバーライドできるようにする
openには、メソッドを としてマークする必要があります。open - Julia では抽象型のサブタイプのみが許可され、継承の代わりに合成が使用されます。ただし、多重ディスパッチがあります。
参照
参考文献
- ^ 「選択的オープン再帰: 脆弱な基底クラス問題の解決策」、ジョナサン・アルドリッチ
- ^ 「選択的オープン再帰:脆弱な基底クラスの問題に対する解決策」、Lambda the Ultimate
- ^ 「Override 修飾子 - C# リファレンス」。
外部リンク
- Mikhajlov, Leonid; Sekerinski, Emil (1998). 「ECOOP'98 — オブジェクト指向プログラミング」(PDF) . ECOOP'98 — オブジェクト指向プログラミング. ECOOP 1998. LCNS . Vol. 1445. pp. 355–382. doi :10.1007/BFb0054099. ISBN 978-3-540-64737-9. ISSN 0302-9743. QID 29543920 . 2020年7月21日閲覧。
- Holub, Allen (2003 年 8 月 1 日)。「extends が悪である理由」。Java ツールボックス。JavaWorld。2020年7 月 21 日閲覧。
