抽象化 (計算機科学)

抽象化（ちゅうしょうか、英: Abstraction）は、計算機科学において詳細を捨象し、一度に注目すべき概念を減らすことおよびその仕組みである。
この概念は数学における「抽象化」からのアナロジーである。数学での抽象化技法の起源は数学的定義である。例えば、コンピュータでも数学でも、数はプログラミング言語上の概念であり、数学上の概念でもある。数の計算概念は数学の概念に基づいているため、実装の詳細はハードウェアとソフトウェアに依存したとしても、それが制約とはならない。
大まかに言えば、抽象化は制御抽象化とデータ抽象化に分けられる。制御抽象化は動作の抽象化であり、データ抽象化はデータ構造の抽象化である。例えば、構造化プログラミングでの制御抽象化とは、サブプログラムや定式化された制御フローの使用を意味する。データ抽象化とは、本来ビット列であるデータを意味のある方法で扱うことを意味する。例えば、データ型の背景にある動機は抽象化である。オブジェクト指向プログラミングはデータとコードを同時に抽象化する試みと見ることもできる。

原理

コンピューティングの大部分は実世界とは独立している。ハードウェアは計算モデルを実装したものであり、他のモデルとの入れ替えが可能である。ソフトウェアはソフトウェアアーキテクチャで構築され、人間が一時に一部の問題に集中することで巨大なシステムを生み出すことを可能にしている。このようなアーキテクチャは特定の抽象化を選択して利用している。グリーンスパンの10番目の規則は、そのようなアーキテクチャがいかに必然的で複雑であるかを示す格言である。
コンピューティングでの主な抽象化は言語の抽象化である。新たな人工言語はシステムの特定の観点を表現するために開発される。モデリング言語は計画立案を補助する。コンピュータ言語はコンピュータで処理できる。このような抽象化プロセスの例として、プログラミング言語の世代的開発が挙げられる。各世代は次の世代の基盤となった。言語の抽象化は現在も続いており、例えばスクリプト言語やドメイン固有言語でこの進化が著しい。
プログラミング言語では、一部機能によってプログラマが新たな抽象化を生み出せるようにしている。例えば、サブルーチン、モジュール、ソフトウェアコンポーネントなどがそれである。プログラミング言語自体の機能ではないが、設計技法上の抽象化としてデザインパターンやソフトウェアアーキテクチャがある。
抽象化によっては、次々に構築される概念を完全に隠蔽することでプログラマが把握しなければならない概念の幅を制限しようとする。Joel Spolskyは、あらゆる抽象化は破綻しやすいと主張して批判した。つまり、抽象化によって下部構造が完全に隠蔽できたためしがないというのである。一部の抽象化は他との相互のやりとりのために設計されている。例えばプログラミング言語には外部関数インタフェースを持つものもあり、低レベルな言語を呼び出すことができる。

仕様記述言語

仕様記述言語は一般に何らかの抽象化に基づいている。仕様はプロジェクトの初期に定義されるもので、最も抽象的なレベルであり、それが最終的に実装される。例えば仕様記述言語であるUMLは「抽象」クラスを定義でき、プロジェクトの仕様設計段階ではそれらは「抽象」のままである。

データ抽象化

データ抽象化とは、データ型の「抽象的」属性と「具体的」実装詳細の明確な分離を強制することである。抽象化された属性はデータ型（あるいはそのインタフェース）を利用するクライアントコードとして明確化され、具体的実装は完全にプライベートな状態でかつ必要に応じて変更できる形で存在する。概念的には、そのような変更は抽象的振る舞いには変化をもたらさないので、クライアントコードには全く影響を与えない。
例えば、「参照テーブル」という抽象データ型を定義したとする。参照テーブルには「キー」とユニークに対応する「値」があり、キーを指定することで値を操作できる。このような参照テーブルの実装方法はハッシュテーブルや2分探索木や線形リストなどいくつかある。クライアントコードからすれば、データ型の抽象化された属性はどの場合でも同じである。
もちろん、以上の話は最初にインタフェースを正しく詳細化することにかかっており、そうでないと実装の変更がクライアントコードに影響を及ぼしてしまう。別の見方をすれば、インタフェースをデータ型とクライアントコードの間で合意された振る舞いの「契約」を形成すると考えることもできる。契約にない部分は予告なく変更される可能性がある、というわけである。
データ抽象化を実装した言語としてはAdaやModula-2がある。オブジェクト指向プログラミング言語も一般にデータ抽象化を提供すると言われているが、継承の概念によって実装側の情報がインタフェース側に持ち出される傾向がある。したがって、継承関係の変更はクライアントコードに影響を与えることがあり、脆弱な基底クラス問題につながる。

オブジェクト指向プログラミングでの抽象化

オブジェクト指向プログラミングの理論では、抽象化とは抽象的「アクター」であるオブジェクトを定義する機能であり、アクターは作業を行い、状態を変化させ、状態を報告し、システム内の他のオブジェクトと「通信」する。カプセル化は、状態の詳細を隠蔽することを意味する用語だが、従来のプログラミング言語でのデータ型の概念の拡張である。カプセル化はデータに強く結びついた「振る舞い」をそのデータと関連付け、他のデータ型との相互作用を標準化し、抽象化の起点となる。抽象化をさらに進めて、異なる型のオブジェクト間で同じ操作を定義することをポリモーフィズムと呼ぶ。また、逆方向に抽象化を進め、データ型やクラスの内部を抽象化してそれらの複雑な関係を単純化し構造化することを委譲または継承と呼ぶ。
オブジェクト指向言語は様々あるが、似たような抽象化手法を提供している。ポリモーフィズムはほぼ全てのオブジェクト指向言語でサポートされており、類似あるいは同じ役割を持つデータ型の置換なども含まれる。それほど一般的ではないが、構成・イメージ・パッケージによってコンパイル時/リンク時/ロード時にオブジェクト間の関係を決定することがあり、この場合実行時に関係を決定する必要性がほとんどなくなる。
CLOSやSelfなどの言語では、クラスとインスタンスをあまり区別せず、ポリモーフィズムの実現に委譲をよく使う。また、個々のオブジェクトや関数はより柔軟に抽象化され、LISPの機能により適するようになっている。
C++では、テンプレート、演算子オーバーロード、その他のコンパイル時の静的バインディングなどが特徴であり、これらがC++固有の柔軟性に関する問題を生んでいる。
同じ抽象化にも様々な戦略があるが、基本的に抽象名詞をコード内でサポートする必要性に違いはない。いずれのプログラミング言語も動詞を関数として、名詞をデータ構造として、そして両者を合わせてプロセスとして抽象化する機能に基づいている。
例えば、以下のサンプルコードはJavaによる動物の抽象的表現である。ここでは、飢えと食事という観点で抽象化している。Animalクラスは動物の状態と機能を表現するよう定義されている。
class Animal extends LivingThing {
    Location loc;
    double energyReserves;

    boolean isHungry() {
        if (energyReserves < 2.5) { return true; }
        else { return false; }
    }
    void eat(Food f) {
        // Consume food
        energyReserves += f.getCalories();
    }
    void moveTo(Location l) {
        // Move to new location
        loc = l;
    }}
この定義で、Animal型のオブジェクトを生成し、以下のようにメソッドを呼び出すことができる:
thePig = new Animal();theCow = new Animal();if (thePig.isHungry()) { thePig.eat(tableScraps); }if (theCow.isHungry()) { theCow.eat(grass); }theCow.moveTo(theBarn);
この例では、Animalクラスが動物を抽象化したもので、LivingThing は Animal よりさらに高い抽象度の抽象化（この場合汎化）である。
もっと違った抽象化も考えられる。例えば中間的な抽象化として、毎日ミルクを生み出してくれる動物と、最後に肉となってくれるだけの動物とに分類することができる。DailyAnimal （雌牛や山羊）はミルクを生み出すのに適した餌を与えられ、Animal （豚や雄牛）は肉の質を高める餌を与えられる。
このような抽象化をすれば、アプリケーションを書く人は餌の種類を指定する必要がなくなり、給餌スケジュールに集中することができるようになる。この2つのクラスは継承関係であっても、全く独立していてもよく、それによってポリモーフィズムの度合いも変わってくる。このような機能は言語によって大きく違うが、大体においてある言語でできることは他の言語でも可能である。演算子オーバーロードや抽象データ型を多用することで、継承や他のポリモーフィズムを実現する手法と同様の効果が得られる。クラスを使った記法は単にコードを書くものの利便性のために存在するに過ぎない。

オブジェクト指向設計

何を抽象化して、何をコードを書く者の制御下に置くかの判断は、オブジェクト指向設計とドメイン分析の主要テーマである。実世界での適切な関係を見極めることは、オブジェクト指向分析のテーマでもある。
一般に、適切な抽象化を決定するには、範囲/ドメイン分析/連携すべきシステムの決定/様々な制約の分析など、様々な判断を必要とする。そして、オブジェクト指向分析をプロジェクトの外的条件（時間と費用）を考慮して行う。上の単純な例では、ドメインとは庭であり、豚や牛やそれらの食習慣は制約である。詳細な解析により、コード作成者は利用可能なものなら何でも餌として与える柔軟性を持つ必要があると判断する。そのため、クラス自体に餌の型をコード化する必要がないと判断され、結果として豚であっても雌牛であっても同じ Animal クラスとなる。DairyAnimal を別のクラスとする判断を下した場合には詳細は変化するが、ドメインや制約は変わらない。従って全てはプログラマの制御下にある。オブジェクト指向プログラミングでの抽象化と、ドメインや制約の抽象化は区別されている。

考察

プログラム意味論、形式手法、抽象解釈などを論じるとき、抽象化とは観測されたプログラムの動作の定義をより大雑把にかつ安全に検討する作業を意味する。例えば、実行途中の全段階を追うことなく結果だけを検討する場合などである。抽象化とは具体的実行モデルと反対の概念として定義される。
抽象化は、属性が具体的モデルと同じとなるよう属性に関して「正確」または「忠実」でなければならない。例えば、加減算と乗算だけを使った式が n で割り切れるかを知りたければ、n で割り切れる値を全て計算すればよい（この抽象化は九去法と類似の形式である）。
抽象化は必ずしも「正確」である必要はないが、「健全」であることを要求されることもある。つまり、決定不能な結果を生むとしても、抽象化から正当な結果が得られるべきだという意味である。例えば、クラスの生徒たちを年齢の最大と最小で抽象化した場合、ある人物がそのクラスに属するかどうかを判定するためにその年齢を最大値・最小値と比較することが考えられ、年齢がその範囲外だった場合は、その人物がクラスに属さないと確信を持って言うことができる。範囲内だった場合、「わからない」としか答えようがない。
コンピュータプログラムの特性は本質的に決定不能であるため、抽象化はコンピュータプログラムを扱う場合に有効である（ライスの定理参照）。同様にプログラムの動作によって情報を引き出す自動化手法は、停止性（場合によっては失敗したり、いつまでたっても結果が出ないことがある）、健全性（誤った情報を提供することがある）、正確性（問題に対して答えられない場合がある）のいずれかを諦めざるをえない場合がある。
抽象化は抽象解釈の中核的概念である。モデル検査は一般に対象システムを抽象化したものに対して行われる。

抽象化レベル

計算機科学における「抽象化レベル」または「抽象化層」の概念では、各レベルは同じ情報やプロセスの異なったモデルとなっているが、特定領域に適用可能なオブジェクトや構成のユニークな集合に関する表現体系を使う。レベルの高いほうはより抽象的であり、レベルが下るに従ってより細かく詳細になっていく。例えば、電子回路による論理ゲート、論理ゲート上のバイナリ、バイナリ上の機械語、機械語上のプログラミング言語、プログラミング言語上のアプリケーションソフトウェアやオペレーティングシステムといった階層である。各レベルには実体があるが、ある程度自己完結的な言語とすることで、下位レベルに完全に依存しないようにできる。