2019/02/06
sponsored link
カプセル状のオブジェクト?
カプセルっていうのは、その中に何かを入れて密閉してしまうような容れ物のことです。知ってますよね。
ではカプセル化というのは、どういうことなのか?
勘違いしてはいけないのは、オブジェクトを大事にカプセルの中に入れるわけではありません。オブジェクトの構造をカプセル状にしてしまうんです。
カプセル状と言うからには、中に何かを入れて密閉するわけですが、一体何をカプセルの中に大事に密閉しておくのか?それはフィールド(パラメータ)です。
全てのフィールドをprivateにすることで、まるでカプセルで覆われたように外(他のクラス)からフィールドの値を触れないように、というか見えないようにしてしまうわけです。
そして、そのカプセルには棒が刺さっています。その棒は動かすことができます。棒をカプセルの外から動かして、カプセルの中身をごちゃごちゃかき回すことができるような形状です。例えていうなら、耳かきみたいなことです。耳の中は見えないですが、耳かきを使って耳垢にアクセスすることができますよね。
一体、なんの話かというと、そのカプセルに刺さっている耳かきのような棒というのはpublicなメソッドの例えです。カプセルの外側からカプセルに突き刺さっている棒(メソッド)を使うことで、カプセルの中身(フィールド)を操作するわけです。カプセルは強固なのでその棒を使ってしか中身にアクセスできません。
気持ち悪い手書きの絵で申し訳ないのですが、カプセルに棒が刺さっていて操作できるというイメージが分かりますでしょうか。
ゲッターとセッターもこの棒の一つです。フィールドをカプセルで覆って(privateにして)、それぞれのフィールドのゲッター棒とセッター棒を刺して、それぞれのフィールドを見たり、操作したり出来る状態にするのがカプセル化の大原則です。
ゲッター、セッターをつければいいってもんではない
しかし、ただ単に全てのフィールドをprivateにして、全てのフィールドに対してゲッターとセッターを作るだけではカプセル化とは言えません。メソッドを介してしかフィールドを触れない状態にするのは必要条件ではありますが、それで十分ではないのです。
例えば、以下のHumanクラスのmanpukudo(満腹度)というフィールドを見てください。
Human.java
public class Human{
private static int count_Human = 0;
private String name;
private int birthday;
int manpukudo;
// 省略
}
上記ではインスタンスフィールドのmanpukudoにはアクセス修飾子がついていません。アクセス修飾子がついていないということは、アクセス制限はデフォルト状態なので、同一パッケージ内からなら普通にアクセスできる状態ですね。(参考:オブジェクトって何? 〔アクセス修飾子とは〕 〔ゲッターとは〕)
ということは、Humanクラスと同一パッケージ内のHumanTestクラスから、以下のようなことができてしまいます。
HumanTest.java
public class HumanTest{ public static void main(String[] args){ Human human1 = new Human("ノブオ",19770101); human1.manpukudo = 200; human1.manpukudo = 0; } }
有無を言わさず満腹度を200にしたり、0にしたり。まるで胃の中に無理やり手を突っ込んでいるような状態です。こういう無茶な行為を防ぐために、フィールドは全てprivateにして、ゲッターとセッターを作ります。
public class Human{ private static int count_Human = 0; private String name; private int birthday; private int manpukudo; // 省略 public int getManpukudo(){ return this.manpukudo; } public void setManpukudo(int n){ this.manpukudo = n; } }
しかし、そのフィールドに値をセットするだけの単純なセッターを付けてしまうと、
HumanTest.java
public class HumanTest{ public static void main(String[] args){ Human human1 = new Human("ノブオ",19770101); human1.setManpukudo(200); human1.setManpukudo(0); } }
セッターを使って、好き放題胃の中をいっぱいにしたり空っぽにしたりできてしまいます。これではせっかくprivateにして直接フィールドを触れなくしても、結局同じですよね。
先ほどの気持ち悪い絵の、カプセルから突き出ていたのはpublicなメソッドの例えです。ゲッターやセッターもその棒の一つです。棒は刺せばいいってもんじゃありません。大事なのは、どんな機能を持った棒を刺すのか?です。余計な棒を刺したばっかりに、カプセルの中身がぐちゃぐちゃにされてしまっては、カプセルで覆った意味がなくなります。
カプセルにどんな棒を突き刺すべきか??
フィールドの性質によっては単純なセッターをつけない方がいいこともあります。満腹度は、セッターによって数値をセットするのではなく、食べれば増えて、動けば減るようにするべきです。なので、eatメソッドと、walkメソッドと、runメソッドを作りましょう。
Human.java
public class Human{ private static int count_Human = 0; private String name; private int birthday; private int manpukudo; // 省略 public int getManpukudo(){ return this.manpukudo; } public void eat(){ this.manpukudo += 60; } public void walk(){ this.manpukudo -= 10; } public void run(){ this.manpukudo -= 20; } }
これでこのHumanオブジェクトの満腹度は、食べれば60増えて、歩けば10減って、走れば20減るようになりました。セッターはないので、満腹度が変化するのは食べる、歩く、走るのいずれかの行動をとった時に限られます。これなら、いきなり満腹度を200にしたり、0にしたりということは出来なくなりました。この方が現実に即していますよね。
ただし、やろうと思えばこんなこともできます。
HumanTest.java
public class HumanTest{ public static void main(String[] args){ Human human1 = new Human("ノブオ",19770101); human1.run(); human1.run(); human1.run(); human1.run(); human1.run(); human1.run(); human1.run(); } }
そんなに走れるかよ。っていう状態です。これだけ走らされればmanpukudoはマイナスになっていることでしょう。けど満腹度がマイナスってどう考えてもおかしいですよね。胃が空っぽの状態でmanpukudoは0、それ以下に下がらないようにしておくべきです。同じように食べるのも限界がありますよね。manpukudoのMAXは100ということにしておきましょう。
Human.java
public class Human{ private static int count_Human = 0; private String name; private int birthday; private int manpukudo; // 省略 public int getManpukudo(){ return this.manpukudo; } public void eat(){ this.manpukudo += 60; if(this.manpukudo > 100){ this.manpukudo = 100; } } public void walk(){ this.manpukudo -= 10; if(this.manpukudo < 0){ this.manpukudo = 0; } } public void run(){ this.manpukudo -= 20; if(this.manpukudo < 0){ this.manpukudo = 0; } } }
これで、満腹度は絶対に0~100までの中で収まるようになりました。しかししかし、この状態では満腹度はマイナスにはならないものの、0になってもいくらでも走り続けられます。それもおかしいですよね。お腹が空っぽの時は動けないようにするべきだし、お腹がいっぱいの時は食べられないようにするべきです。
Human.java
public class Human{ private static int count_Human = 0; private String name; private int birthday; private int manpukudo; // 省略 public int getManpukudo(){ return this.manpukudo; } public void eat(){ if(this.manpukudo < 100){ this.manpukudo += 60; if(this.manpukudo > 100){ this.manpukudo = 100; } } } public void walk(){ if(this.manpukudo > 0){ this.manpukudo -= 10; if(this.manpukudo < 0){ this.manpukudo = 0; } } } public void run(){ if(this.manpukudo > 0){ this.manpukudo -= 20; if(this.manpukudo < 0){ this.manpukudo = 0; } } } }
if文を使って、manpukudoが100の時は、eat()が呼ばれても何もしないようにしました。また同じように、manpukudoが0の時は、walk()が呼ばれても、run()が呼ばれても、何も起こりません。これで、お腹いっぱいの時は食べろと言われても食べないし、お腹ぺこぺこの時は動けない状態になりました。しかし、例えおなかいっぱいでもeatメソッドを呼ぶことはできるし、その結果、食べたのかそれともお腹いっぱいで食べられなかったのか?は分かり様がありません。そこで、戻り値を使ってこんな風にしてみましょう。
Human.java
public class Human{ private static int count_Human = 0; private String name; private int birthday; private int manpukudo; // 省略 public int getManpukudo(){ return this.manpukudo; } public boolean eat(){ boolean result = false; if(this.manpukudo < 100){ this.manpukudo += 60; if(this.manpukudo > 100){ this.manpukudo = 100; } result = true; } return result; } public boolean walk(){ boolean result = false; if(this.manpukudo > 0){ this.manpukudo -= 10; if(this.manpukudo < 0){ this.manpukudo = 0; } result = true; } return result; } public boolean run(){ boolean result = false; if(this.manpukudo > 0){ this.manpukudo -= 20; if(this.manpukudo < 0){ this.manpukudo = 0; } result = true; } return result; } }
それぞれのメソッドの中でローカル変数boolean resultを宣言してfalseで初期化しています。それぞれのメソッドが呼ばれた際、命令どおりに食べる、歩く、走るというのを実行した場合に限りresultにtrueを代入しています。もし、お腹いっぱいで食べられなかった場合や、お腹ぺこぺこで走れなかった場合はfalseのままです。つまり、boolean変数resultは、命令通り行動できたかどうかを示す値が入っているわけです。そのresultを戻り値としてreturnしています。
こうしておけば、メソッドを呼んだ結果、その通り実行されたかどうかが一目瞭然なので、その戻り値を使っていろんなことが可能になります。
HumanTest.java
public class HumanTest{
public static void main(String[] args){
Human human1 = new Human("ノブオ",19770101);
if(human1.run()){
System.out.println(human1.getName() + "は走った");
}else{
System.out.println(human1.getName() + "は空腹の為、走れなかった");
}
}
}
↑こんな風に、メソッドを呼んだ結果によってif文で分岐させたり、
HumanTest.java
public class HumanTest{
public static void main(String[] args){
Human human1 = new Human("ノブオ",19770101);
boolean result;
do{
result = human1.eat()
}while(result);
}
}
こんな風に、do-while文を使って、お腹いっぱいになるまでeatメソッドを呼び続けたりすることもできます。(けど、これだとmanpukudoが100になってからさらにもう一回eatメソッドを呼んでしまっているので、例としては問題ありです。。)
カプセルのもう一つの意味
とまあ、こういうのは一例ですが、フィールドに変な値が代入されることが絶対ありえないように、メソッドを定義するというのは非常に重要なことです。そして、フィールドをいじるメソッドの定義次第で、そのオブジェクト自体の使い勝手も変わってきます。
「カプセル」と言う言葉には、「中身を守る」というニュアンスだけではなく、「かっちり完成されている」というニュアンスも含まれているんだと思います。つまり、そのクラス(オブジェクト)自体が完成された一つの部品として働くように設計するということこそが「カプセル化」という言葉に含まれている大事なポイントです。
外部との接触点であるpublicなメソッド(カプセルから突き出ている棒)や、またそのクラス内だけで使うprivateなメソッドによって、カプセルの中のフィールドを完璧に操作できる状態になるようにクラスを設計するのが大事です。クラス単体が完璧な設計をしてさえいれば、複数のクラスを作用させて組んだプログラム全体も、完璧な状態になるはずだ。というのがカプセル化というプログラミング手法の目指すところです。
カプセル化はオブジェクト指向の根っこを支える超重要な概念です。外部(ほかのクラス)からフィールドにおかしな値が入れられたり、つじつまの合わない状態にされることが絶対ないように頑丈なカプセルで守られたクラス設計を心がけましょう。
実はちょっと気に入ってるのでもう一度載せておきます。
今だけ→転職できなければ全額返金の「エンジニア転職保証コース」
絶対エンジニアになる!→テックエキスパート
フリーランスエンジニアの収入例を見てみる→レバテックフリーランス
コメント
本当にわかりやすくて助かります。
これまで見てきたどの本・サイトよりも分かりやすく解説して頂けました。
抽象メソッドとか、インターフェースとかも書いて頂きたい!
更にもうちと応用も書いて頂きたい!
ありがとうございます。
by tomo 2014/07/30 16:13
>これまで見てきたどの本・サイトよりも分かりやすく解説して頂けました。
そこまでおっしゃっていただけるとは嬉しすぎます。ありがとうございます。
>抽象メソッドとか、インターフェースとかも書いて頂きたい!
サイトの完成度という意味では、継承やらインターフェイスやらも書きたいのですが、そういうのって実際大規模なシステムを組んだりした経験がないと、ツボを外した解説になってしまうんじゃないかと思って手を出していません。とりあえずは自分の守備範囲内で頑張ります。(^^;)
嬉しいコメントありがとうございました。
by Nobuo@管理人 2014/07/30 23:53
最初から通して読みました。
javaを本格的に勉強するきっかけになりそうです。
ありがとうございます。
by 匿名 2014/08/01 00:19
最初から最後まで読んでくれたんですか??
ありがとうございます。書いた甲斐がありました。
匿名さんがどんなに偉大なエンジニアになっても、初めの第一歩は「一番かんたんなjava入門」だったことを忘れないで頂けると幸いです。
by Nobuo@管理人 2014/08/01 10:24
コメントしていらっしゃるお二人に同感です。
親切なサイトを作ってくださって、本当にありがとうございました。
Nobuoさんのおかげでこれから頑張るぞという気持ちになれました。
by nobuko 2014/08/08 08:38
>Nobuoさんのおかげでこれから頑張るぞという気持ちになれました。
頑張って優秀なエンジニアになってください!
読んでいただいてありがとうございました。
我ながらいいサイトを作ったものだ。^^
by nobuo@管理人 2014/08/08 09:28
他のサイトや本で理解できなかったことが、すんなり理解できました!
本当に感謝です!
ぜひ応用もお願いいたします!
by 匿名 2014/08/10 00:43
>他のサイトや本で理解できなかったことが、すんなり理解できました!
そんな風に言っていただけるのが一番嬉しいです。ありがとうございます。
by nobuo@管理人 2014/08/10 12:20
プログラミング初心者の僕がこのJavaのおかげでやっと入り口に入れたかなと思いました。ありがとうございます。勝手ながらブログにこのサイトで勉強始めたと書かせていただきました(>_<)
僕も頑張ります!また是非別のジャンルでもこのような入門サイトを作っていただけたらと思います。初心者の心をわかったひとが一番教えるのがうまいと思ってます!
by スリーモンキー 2014/08/12 17:00
>勝手ながらブログにこのサイトで勉強始めたと書かせていただきました
どうぞ宣伝してもらえると嬉しいです。^^
嬉しいコメントありがとうございました。
スリーモンキーさんもブログ頑張ってくださーい。
WordPressでやるならPrincipleもよろしくです!
by Nobuo@管理人 2014/08/12 22:46