一番かんたんなJava入門

これからJavaを始めようという人の為の超入門サイトです。丁寧、簡単にこだわった解説なので初心者にぴったりです。

【Java】 カプセル化って何?

time 2013/08/18

sponsored link

カプセル状のオブジェクト?

 カプセルっていうのは、何か中に入れて密閉してしまうような容れ物のことです。知ってますよね。
 ではカプセル化というのは、どういうことなのか?
 勘違いしてはいけないのは、オブジェクトを大事にカプセルの中に入れるわけではありません。オブジェクトの構造をカプセル状にしてしまうんです。
 カプセル状と言うからには、中に何かを入れて密閉するわけですが、一体何をカプセルの中に大事に密閉しておくのか?それはフィールドです。全て(例外もあり)のフィールドをprivateにすることで、まるでカプセルで覆われたように外(他のクラス)からフィールドの値を触れないように、というか見えないようにしてしまうわけです。
 そして、そのカプセルには棒が刺さっています。その棒は動かすことができます。棒をカプセルの外から動かして、カプセルの中身をごちゃごちゃかき回すことができるような形状です。例えていうなら、耳かきみたいなことです。耳の中は見えないですが、耳かきを使って耳垢にアクセスすることができますよね。
 一体、なんの話かというと、そのカプセルに刺さっている耳かきのような棒というのはpublicなメソッドの例えです。カプセルの外側からカプセルに突き刺さっている棒(メソッド)を使うことで、カプセルの中身(フィールド)を操作するわけです。カプセルは強固なのでその棒を使ってしか中身にアクセスできません。
capsule
 気持ち悪い手書きの絵で申し訳ないのですが、カプセルに棒が刺さっていて操作できるというイメージが分かりますでしょうか。
 ゲッターとセッターもこの棒の一つです。フィールドをカプセルで覆って(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なメソッドによって、カプセルの中のフィールドを完璧に操作できる状態になるようにクラスを設計するのが大事です。クラス単体が完璧な設計をしてさえいれば、複数のクラスを作用させて組んだプログラム全体も、完璧な状態になるはずだ。というのがカプセル化というプログラミング手法の目指すところです。
 カプセル化はオブジェクト指向の根っこを支える超重要な概念です。外部(ほかのクラス)からフィールドにおかしな値が入れられたり、つじつまの合わない状態にされることが絶対ないように頑丈なカプセルで守られたクラス設計を心がけましょう。
 実はちょっと気に入ってるのでもう一度載せておきます。
capsule

sponsored link

Androidアプリを作ろう

コメント

  • 最後まで読ませて頂きました!
    自分も全くの0からjavaを学んでいたので、非常に分かりやすく親切なこちらのサイトに大変お世話になりました…有難うございます!

    ところで初歩的な質問で申し訳ないのですが、HumanTestクラスにて、満腹度50に設定したhuman1を作って、eat、walk、runのif文だけを下に記述して実行ところ、eatのみ食事をして、後の2つは空腹時のメッセージを表示してしまいます…
    文章はサイトにあるものをそのまま書き写したのですが、if文だけでも命令として動くのでしょうか?また正しく動作させるにはどう書くのが良いのでしょうか?

    こちらの欄で質問しても良いものか迷ったのですが、どうぞご回答頂ければ幸いです…

    by orz €2015/04/30 19:10

  • ありがとうございます。お役に立てたようで良かったです。(^^)

    質問に関してなのですが、よく理解できませんでした。申し訳ないです。。

    by Nobuo@管理人 €2015/05/02 15:00

  • 絵が上手い!

    by 匿名 €2015/06/12 13:14

  • 芸術の分かる方ですねw

    by Nobuo@管理人 €2015/06/12 16:31

down

コメントする



CAPTCHA


一番かんたんなJava入門

Androidアプリの作り方

忘備録

私の作ったAndroidアプリ

私の作ったWordPressテーマ

プロに教わるオンライン学習

管理人

Nobuo_CREATE

Nobuo_CREATE

WordPressテーマPrinciple、マテリアルを作ったり、Androidアプリを作ったり、Java入門サイトを作ったり、本を書いたりしています。どうぞよろしく。 [詳細]



sponsored link

オススメ書籍

[オススメpoint]

 この本は全く何も分からない初心者の方にお勧めです。プログラミングをするには覚えなければならない事が無茶苦茶いっぱいありますが、この本は教えてくれる順番、その構成が素晴らしいです。RPGのゲームを作るというストーリーにのっとってちょっとずつ難しいことを教えてもらえます。
 無機質で膨大なデータが載っているような本は読む気にならないという方は、こういうストーリー仕立ての本でチャレンジしてみてはいかがでしょうか?(注:RPGを作る為の本ではありません。)

[オススメpoint]

 ある程度、Javaを読み書きできるようになったら、オブジェクト指向について学ぶべきです。本書は、抽象的で分かったような分からんようなオブジェクト指向について、非常に分かりやすい例を出して説明してくれています。オブジェクト指向とは何なのか?という本質を掴むのにこれほど適した本はないと思います。オブジェクト志向の理念を理解できれば、より効率のいいコードをより楽に書けるようになるはずです。Java上級者を目指すなら必読の一冊!

只今、急拡大中

ゲームを作りたい人専門

個人フットサル予約サイト

ガチ専門

JavaからのRuby on Rails入門

JavaからのRuby on Rails入門