オブジェクト指向に関する世界観(B.考え方2)です。オブジェクト指向も参照のこと。
(以下の文章は、オブジェクト指向についての知識や経験の浅い自分が書いたため、一般的な良識と比較して何も分かっていません。注意してご覧ください。)
オブジェクト指向とは要するに、グローバル変数や関数を、同じ「クラス」と呼ばれる仕組みにまとめる、ということです。
C言語では、たとえばスタックのデータ構造を表すのに、関数push()とpop()を用意し、その関数の中だけで操作されるグローバル変数val[]を定義します。
しかしながら、これはひとつのStackクラスにまとめて、使う時に必要なパッケージ化されたグローバル変数(とでも呼ぶべきものであり、オブジェクト指向の用語ではインスタンス)を一度に「生成」し、Stack.push()やStack.pop()のような「メソッド」から操作するのです。
このようにすることで、データを「隠蔽・保護」することができます。どの関数からでも、クラス内部のインスタンス変数にアクセスするためには、アクセスの許されているメソッドからしかアクセスできません。
また、このようにすることで、継承やオーバーライドを用いて、クラスに対して「別のユーザーが機能を追加しやすくなる」というメリットがあります。新しいNewStack.print()メソッドを定義することが、もとのソースファイルを編集することなしに可能となるのです。
ゲームなどを作るのであれば、プライヤーはPlayerクラス、戦闘画面はBattleGroundクラス、攻撃はAttackクラス、エフェクトはEffectクラス、判定はJudgeクラスで行います。
このようにすることで、大規模なソフトウェア開発が可能になる、というメリットが、オブジェクト指向にはあります。ひとつひとつのクラスで処理や管理が完結し、たくさんのオブジェクトを組み合わせることで、バグのない大きなプログラムを開発できるのです。そのためには、名前空間や例外などの機能も活用できるでしょう。
また、オブジェクト指向におけるクラス階層や継承については、ある意味、「機能を持ったデータベース」のようなものです。たとえば、ものクラスであるObjectがあり、次に生き物であるLifeクラスがObjectクラスを継承し、その上で植物であるPlantクラスがLifeクラスを継承します。このようにすることで、PlantクラスはLifeクラスの全ての機能を持ち、LifeクラスはObjectクラスの全ての機能を持ちます。このようにすることで、「同じふるまいなら同種の機能を持つはずだ」という仮想関数によるポリモーフィズムが可能となります。それぞれが適切な機能を持つようにするだけで、データと機能そのものを管理しやすい形にすることができます。
後日注記:少し説明がおかしいところがあるかもしれません。概念的な話をすると、従来プログラムとデータがそれぞれ分かれていて、「プログラムがデータを操作する手続きとして存在する」モデルだった手続き型プログラミングが、オブジェクト指向においては「データの中にプログラムが入り込み、データのふるまいとしてプログラムが存在する」という考え方になるのです。これは従来のプログラミングとはまったく異なる考え方であるため、「オブジェクト指向とはなんぞや」という本がたくさん出版されながら、「オブジェクト指向はよくわからない」という人を大量に生み出す結果となりました。
後日注記:また、オブジェクトのインスタンスが生成される、というのがポイント。メモリ上にプログラムが1つ2つとロードされるように、オブジェクトのインスタンスが1つ2つと生成される。これにより、Stackクラスのインスタンスst1とst2は別々のスタックオブジェクトとして使用できる。そのため、オブジェクトのインスタンスは構造体変数に近い。
オブジェクト指向について。
オブジェクト指向は、大規模な開発を行う時に用いられるプログラミングの考え方である。
クラスベースのオブジェクト指向言語では、クラス(オブジェクトの設計図)の中にメンバ変数とメンバ関数(メソッド)、そしてアクセス指定記述を書く。
これは、オブジェクト(対象)に対してメソッド(方法)を記述する、と言う、抽象化と記述の方法であり、作法である。
たとえば、C言語のprintf()をオブジェクト指向言語のC++で実装するなら、文字を表示するための個々が個別に持つ文字列をメンバ変数(対象)とし、表示するため抽象的な方法を処理としてメンバ関数(方法)に書くことが出来る。そして、
CStringFormat str = new CStringFormat(); str.set("Hello, I am Assy.\n"); str.print();
とすれば、オブジェクト指向的だ。
関数の引数の型をオブジェクトと同じように設定することで、クラスと同じことは出来る。だが、それだと、派生クラスを作ることが難しい。オブジェクト指向なら、継承をすることで、一部の処理だけを書き換えた新しいメソッドやプロパティを持つ派生クラスを作ることが出来る。
C言語はオブジェクト指向言語ではないが、GNOMEではGLibと言うC言語でオブジェクト指向を実現するライブラリを開発することで、Cでオブジェクト指向を行っている。その上で、色んなプログラミング言語に対応するCORBAを使い、各言語のフロントエンドを作ることで、C++やC#やPythonなど、たくさんのオブジェクト指向言語による記述に対応している。
さらにオブジェクト指向を発展させると、「コンポーネント」と言う利用方法が生まれる。これは、オブジェクト(コンポーネント)の内部に全ての外部からの処理に対するメソッドとプロパティを実装することで、コンポーネントの外部と内部を分け、外部からの操作と内部の対応処理による「操作」によってプログラムを記述する。そうすることで、コンポーネントを再利用可能にし、大規模な開発(たとえば巨大なGUIアプリケーションの開発)に対応している。
また、アクセス指定記述によって、クラスの内外からの操作(オブジェクトの呼び出し元や派生クラスのメンバ関数)による不用意なメンバ変数の書き換えを防いでいる。メンバ関数にプロパティ(setやget関数)を設定することで、メンバ関数の取得・変更処理もスマートに出来る。これを、「カプセル化」と呼ぶ。
これらのような技術は、GUIによる開発を行う上で、考え方として使われる。特に、ツールキットライブラリやコンポーネント、ドキュメント・ビュー・アーキテクチャなどをGUIで行う場合、メニューやボタンなどをGUIに表示したり、プログラムの内外から操作したりする時に、「安全かつ柔軟に操作」することが出来る。
オブジェクト指向言語は、C++、Objective-C、Java、C#、VB.NET、Python、Ruby、Smalltalkなどがある。
最近は、多くのプログラミング言語で、オブジェクト指向の仕組みを提供している。この、「オブジェクト指向が分からない」という人間が多い。
簡単に言ってしまえば、オブジェクト指向とは、データの共有と作成・破棄・取得・変更の仕組みである。
1.クラスは、メンバ変数とメソッドを持つ。クラス内のメソッドの中で、メンバ変数は共有される。
2.クラスは、インスタンスとなることで、作成・破棄される。また、一度インスタンスになったクラスのメンバ変数は、アクセサやプロパティを通じて、取得・変更される。
3.クラスは、一度作られた既存のクラスを継承し、メソッドを追加したり、枠組み(インターフェース)を実装することができる。
4.クラスは、実装の詳細を知らなくても、外部向けに公開されているインターフェースを使って、カプセル化して安全に取り扱うことができる。
つまり、オブジェクト(もの)として取り扱うことで、考えるべきことを事前に考えておき、メソッドの間で簡単にデータを共有し、使用する時にそれらのデータを簡単に作成し、破棄することができるような、そういう枠組みを作ってプログラミングを行うのが、オブジェクト指向である。
C++における動的なインスタンスにおけるメソッドの呼び出しや、PHPでは、アロー演算子(->)が良く出てきます。
僕は、このアロー演算子をどのように感覚で捉えて良いのか、良く分からず、アロー演算子が苦手でした。全てのオブジェクト指向言語は、アロー演算子ではなく、.(ドット)を用いてメソッドの実行を記述するようにしてほしかったです。
ですが、僕はこのアロー演算子の意味が、最近ようやく分かりました。それは、このアロー演算子における変数は、「クラスのインスタンスである」ということを認識したからです。
アロー演算子を、ポインタから関数が実行される、と認識している間は、分かっていません。これはインスタンスである変数から関数へと「変数の中身を送り出している」と考えれば分かるのです。
たとえば、
CString *pcStr = new CString; *pcStr = "Test"; int n = pcStr->Find("T");
などと書いた時、pcStrという「インスタンス変数」を、Find()という「メソッド」に送り出している、そういう感覚でこの記述をつかめば、アロー演算子を直観的に捉えられます。
インスタンスハンドルは、C++のようなオブジェクト指向言語だけではなく、CによるWindows APIなどでも出てきます。オブジェクト指向の本質は、インスタンスハンドルにあるのです。
C++やJavaなどのオブジェクト指向を、「構造体の中の関数を呼び出す」と理解している人が多いと思います。
ですが、そのように考えると、いまいち、なぜオブジェクトのメソッドを呼び出して、オブジェクトそのものを操作するのか、ということが、分かりづらいと思います。
実際のところ、Smalltalkを参考にしたクラスベースのオブジェクト指向言語では、オブジェクト指向は、「レシーバ」「メソッド」「メッセージ」の関係だと表現できます。
レシーバとは、メッセージを受ける「受け手」のオブジェクトのこと。
メソッドとは、レシーバ・オブジェクトに働きかけるメッセージのことで、C言語などの構造化プログラミング言語では「関数」と呼ばれるものです。
そして、レシーバのメソッドを実行することを、「レシーバにメッセージを与える」と表現します。
そして、そうしたオブジェクトはクラスから作成します。クラスは、データ型の構造と関連するメソッドをまとめたものであり、基本のクラスから派生のクラスがひとつの「継承」関係の「階層構造」になります。
クラスには、あらかじめ決められたメソッドでしか操作することはできません。これを「カプセル化」や「隠蔽」と呼びます。
自分でデータ型を作る時は、既存のクラスを自分で継承します。自分でデータ型を追加したり、メソッドを追加したり、上書きすなわち「オーバーライド」することができます。
そう、オブジェクト指向とは、そうした「プログラムのリソースや方法を全てモノとして扱う」ということです。オブジェクト指向は、直訳すれば「もの指向」です。