【初心者向け】クラスのカプセル化について

プログラミング初心者プログラミング

前回はクラスの基礎編を記載してみました。
前回の内容が前提知識になるので、見てない場合は確認してみてください。

今回はクラスのカプセル化ついて書いています。
こちらではクラスのカプセル化を学んだ後に、実際にjavaのコードを見て、カプセル化について理解していきます。

それでは、クラスのカプセル化について確認していきましょう。

クラスのカプセル化とは?

クラスのカプセル化とは?

クラスのカプセル化はデータを隠蔽して、簡単にアクセスできないように保護することです。
クラスのフィールドやメソッドにアクセス制限をかけて、インスタンスを作成したときに必要のないアクセスやデータ変更を避けるようにします。

一般的にはフィールドをprivateにして、publicなメソッドからアクセスできるようにします。
すると、仕様変更に柔軟に対応できて、バグが起きづらいクラスが出来上がります。(※具体的にはコードの方で後述しています。)

例えば
「Aクラスのメソッドは、Bクラスからは呼び出せるけど、Cクラスから呼び出せない」というようにしたり
「Aクラスのフィールドの内容は、誰でも呼び出せるようになっているが、書き換えはできない」という感じの実装が可能になります。

カプセル化をしたコードを見てみよう

カプセル化をしたコードを見てみよう

それでは、実際にコードでみていきましょう。
前回このように動物(Animal)のクラスを作成しました。

class Animal {
    private String genericName;
    private String name;
    private String voice;

    Animal(String genericName,String name,String voice) {
        this.genericName = genericName;
        this.name = name;
        this.voice = voice;
    }

    public void showVoice() {
        System.out.println("我輩は"+ this.genericName + "である。名前は" + this.name + "。鳴き声は" + this.voice + "!");
    }
}

フィールドがこのように定義されていますが、それぞれprivateになっています。
privateだと同じクラス内でしか呼べない状態になります。
このprivateやpublicはアクセス修飾子と呼ばれます。

private String genericName;
private String name;
private String voice;

名前が下記のようにpublicだったを考えます。

public String name;

publicにすると、インスタンス化されたオブジェクトから直接呼べてしまいます。

Animal animal = new Animal("猫", "たま", "にゃー");
System.out.println(animal.name); // 「たま」が出力される。

animal.name = "ほげ"; // 「ほげ」が格納される。

クラスのフィールドへの設定や参照が直接行われてしまいます。
一見便利なように見えますが、これができてしまうと、予期せぬ不具合や仕様が変わるときに大変になってしまいます。

publicのままの場合だと、nameのフィールドには好きなようにデータが格納できるので、長すぎる名前や記号などが格納できてしまいます。
カプセル化して、渡す値をチェックすることで格納するデータを制限することが可能です。

具体的にはAnimalクラスにこのようにpublicなメソッドを作成します。
このように値を設定したり、取得したりするメソッドはよく、getter(ゲッター) / setter(セッター)と言われます。

class Animal {
    // ----- 省略 -----
    public void setName(String name) {        
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

このセッターメソッドに名前が長すぎる場合は、設定できないようにしておけば防ぐことができます。
10文字以下じゃない場合は、設定できないようにしました。

public void setName(String name) { 
    if (10 > name.length()) {
        this.name = name;   
    }
}

こうすることで、呼び出し側でこのように長い名前を設定しても無視されるようになります。

Animal animal = new Animal("neko", "tama", "nyaaa");

animal.setName("tamaaaaaaaaaaaaaaaa");
System.out.println(animal.getName()); // tamaが出力される。

ここでは、名前が長すぎるので、setNameメソッドは無視されて、「tama」が出力されます。
このように、Animalクラスの名前を使用するときには予期しない値を設定されることを防げるようになりました。
こうすることで、変な値が設定されないようになるためバグが発生しずらくなります。

仕様変更が発生した時を考えてみます。
例として、名前の最後に敬称(xxxさん)をつけて、返してあげることになったとします。

直接名前を取得している場合は、このように取得したインスタンスの名前に敬称を連結させて、つけてあげる必要があります。
クラスをインスタンス化した先で、全てのフィールドの名前に敬称の連結が必要になってしまいます。。

animal.name = animal.name + "ちゃん。";

これがゲッター(getter)を使用されている場合だと、クラスのゲッターメソッドを変更するだけで対応できます。

public String getName() {
    return this.name + "ちゃん。";
}

クラスに、このように書くだけでクラスをインスタンス化して、名前を取り出している箇所は全て対応できました!

カプセル化することで、下記の2点が確認できたかと思います。
・仕様変更に強くなる
・バグが発生しずらくなる

アクセス制限(アクセス修飾子)の種類について

アクセス制限をかけるアクセス修飾子には、下記のようなものがあります。
下記はjavaの場合です。言語によっては増えていたり、なかったりします。

アクセス修飾子内容
publicすべてのクラスからアクセス可能
private現在のクラスのみアクセス可能
protected現在のクラスとサブクラス(継承先)からアクセス可能

protectedにはサブクラスとありますが、これは今のクラスを継承したサブクラスはアクセスできます。
サブクラスについては、次回の記事で書きます。

おわりに

今回はクラスのカプセル化について見ていきました。
カプセル化のイメージは掴んでいただけましたでしょうか。

次回は、クラスと言ったら継承。
継承について見てみましょう。

コメント

タイトルとURLをコピーしました