オブジェクト指向を理解するまでの軌跡

オブジェクト指向を正しく理解する

ブックマーク数の多さに驚きました.“オブジェクト指向を理解したい”と思っている人は,まだまだ多いのだなと改めて感じました.せっかくなので,今日は私がオブジェクト指向というものを理解するまでの軌跡を書き綴ってみようと思います.

一関数で完結させることの限界

オブジェクト指向が流行る事となった原因は,多くのプログラマが“一つの機能を一つの関数で完結させることに限界を感じてきた”からであると思います.

私がこの限界を感じた例として,Cの標準ライブラリで提供されている関数char* strtok(char *str, const char *delim)があります.この関数は,strで与えられた文字列をdelimを区切り文字として分割する,という関数なのですが,いささかトリッキーな使用方法になっています.具体的には,以下の通りです.

  • 最初にこの関数を呼び出す時には,strに分割して欲しい文字列をセットする
  • 2回目以降は,strにはNULLをセットする

なぜこのような方法を取っているかというと,“与えられた文字列を区切り文字で区切って,その結果(トークン)を利用者に返す”という機能を一つの関数で完結させようとしたから,であると思います.様々な機能を提供していくにつれて,一つの機能を一つの関数で完結させようとすることには限界が存在することが分かってきました.

機能の細分化

この問題が深刻になってくると,“一つの関数で完結させる”ことは諦めて,“複数の関数で一つの機能を実現する”というアプローチが取られるようになってきます.例えば,strtok()では,

  • 分割して欲しい文字列を設定する
  • 区切り文字を設定する
  • 次のトークンを受け取る

という3つの機能に分けられます.そこで,それぞれの機能に対して

  • void strtok_init(const char* str);
  • void strtok_delim(const char* delim);
  • void strtok_next(char* dest);

という3つの関数を定義し,利用者に“この機能を使うときは,最初にstrtok_init()strtok_delim()を呼び出して,それぞれ文字列をセットして下さい.そして,その後はstrtok_next()を使って結果(トークン)を取得して下さい”という取り決め(というか使用方法)を知らせます.こうすることで,先ほどのトリッキーさは薄れ,場合によっては柔軟性も持たせることができます.

しかしながら,ここで次の問題が発生します.“一つの機能を複数の関数で実現する”ということは,前の関数で呼ばれた時の情報をどこかで覚えておかなければなりません(上の例では,strtok_init()で渡された文字列とstrtok_delim()で渡された区切り文字).これに対して,最もシンプルな方法は,“グローバルな変数を持っておく”ということです.グローバル変数はどの関数からもアクセスすることができますので,最初に呼び出された関数が設定して,次に呼び出された関数がその値を読み取りに行く,といった形で実現することができます.ですが,これにも問題があります.それは,グローバル変数は一つのプログラムで共有しているので,その機能を同時に使えるのは一つ(前の利用者が使い終わるまで使えない)と言うことです.

コア情報という概念

そこで,コア情報という概念が生まれます.コア情報とは,その機能を実現する際に必要となる変数(記憶しておかなければならない情報)をまとめて,一つの構造体で定義したものです.strtok()の例で言うと,以下のような感じでしょうか.

struct strtok_inf {
    char* source; // 分割して欲しい文字列
    char* delimiter; // 区切り文字
    char* current; // sourceのどこまで解析したか
};

そして,関数の定義も次のように変更してどんな時でも必ずこのコア情報を渡してもらい,“その構造体(コア情報)を中心とした,その構造体のメンバ変数に変更を加える関数群”という形態を取ることによって,先の問題を解決します.

  • void strtok_init(strtok_inf* inf, char* str);
  • void strtok_delim(strtok_inf* inf, const char* delim);
  • void strtok_next(strtok_inf* inf, char* dest);

Cで私が今までに触った中では,gtkなどはこの形態を取っていたように思います.

クラスという概念の完成

ここまでで,問題点はそれなりに解決したことになります.ですが,どの関数にもstrtok_inf* infという訳の分からない引数が存在するのは,やはり何だかスマートではありません.また,この形態だと利用者側が構造体(今回は,strtok_inf)がどうなっているのかを知っている場合には,その情報を勝手に弄られてしまいます.

そこで,逆に構造体の方へそれに関する関数(メソッドと呼ばれる)を埋め込んでしまおう,という考えが生まれます.これらの事(メンバ変数などへのアクセス規制など)を考慮して,最終的にクラスという概念が生まれます.

class strtok {
public:
    void init(const char* str); // この辺は,コンストラクタの役目か
    void delim(const char* delim);
    void next(char* dest);

private: // コア情報には触れさせない
    char* source; // 分割して欲しい文字列
    char* delimiter; // 区切り文字
    char* current; // sourceのどこまで解析したか
}

以上が,私がオブジェクト指向というものを理解するまでに至った軌跡です.この辺は,文章で読んでもやはり分かりにくいものがあるので,一度壁にぶつかって実感するのが良い,と思っています.願わくば,趣味の間にその壁にぶつかって実感できることを.