Skip to content

Instantly share code, notes, and snippets.

@sile
Last active August 29, 2015 14:14
Show Gist options
  • Save sile/f04c7a1bfb764676a91d to your computer and use it in GitHub Desktop.
Save sile/f04c7a1bfb764676a91d to your computer and use it in GitHub Desktop.
オブジェクト指向入門 第2版 方法論・実践 - 第24章10節~第25章

24.10 複数の基準とビュー継承

分類基準が複数存在する際の話。

24.10.1 複数の基準によって分類する

自然科学における伝統的な分類では1つのレベルについて1つの基準が使われている:

  • ex: 脊椎動物or無脊椎動物、葉が毎年新しいものと入れ替わるかどうか
  • 単一継承の階層構造
  • 利点: 非常に単純
  • 問題: 自然は決して単一の基準で成り立っているのではない (例は本文参照)

ソフトウェアを作る際に、1つの基準では限定的するぎるような場合にはどうするか?

  • これまでの章で(完全に習得すべく学んだ)多重継承、特に反復継承の技法が利用可能

人事管理システムにおけるクラスEMPLOYEEの例:

  • 従業員を分類するための、二つの独立した基準がある:
    • 契約の種類による分類: 正社員と臨時雇い、etc
    • 仕事の種類による分類: 技術職、事務職、管理職、etc

単純だか好ましくない継承構造の例:

  • 図24.16: 図乱雑な分類
  • 大きく異なる概念が同じレベルのクラスによって表現されている
    • この継承構造は満足なものとは言えない

24.10.2 ビュー継承

今検討中の例で使われているこの分類のために、まだ継承を使うつもりなら、競合する分類基準を表すための中間的なレベルを作るべきである

中間的なレベルを導入した例:

  • 図24.17 viewによる分類
  • CONTRACT_EMPLOYEEとSPECIALTY_EMPLOYEEが中間的なレベルのクラスとして導入されている
  • ビュー継承の例
  • 「正社員の技術職」という従業員が存在し得る
    • 反復継承が向いている

部分型継承とビュー継承:

  • 部分型継承: ある特定のクラスのさまざまな後継者が互いに素であるインスタンスの部分集合を表す
  • ビュー継承: 同じ親のインスタンスを分類するさまざまなやり方を表している

注意:

  • ビュー継承に意味があるのは、親と後継者の両方が暫定クラス(一般なカテゴリを表すクラス)の場合だけ

24.10.3 ビュー継承は妥当か?

ビュー継承は継承のより一般的な使い方から比較的かけ離れており、批判を受けやすい:

  • 「読者の皆さんがご自身の目的のために使うかどうかはご自分で判断されるのが良い」
  • 「いずれにしろ、ここで賛否両論を検討しなければならない」

ビュー継承は熟練者向け:

  • ビュー継承(およびそれが必要とする反復継承)が 初心者向けのメカニズム ではないことは明らか
    • もし、重大なオブジェクト指向開発プロジェクトの実戦経験が数ヶ月にも満たないのなら、ビュー継承には関わらない方が良い

代替案:

  • 最優先する基準として分類基準のうちの1つを選び、継承の階層構造を考える際の唯一の指針として使う
    • そのほかの基準を扱う場合は、特定の特性を使う
    • EMPLOYEEの例 (図24.18)
      • 仕事の種類による分類、を優先 (メインの継承構造に採用)
      • 契約の種類による分類は WOKER_CONTRACT 型の特性contractを持たせることで解決

この方法はビュー継承の代わりに使うことが考えられる:

  • 別の階層構造と新しい属性(この場合はcontract)ができ、対応する顧客関係が増えたことで、確かに構造は複雑になる
  • とはいえ、そのような階層構造における抽象は分かりやすい
    • ex: 「雇用契約」、「正社員の雇用契約」
  • ビュー継承の方法でも抽象は明らかであるが、説明が少し難しくなる
    • ex:「雇用契約という視点からみた従業員」、「職種という視点から見た従業員」

24.10.4 ビュー継承の基準

問題領域分析の初期の段階においてビュー継承について考えることは珍しいことではない:

  • 応用領域に関する理解が浅いので、すべての候補が有力に見える
  • 最終的には、それらの中の一つの基準が他より飛び抜けて優れていることが分かることがある
    • そのような場合には、ビュー継承は捨てるべき (そして、24.10.3の代替手法等の、より複雑ではないものを選ぶべき)

ただし、次の三つの条件が満たされる場合は、ビュー継承が有効:

  1. いろいろな分類基準が同じように重要であるため、主たる基準として任意の基準を選ぶことができる
  2. たくさんの組み合わせを考える必要がある (ex: 正社員の管理職、一時雇いの技術者、正社員の技術者、etc)
  3. 検討中のクラス群が非常に重要であり、考え得る限り最善の継承構造を得るために多くの時間をかける価値がある
  • それらのクラスが再利用の可能性の大きい 再利用可能なライブラリ に含まれる場合は、特にそうである

Eiffelのコンテナライブラリの例:

  • 図24.19
    • BOX, COLLECTION, TRAVERSABLE, はビュー
  • 上の三つの条件を満たしている
  • 「最初はビュー構造ではなかったけど、後になって...」

ビュー継承に関して検討してきたことの結論:

  • お気楽な人向けではない
  • 適用できる場合は、多くの基準が相互に作用する複雑な問題領域における主要な役割を果たす
    • とにかく正しくできなければならない、再利用可能な部品の基本ライブラリの場合のように、それだけの努力をする価値があるならば

24.11 継承構造の構築方法

  • オブジェクト指向に関する書籍/記事/ライブラリを見ているとき、その中に見られる継承構造はすでに設計済み
    • なぜそのような構造にする必要があったかが必ず説明されているとは限らない
  • 自分で継承構造の設計に取り掛かることになったら、どうすればよいか?

24.11.1 特殊化と抽象化

トップダウン設計について:

  • 教育用の文献の多くは、最も汎用的なレベル(一番上の部分)から最も具体的なレベル(葉の部分)へと継承構造を設計すべきである、という印象を与える傾向がある
  • 図形のように、既に良い構造があるなら、それについて 記述 することがしばしば最善の方法である
  • しかし、構造を記述するための最善の方法が、構造を 作り出す 最善の方法とは限らない
  • 現実の世界では、先に特殊なケースを見つけ、後からその特殊なケースが実はある汎用的な抽象の変形の1つだと分かる場合が多い

多くの場合、抽象は唯一無二ではない。 ある特定の概念を一般化するための最善の方法は、その概念とその概念の変種に対して、あなた、または、あなたの顧客が最もしそうなことが何かによって異なる。

二次元空間における点(POINTクラス)の例:

  • 少なくとも四つの一般化が考えられる:
    • 任意の次元の空間における点: POINT, POINT_3D
    • 幾何学的図形: POINT, FIGURE, RECTANGLE, CIRCLE
    • 多角形: POINT, QUADRANGLE, TRIANGLE, SEGMENT
    • 二つの座標によって完全に決定されるオブジェクト: POINT, COMPLEX, VECTOR_2D
  • どれが最善かを断定することは不可能 (ソフトウェアの対象領域依存)

したがって、最も役に立つ一般化の道筋をみつけたと確信できるまで待ったために、 抽象化が少し遅すぎると思うこともあるような慎重な過程を辿る方が、 試されていない抽象をあまりにも多く、あまりにも早く使うやり方よりも望ましい。

24.11.2 分類の任意性

POINTは典型的な例である。 ある特定の抽象の集合について2つの競合する分類を提示されたとき、多くの場合、どちらがより良いかは合理的な論拠に基づいて判断できる。 しかし、考え得る中で特定の継承構造が最善のものであると判断できるケースはめったにない。

ソフトウェア以外の領域でも同様。

24.11.3 帰納と演繹

ソフトウェアの階層構造を設計するときの適切な過程は、演繹と帰納の組み合わせ、特殊化と一般化の組み合わせである。

帰納的な「ヨーヨー」アプローチも正常

経験を積み、直感が磨かれてゆくにつれて、(具体的な)演繹的な決定の割合が大きくなっていくことに気付くはずである。 しかし、帰納的な部分は常に残ることだろう。

24.11.4 クラス抽象の種類

二つの形式の帰納的な親の構築が一般的であり、有効である:

  • 概念抽象:
    • 図24.21
    • より高いレベルの概念を後から認識すること
    • 「ある役に立つ概念を表すクラスBを見つけたが、このクラスを作った人は、その概念が、実は、より一般的な概念Aの特殊なケースであることを認識していなかった」
  • 要素抽出:
    • 図24.22
    • EとFという二つのクラスが実は同じいパン的な概念の変形を表すことが分かった場合

後からより適切な抽象に気づいた場合は、書き直し(親クラスの追加)を推奨。

24.11.5 顧客の独立性

図 24.23

概念抽象や要素抽出によって、継承構造を変更したとしても、各クラスの直接の顧客には影響を与えない。

25.11.6 抽象のレベルを高める

概念抽象と要素抽出は成功するオブジェクト指向ソフトウェア構築の過程の特徴:

  • 継続的改善の過程を表す典型
  • 「私の経験では、オブジェクト指向の実戦において最も自慢できる側面の1つがこの点」
  • 「最初から完全なものを作ることを期待されてはいないけれども、皆が満足するまで設計を継続して改善する機会が与えられていると分かるのだ」

オブジェクト指向をうまく適用している開発グループでは、このように、ソフトウェアの 抽象のレベル が定期的に高まり、 その結果が品質に反映されていることがプロジェクトのメンバに明らかに分かり、それが彼らに絶えず刺激を与え、 やる気を促す役割を果たす。

24.12 概要:継承の上手な使い方

この章で扱ってきた継承が「何を意味するのか」や「どのように利用すればよいのか」についての、いくつかの結論。

  • 継承の使い方の多様性に怖気付いてはならない
    • 多重継承や共有性質継承を禁止しても、自分を傷つける以外の目的を果たさない
    • あなたを助けるためのものなので、上手に使いなさい
  • 継承はだいたいにおいて 供給者 の技術
    • 敵(特に、ソフトウェア開発者にとって情け容赦のない敵である複雑さ)と戦うための武器の一つ
    • (特にライブラリの場合は)__顧客__ソフトウェアにとっても重要
      • しかし、継承の主な目的はそもそも「もの」作りを助けること
  • 目的はソフトウェアの構築であって、哲学ではない
    • 解決策が1つしかないとか、絶対的に最善の方法といったものがあることはほとんどない
    • 「最善」とは、顧客アプリケーションの中にある特定のクラスの目的に最も適したという意味
    • ソフトウェアで絶対的なカテゴリを考えなければならないのは、汎用ライブラリを作る仕事に携わるときだけ
    • たいてい、目的はもっと穏やかなものであり、求められるのは、良い 階層構造の設計
      • すなわり、特定の種類の顧客ソフトウェアの必要を満たす階層構造
  • クラス構造を構築する際の難しさは継承そのものにではなく、抽象を見つけ出すことにある
    • 一般的な法則:
      • 妥当な抽象を選んでいるならば、継承構造は自ずとできてくるもの
      • 抽象を見つけ出すときのガイドとなるのは、抽象データ型理論

24.13 本章のまとめ

本文参照

24.14 参考文献

省略

24.15 付録:分類学の歴史

省略

24.16 演習問題

省略

25 役に立つ技法

これまでの要約の章

25.1 統計の哲学

25.1.1 一般的な開発計画

ボトムアップ開発: 確固たる基礎を構築し、それからそれを個々のケースに適用する

  • シームレス性: 一貫した技術とツールを使用して分析、設計、実現、保守を行う
  • 遡行可能性: 実現から得られた知見を上流の機能仕様定義に活かす
  • 汎化: 特殊なクラスから再利用可能なクラスを引き出す。抽象化、共通点の抽出。

25.1.2 システムの構造

システムを構成するのはクラスだけ:

  • 開発スタイルはボトムアップ
  • 最初からクラスをできる限り汎用的にするように務める
  • クラスはできる限り自立させる

二つのクラス間の関係:

  • 顧客と継承
    • 顧客には参照顧客と拡張顧客の二種類がある
  • それぞれが「がある(has)」と「である(is)」に対応する

多層的なアーキテクチャを使い、さまざまなプラットフォームのための実現から抽象的なインタフェースを分離する

25.1.3 システムの進化

変化と再利用のための設計

設計を改良するときは、特性やクラスに対して「obsolete」を利用し、顧客が移行を容易にできるようにする

25.2 クラス

25.2.1 クラスの構造

  • クラスはすべて良く定義されたデータ構造に対応するべき
  • 買い物メモアプローチ: もし、ある特性が役に立つ可能性があり、そのクラスのデータ抽象と調和するのなら、その特性を取り入れる
  • 共有性質クラス: 互いに関連する性質をひとまとめにする (ex. 定数の集合)
  • アクティブデータストラクチャ(抽象マシンとしてのオブジェクト)
  • 重要な決定事項は、どの特性を非公開にし、どの特定をエクスポートするか。
    • LINKED_LISTとLINKABLEのように、密接に結びついたクラス同士では選択的エクスポートを使う。
  • オブジェクト指向ではないソフトウェアは抽象をクラスにカプセル化し、作り直す。

25.2.2 クラスドキュメントの作成

できるだけ多くの情報をクラス自身の中に記述する。

ヘッダコメントは公式のインタフェースの一部なので、注意深く、一貫した形式で書く。

indexing句。

25.2.3 特性のインタフェースの設計

コマンドとクエリの分離原則:

  • ファンクションはいかなる抽象的な副作用ももたらしてはならない
  • オペランドだけを引数として用いる
  • まず状態を設定し、それから操作を実行する
  • 状態を設定するコマンドのそれぞれについて、状態を返すクエリを用意する
  • 引数のないクエリについては、属性としての実現とファンクションとしての実現の間に外部から見える違いがあってはならない
  • 要求された操作の結果、オブジェクトが表現を変えるときは内部で静かに行われるようにする
  • カーソル構造 (ex. LIST, LINKED_LIST, etc)

25.2.4 表明を使う

  • 事前条件は顧客を束縛し、事後条件は供給者を束縛する
  • 事前条件はルーチンが役割をきちんと果たせるだけの強さにする
    • しかし、強すぎてはならない
  • 不変表明句は2種類ある:
    • 元となる抽象データ型に由来するもの
    • 実際の一貫性に関する属性を表すもの(表現不変表明)
  • 実装不変表明
    • 同じクラスの異なる構成要素間、特に、属性間の関係について理解したことを表現し、それを改善するために使う
  • 引数のないクエリの場合
    • 不変表明の中に抽象的な性質を記述する (ファンクションの場合は事後条件の中にも記述される)
  • 再宣言により事前条件を弱め、ルーチンをより寛容にすることができる
  • 事前条件を強める効果を出すためには、元の事前条件を抽象的な事前条件としておいて実現する
    • 強める必要がない場合でも、抽象的な事前条件が望ましい
  • いかなる事前条件も顧客による強制と事前テストが可能でなければならない
  • 子孫でより強い条件にできるように、事後条件は厳しくしすぎてはならない
    • ex: 同値ではなく、一方通行の implies を使っておく

25.2.5 特殊なケースの扱い

  • 演繹的なチェック:
    • 操作の前に、その操作を適用できるかをチェックする
  • 帰納的なチェック:
    • とりあえず操作を実行し、それから属性を問い合わせ、うまく行ったかどうかを調べる
  • ほかのすべてが失敗したとき:
    • 例外処理を使う
      • 整然とした失敗:
        • rescue が最後まで実行された場合、不変表明の復元を忘れてはならない
        • 呼び出し側も例外を受け取る
      • 再試行:
        • ほかのアルゴリズムを試すか、同じことをやり直す
        • 起きたことの記録、ローカルエンティティの初期化タイミング

25.3 継承のテクニック

25.3.1 再宣言

  • より効率的にするために、より具体的なアルゴリズムを使うようにルーチンを再定義する
  • ルーチンを属性に再定義する
  • 親クラスで暫定特性だった特性を有効にする
  • 2つまたはそれ以上の特性を有効化で連鎖せせる
    • 1つを除き暫定特性として継承、有効な特性を1つ定義して他を連鎖的に有効化
    • 必要に応じて有効な特性の定義を取り消す
  • 2つまたはそれ以上の特性を一緒に再定義する
  • 再定義の中で親のバージョンにアクセスする
  • 再宣言は意味を維持する (表明に関する規則)

25.3.2 暫定クラス

  • 暫定クラスは高いレベルのカテゴリを表す
    • 分析設計ツールとして利用し、1つの実現に偏ることなく、抽象を表すことも可能
  • 振る舞いクラス
    • 一般的な振る舞いを表す
    • 有効なルーチンは暫定ルーチンを呼び出す
    • クラスは部分的に暫定にされ、部分的に実現される
      • 抽象データ型の実現を部分的に選択したことを表す

25.3.3 多相性

  • 多相的なデータ構造: 継承と総称性により、適度な類似と適度な変化を結合する
  • ハンドル: 多相的な属性によって可変コンポーネントを表す
  • 動的束縛: 明示的な区別を避ける
  • 多相的なデータ構造に対する動的束縛:
    • 1つの構造の個々の要素に対してそれぞれに適した方法で操作を適用する
  • 単一責任選択を使い、可能なそれぞれのオブジェクトに対するデータ構造をあらかじめ計算する
    • ex: 実行取り消しのパターン

25.3.4 継承の形式

  • 継承を使う箇所すべての分類上のカテゴリのいずれかに当てはまることを確認する
  • 部分型のための継承
  • モジュール拡張のための継承
  • 政略結婚: 具体的な構造によって抽象を実現する
  • 制約継承: 制約を追加する
  • 汎用的なメカニズムを共有性質クラスから継承する
  • ファンクショナルバリエーション継承: 「系統的ハッキング」、開放/閉鎖の原則
  • 型バリエーション継承: 共変性
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment