日々常々

ふつうのプログラマがあたりまえにしたいこと。

カプセル化

今更ながらカプセル化とは何か、と言うところから書いてみる。読んで字のごとく、カプセルっぽくする事です。
では、カプセルとは何かと言う事を思い浮かべてみます。ぱっと思いつく薬のカプセルは、中に粉末とか粒とかが入っていて、そのままだと不味いとかの理由で飲み辛かったり、分量を量るのが面倒だったりするのを、とりあえず飲み込めば良いという形にしてくれます。他のカプセルとなると、カプセル自動販売機*1の商品になりますか。このカプセルは、複雑な形をしてたり、組み立てる必要があったり、大きさに違いがある玩具を一つのカプセルとして扱い、販売機からみると全部同じものとして扱っています。
これらの事から考えると、カプセル化のメリットが見えます。カプセルはカプセルのまま扱える、と言う事です。
カプセルの事を把握した上で、入門書とか初心者向けの説明で見られる「カプセル化とは、フィールドをprivateにし、それらにgetter/setterを作る事だ」と言う解説を振り返ると、カプセルとはあまり関係の無いお話になっている事がわかります。最終的にフィールドの隠蔽はカプセル化に通じる事は通じるのですが、カプセル化とはフィールドの隠蔽の事である、と言うのは流石に無茶かと思います。


では戻って、カプセル化とは何か。カプセル化の何が嬉しいのか。OOPにおけるカプセル化は、オブジェクトをカプセルのように扱う事を指します。つまり、オブジェクトの中身が何であろうと、一つのカプセルとしてみた場合に同じように扱えると言う事です。
まず、データの隠蔽によるカプセル化について書いてみます。これには先に挙げたフィールドの隠蔽を指すと思ってとりあえずは問題ありません。フィールドを非公開とし、getter/setterを作成することは、そのオブジェクトの持つフィールドそのものを、オブジェクトを使用する側が意識する必要がなくなります。例を挙げるとこんな感じ。

// とあるクラス内
private String field;

public void setField(int i) {
    field = Integer.toString(i);
}

public int getField() {
    return Integer.parseInt(field);
}

相変わらずセンスの無い例だけど勘弁して下さい。このクラスを使用する側にとって、fieldが実際はStringである事は意識する必要が有りません。intだと思っているかもしれませんし、全く別のものだと思っているかもしれません。それで全く問題がありません。ここでfieldの型が別のものに変わったとしても、このクラスを使用する他のクラスに影響を与えず修正する事が出来ます。また、fieldがprivateであるとおり、実際にfieldが存在しなくてもgetter/setterで操作できるデータが存在しさえすれば良いので、フィールドで無ければならない必要も有りません。データの隠蔽イコールフィールドの隠蔽で無い理由はここにあります。これが一番広く知られているであろう、データの隠蔽によるカプセル化になります。
でもこれって、別にオブジェクト指向言語じゃなくても出来ますよね。非OOPの枠でカプセル化を考えると、ここに詰め込むしかなくなります。結果として、OOPの解説でカプセル化とはデータ隠蔽だと言うのが出来上がってしまったのでしょう。

次に、オブジェクトの型の隠蔽および振る舞いの隠蔽によるカプセル化について。これには継承に対する理解が必要です。あるオブジェクトをその親の型として扱う事で、そのオブジェクトの本来の型を隠蔽します。例はを挙げるとこんな感じ。

// とあるクラスのとあるメソッド内
Object obj= new MyClass();
obj.toString();

// MyClass.java
public class MyClass {
    public String toString() {
        return "何かそれっぽい文字列を返す";
    }
}

1行目で生成したMyClassのインスタンスは実際はMyClass型ですが型が隠蔽されてObject型となっています。2行目で実行されるtoString()はMyClassのメソッドが実行されますが、Objectのメソッドに隠蔽されています。インスタンス生成が全く別の箇所にあれば、MyClassのtoString()が実行されるだなんて判りません。ここでの型や振る舞いの隠蔽は、継承とオーバーライドによって実現しています。例を見れば判ると思いますが、このカプセル化は知らなくてもOOPLを使っていれば自然と使っていると思います。ですが、これを意識して使うのはそこそこ難しいです。型および振る舞いの隠蔽を行うには、隠蔽するための継承を行う必要があり、作成したクラスを使用する側に公開する情報を決定する必要があります。特に規模が大きくなり、使用する側が遠くなればなるほど必要な情報が何でどういう形で提供するのが良いかが見えづらくなります。作ったは良いが、全く使えないって事にもなりまかねません。反面、きちんと使えれば、作成したクラスを使用する側がどこまで遠くても全く問題が無いというメリットもあります。


OOPにおけるカプセル化は、オブジェクトのあらゆる情報をカプセルの中に隠蔽する事を指します。ここで言う情報とはデータではなく、型や振る舞いも情報に含まれます。下手に情報をデータと捕らえ、さらにデータをフィールドと一面だけの解釈を行った結果、カプセル化=フィールドの隠蔽になったりするから困ったもの。
誤解しちゃいけないのは、OOPによって作る側が作成時点で楽になる事は殆ど有りません。単純に作るだけならOOPじゃ無いほうが楽なくらいだと思います。散々言われてますが、継承したら親クラスのメソッドがそのまま使えるから楽だ、と言うのはとんでもない誤解です。楽になるのは作る側ではなく、使う側。もっとも使う側にそれを作った人も含まれるわけなんですが。使われる事を前提として行うプログラミングがオブジェクト指向プログラミングだったりするわけで。

*1:ガチャガチャやガチャポンと呼ばれるアレ