🔧

オブジェクト指向とは結局何なのか 補足

2022/08/02に公開

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

インターフェース(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 constVariable = 100;
constVariable = 50;//finalなのに値を変更しようとしたのでエラー

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

double price = 100 * 1.08;

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

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

これなら「.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();
    }
}

Discussion