なぜ調べたいと思ったか?
クラスのフィールド変数に、アクセスを制限する修飾子をつけてカプセル化してきた。ただ、アクセスをどうやって制限しているかが分からなかった。
カプセル化をイメージすると・・
まずは、薬局にあるカプセル状の薬をイメージしてみましょう。
画像参照元:https://webpia.jp/encapsulation/
・液体や粉状の薬を、容器で包んだもの
をイメージしたのではないでしょうか?
実はプログラミングのカプセル化も、その様なイメージなんです。オブジェクト指向のカプセル化を理解するには、以下の3ステップの理解が重要だと思っています。
STEP1 :「何を包んで何から守るか?」
STEP2 :「カプセル化したデータを変更するには?」
STEP3 :「データを直接変更してはいけないのはなぜ?」
STEP1:何を包んで何から守るか?
「オブジェクトにあるデータ」を包んで、「オブジェクト外からの不正アクセス」から守るのが答えですが、これでは理解できません。カプセル化するとしないで、どんな違いがあるのでしょうか?
そこでJava言語を例に、カプセル化の機能を持つprivate修飾子と、その機能を持たないpublic修飾子で、どんな違いがあるのか調べてみました。
class Car {
private String name;
private String color;
public int distance = 0;
public int fuel = 100;
}
「name」と「color」はデータ1、「distance」と「fuel」はデータ2に該当します。
画像参照元:https://webpia.jp/encapsulation/
データ1はカプセル化によって、オブジェクト外からのアクセスを禁止していますが、データ2はアクセス出来てしまいます。
「オブジェクト外からのアクセスを禁止すると、カプセル化したデータを変更出来ないんじゃ・・」
と思いますが、そんな事はありません。
STEP2:カプセル化したデータを変更するには?
データをカプセル化したなら、カプセル化していない関数を用意して、データを変更すれば良いんですね。
class Car {
private String name;
private String color;
public int distance = 0;
public int fuel = 100;
Car(String name, String color) { # Carクラスの生成と同時に、カプセル化したデータを代入)
this.name = name;
this.color = color;
}
public String getName() { # カプセル化したデータのnameを取得
return this.name;
}
public String getColor() { # カプセル化したデータのcolorを取得
return this.color;
}
}
クラス名が同じのコンストラクタで、カプセル化したデータを変更しています。
画像参照元:https://webpia.jp/encapsulation/
そして、getから始まるゲッタメソッドで、カプセル化したデータを取得できる様になりました。
関数を用意してデータを変更できましたが、ここで最大の謎である「データを直接変更していけないのはなぜ?」が浮かんできませんか?
STEP3:データを直接変更してはいけないのはなぜ?
class Car {
private String name;
private String color;
public int distance = 0;
public int fuel = 100;
}
数値型データを例にすると、データを直接変更してはいけない理由がわかると思います。
燃料満タンの意味で、「fuel」に100を代入しました。
ところが、負数や100より大きい数値が代入され、データの整合が取れなくなりました。
オブジェクト外から自由にデータ変更できる状態だったのが理由なので、
・データのカプセル化
・データの許容を制限する関数の用意
が必要なんですね。以上のことを踏まえて、Carクラスの内容を変更しました。
class Car {
private String name;
private String color;
private int distance = 0;
private int fuel = 100;
Car(String name, String color) {
this.name = name;
this.color = color;
}
public String getName() {
return this.name;
}
public String getColor() {
return this.color;
}
public int getDistance() {
return this.distance;
}
public int getFuel() {
return this.fuel;
}
# ガソリンを消費して車を走行する
public void run(int run_distance) {
System.out.println(run_distance + "km走ります");
if (run_distance <= this.getFuel()) {
this.distance += run_distance;
this.fuel -= run_distance;
} else {
System.out.println("ガソリンが足りません");
}
System.out.println("走行距離:" + this.getDistance() + "km");
System.out.println("ガソリン量:" + this.getFuel() + "L");
}
# ガソリンを補充する
public void charge(int fuel_amount) {
System.out.println(fuel_amount + "L給油します");
if(fuel_amount <= 0) {
System.out.println("給油できません");
} else if((this.fuel + fuel_amount) >= 100) {
System.out.println("満タンまで給油します");
this.fuel = 100;
} else {
this.fuel += fuel_amount;
}
System.out.println("ガソリン量:" + this.fuel + "L");
}
}
・ガソリンを消費して車を走行するメソッド
・ガソリンを補充するメソッド
上記のガソリンを補充するメソッドに注目すると、「負数を許可しない」「燃料満タンを超えての給油はできない」といった制限がありますね。
調べてみて分かったこと
「カプセル化は、オブジェクト外からデータを直接変更するのを禁止する」とあったけど、想定していない数値を設定するケースを考えたら、その意味がようやく分かりました。