オブジェクト指向のメリット (カプセル化編)

Eyes, JAPAN では、ソフトウェア開発には専ら C++, Java, Ruby などの オブジェクト指向プログラミング言語を用いています。(一部例外もありますが。)

ところで、「"オブジェクト指向" とは何か?」と問われたとき、「○○○○のことだ」とはっきり答えることのできる方はいないのではないでしょうか?
それもそのはず、「オブジェクト指向 (プログラミング)」というのは、

といった概念・機能 (の一部または全部) を取り込んだプログラミング手法の総称であり、厳密な定義は存在しないのです。
とはいえ、これらの概念・機能はプログラムの構造を整理し、保守性 (可読性, 再利用性) を向上させるには非常に有用です。
個人的には、この「オブジェクト指向」というのは、プログラム (特に大規模なもの) を作成するための「本質的に正しい」方針だと考えています。

今回は、その中でももっとも分かりやすい (目立つ) 機能である「カプセル化」について、その概要とメリットを説明してみたいと思います。

カプセル化を行わない場合

例えば、次のような構造体があったとします。

#define MAX_LEN 32

typedef struct {
    char acName[MAX_LEN + 1]; //氏名
    int  nHeight;             //身長 (単位: mm)
    int  nWeight;             //体重 (単位: 0.1kg)
} Student;

名前 (acName) は MAX_LEN (32) 以内の文字列でなければなりません。
また、身長, 体重 (nHeight, nWeight) も負の値になっては困ります。
そこで、これらのメンバの値を設定する次のような関数を作ったとしましょう。

//氏名の設定
bool setName(Stunden* lpStudent, const char* lpszName){

    if (!lpStudent) return false;
    if (!lpszName) return false;

    int nLen =strlen(lpszName);
    if (nLen > MAX_LEN) return false;

    memmove(lpStudent->acName, lpszName, nLen + 1);

    return true;
}

//身長の設定
bool setHeight(Stunden* lpStudent, int* nHeight){

    if (!lpStudent) return false;
    if (nHeight < 0) return false;

    lpStudent->nHeight =nHeight;

    return true;
}

//体重の設定
bool setWeight(Stunden* lpStudent, int* nWeight){

    if (!lpStudent) return false;
    if (nWeight < 0) return false;

    lpStudent->nWeight =nWeight;

    return true;
}

確かに、氏名や身長・体重の値を設定する際に必ずこれらの関数を使うようにしていれば、制約が破られることはないでしょう。
しかし、acName, nHeight などのメンバ変数はすべて公開されているので、直接値を代入することもできてしまいます。

Student s;

//正しい設定の仕方
setName(&s, "二階堂 昭夫");
setHeight(&s, 1750)

//不正な設定の仕方 (でもコンパイルは通ってしまう)
s.nHeight =-790;

カプセル化を行えば

しかし、カプセル化によるデータの隠蔽を行えば、そうした問題を解決することができます。

Student.h
class Student {
public:

    //member access (getting)
    bool SetName(const char*);
    bool SetHeight(int);
    bool SetWeight(int);

protected:

    char m_acName[MAX_LEN + 1]; //氏名
    int  m_nHeight;             //身長 (単位: mm)
    int  m_nWeight;             //体重 (単位: 0.1kg)

};
Student.cpp
//氏名の設定
bool Student::SetName(const char* lpszName){

    if (!lpszName) return false;

    int nLen =strlen(lpszName);
    if (nLen > MAX_LEN) return false;

    memmove(m_acName, lpszName, nLen + 1);

    return true;
}

//身長の設定
bool Student::SetHeight(int* nHeight){

    if (nHeight < 0) return false;

    m_nHeight =nHeight;

    return true;
}

//体重の設定
bool Student::SetWeight(Stunden* lpStudent, int* nWeight){

    if (nWeight < 0) return false;

    m_nWeight =nWeight;

    return true;
}

今回紹介したのは「データ隠蔽」の例ですが、この他にも、クラス内部だけで使用する関数や型などのデータを外部からアクセスできないよう隠蔽する手法もあります。 これにより、変数や関数に対するアクセス可能範囲を制限することで、プログラムの複雑さを減らすと同時に、不正なアクセスをコンパイラを検出させることができるようになります。

成田