Java
オブジェクト指向
設計
デザインパターン
新人プログラマ応援
More than 1 year has passed since last update.

以下ではオブジェクト指向とは結局何なのか あるいはプログラミングをする上で気をつけるべきたった一つのことで説明の都合上詳細を書けなかった部分について補足します。

オブジェクト指向って具体的に何

順番が逆の気もしますが「オブジェクト指向」が初耳の人のために具体的な例を出してイメージをつかんでもらいます。言語はJavaですがJavaを知らなくても何となく分かるように書きます。オブジェクト指向とは(即物的に言えば)データとそれに関わるメソッドをクラス(class)にまとめてプログラミングすることです。
例えば友達の情報を管理するために以下のような「友人」クラスを考えてみましょう。

Friend.java
public class Friend {
    private String name;//名前
    private String phoneNumber;//電話番号
    private String mailAddress;//メールアドレス

    public Friend(String name, String phoneNumber, String mailAddress) {
        //thisは自身を意味する
        this.name = name;
        this.phoneNumber = phoneNumber;
        this.mailAddress = mailAddress;
    }

    /** 連絡先を出力 */
    public void outputContact(){
        System.out.println(name);
        System.out.println("電話番号:"+phoneNumber);
        System.out.println("メール:"+mailAddress);
    }
}

このクラスではname, phoneNumber, mailAddressという3つのフィールドを持っていて、それを用いたメソッドoutputContact()が存在します。
クラス名と同じ名前のメソッドFriend(String name, String phoneNumber, String mailAddress)コンストラクタと呼ばれ以下のように使います。

Friend tanaka = new Friend("田中一郎", "03-444-444", "tanaka@qmail.com");

Friendコンストラクタを見ればわかるように、このコードではnameフィールドに"田中一郎"、phoneNumberフィールドに"03-444-444"、mailAddressフィールドに"tanaka@qmail.com"をセットしています。
そして以下のようにメソッドを呼び出すと連絡先が表示されます。

tanaka.outputContact();
/*
田中一郎
電話番号:03-444-444
メール:tanaka@qmail.com
*/

このようにクラスというのは「それがどんな情報を持つべきか」(ここでは名前と電話番号とメールアドレス)、「それがどんな機能を持つか」(ここでは連絡先の出力)を定義した設計図で、それに対してnewでコンストラクタを呼び出して実際にデータを入れて実体化したものをインスタンスと言います。
このようにクラスを作ることの利点の一つはデータと機能をひとまとめにしていくつも作れることです。もしクラスを使わない場合、友人が何人もいるとするとこんなコードになってしまいます。

String tanakaName = "田中一郎";
String tanakaPhoneNumber = "03-444-444";
String tanakaMailAddress = "tanaka@qmail.com";
String yamadaName = "山田太郎";
String yamadaPhoneNumber = "03-555-555";
String yamadaMailAddress = "yamada@qmail.com";
String suzukiName = "鈴木花子";
String suzukiPhoneNumber = "03-666-666";
String suzukiMailAddress = "suzuki@qmail.com";
//以下続く...

System.out.println(tanakaName);
System.out.println("電話番号:"+tanakaPhoneNumber);      
System.out.println("メール:"+tanakaMailAddress);
System.out.println(yamadaName);
System.out.println("電話番号:"+yamadaPhoneNumber);      
System.out.println("メール:"+yamadaMailAddress);
System.out.println(suzukiName);
System.out.println("電話番号:"+suzukiPhoneNumber);     
System.out.println("メール:"+suzukiMailAddress);
//以下続く...

わかりにくくて面倒ですね。

Friend tanaka = new Friend("田中一郎", "03-444-444", "tanaka@qmail.com");
Friend yamada = new Friend("山田太郎", "03-555-555", "yamada@qmail.com");
Friend suzuki = new Friend("鈴木花子", "03-666-666", "suzuki@qmail.com");
//以下続く...
tanaka.outputContact();
yamada.outputContact();
suzuki.outputContact();
//以下続く...

と書ければスッキリします。
ところで上記のフィールドとメソッドを見るとnameなどのフィールドはprivateoutputContact()メソッドはpublicというのが冒頭についています。これらは修飾子と呼ばれ、privateは「非公開」という意味でそのクラス内部からのみアクセスでき、publicは「公開」という意味で外部からもアクセスできることを意味します。

System.out.println(tanaka.name);//privateなのでエラー
tanaka.outputContact();//publicなので実行できる。

なぜこんな修飾子が存在するかは本編を御覧ください。

インターフェース(interface)

上でクラスは「それがどんな情報を持つべきか、それがどんな機能を持つかを定義した設計図」と書きましたが、インターフェース(interface)は「それがどんな機能を持つべきか」だけを定義した一種の抽象化されたクラスです。
例えば友達以外に仕事仲間のデータも管理しようと思ったとします。しかしどちらの場合も連絡先の出力メソッドがあるべきだと思うなら以下のようなインターフェースを作りましょう。

Human.java
public interface Human {
    /** 連絡先を出力 */
    public void outputContact();    
}

これは「Humanは連絡先の出力メソッドを持つべきだ」ということを意味します。インターフェースはどんなメソッドがあるべきかを定義するだけなので、実装はimplementsキーワードを使って継承されたクラスで行われます。

public class Friend implements Human{
    ...//(中略)
    @Override
    public void outputContact(){
        System.out.println(name);
        System.out.println("電話番号:"+phoneNumber);
        System.out.println("メール:"+mailAddress);
    }
}
/** 同僚 */
public class Colleague implements Human{
    ...//(中略)
    @Override
    public void outputContact(){
        //(処理内容)
    }
}

なお、メソッドを継承して上書きすることをオーバーライド(Override)と言います。

final修飾子

Javaのfinalは「それで終わり、決まり」という意味の修飾子です。これがつけられた変数は値を一度入れたら変更できません。

final int CONST_VARIABLE = 100;
CONST_VARIABLE = 50;//finalなのに値を変更しようとしたのでエラー

このように値を変更できない変数を「定数」と呼びます。慣習的に大文字と"_"(アンダーバー)で名前をつけるようになっています。
ではなぜ値を変更できない「変数」を作る必要があるのでしょう? それはわかりやすいコードにするためです。例えば以下のようなコードがあったとしましょう。

double price = 100 * 1.08;

2017年11月現在なら「消費税率を掛けてるんだな」とわかるかもしれませんが、将来消費税率が変わった後でこのコードを読む人はなぜ1.08を掛けてるのかわからないかもしれません。
しかし以下のようなコードならどうでしょう。

final double CONSUMPTION_TAX_RATE = 0.08;//消費税率
double price = 100 * (1 + CONSUMPTION_TAX_RATE);

これなら「.08」の部分が消費税率を意味していることがわかるので、消費税率が変更された未来の人にも理解できて改変できるコードになります。

static修飾子

Javaのstaticは「静的」という意味です。上記のFriendクラスのnameフィールドのように、newで「動的(「静的」の反対)」に生成された各インスタンスごとに異なるものではなく、クラス共通であることを意味します。
例えばもしFriendクラスに以下のようなコードがあったら、

public class Friend {
    public static int hoge = 10;
    public static int fuga(){
        return 20;
    }
    //(以下省略)
}

以下のようにクラス共通で、new(インスタンス化)しなくても使えることになります。

Friend yamada = new Friend("山田太郎", "03-555-555", "yamada@qmail.com");
Friend suzuki = new Friend("鈴木花子", "03-666-666", "suzuki@qmail.com");

//インスタンスが違っても共通
yamada.hoge = 30;
System.out.println(yamada.hoge);//30
System.out.println(suzuki.hoge);//30

//そもそもインスタンスを生成しなくても使える
System.out.println(Friend.hoge);//30
System.out.println(Friend.fuga());//20

ただ……この内staticフィールドについては上記のfinalをつけないで使用するのは避けたほうがいいでしょう。本編にも書いたように、オブジェクト指向はクラス内部を知らなくても使えることが利点です。例えばFriend tanaka = new Friend("田中一郎", "03-444-444", "tanaka@qmail.com");なら明示的に与えたデータ("田中一郎", "03-444-444", "tanaka@qmail.com")とそれに対して処理を加えたデータしか保持してないと想定してコードを書くわけです。しかし、staticフィールドが存在するとそれが全く別の場所で書き換えられており想定したのと異なる挙動をする可能性があり、オブジェクト指向の利点を活かせなくなってしまいます。
一方メソッドはstaticにできるなら可能な限りstaticにしたほうがいいです。例えば電話番号が正しい形式(「xxx-xxxx-xxxx」(数字とハイフンの連続))になっているか正規表現で調べるメソッドがあったら、staticにした方が「特定の人物(インスタンス)に依存するわけではない」ことがわかるので余計な混乱を招かなくてすみます。

public class Friend {
    ...//(中略)
    public static boolean isValidPhoneNumber(String phoneNumber){
        Pattern p = Pattern.compile("^[0-9]+-[0-9]+-[0-9]+$");
        Matcher m = p.matcher(phoneNumber);
        return m.matches();
    }
}
System.out.println(Friend.isValidPhoneNumber("03-444-444"));//正しい形式のためtrue
System.out.println(Friend.isValidPhoneNumber("03-44a-444"));//記入ミスのためfalse

なお、上でfinalでないstaticフィールドは使うべきでないと書きましたが、クラス共通の定数をstatic finalで定義するのはわかりやすくなるので推奨されるべきです。

public class Friend {
    ...//(中略)
    /** 電話番号の形式 */
    private static final Pattern PHONE_NUMBER_PATTERN = Pattern.compile("^[0-9]+-[0-9]+-[0-9]+$");

    public static boolean isValidPhoneNumber(String phoneNumber){
        Matcher m = PHONE_NUMBER_PATTERN.matcher(phoneNumber);
        return m.matches();
    }
}