技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

オブジェクト指向を利用する本当の理由

オブジェクト指向を利用する理由として、プログラミングの入門書には
オブジェクト指向を用いると、現実世界をプログラミングでそのまま表現できる」
という意のことが書いてあることが多いです。
その例として、
・犬に「ワン」と鳴かせ、猫に「ニャーン」と鳴かせる
・乗用車に普通に走らせ、レーシングカーに速く走らせる
といった例が用いられることが多いです。
 
しかし、実務での使われ方を見ると、
「現実世界をプログラミングでそのまま表現する」
というのは、目的ではなく手段である、という感があります。
実務では、
ソースコードから重複を取り除き保守性を高めるため」
という目的で使用されます。
オブジェクト指向では、現実世界の関係性を機械の都合に置き換える必要が無くなるため、ソースコードから重複を取り除きやすくなります。
オブジェクト指向の特徴である「継承」や「ポリモーフィズム」は、ソースコードから重複を取り除く上で強力な特徴です。
(同じくオブジェクト指向の特徴として挙げられる「カプセル化」も、オブジェクト指向のルールを守らせるという意味で強力な特徴です)
 
目的が保守性の向上であるため、現実世界では意識されにくい「思考ルーチン」「出力機能」といったものも、クラス化されることがあります。
また、保守性の向上に繋がらないのであれば、現実世界にあるものをクラスとして記述しないこともあります。
 
重複を取り除くことによる保守性向上、というのはイメージしにくい所だと思いますので、以下ではJavaのサンプルコードを用いて説明していきます。
 
----
 
今回のサンプルコードでは、「乗り物に乗って、タイミングを見計らって、乗り物を走らせる」という処理を実装していきます。
「乗り物に乗る」という処理と「乗り物を走らせる」という処理は別々のタイミングで実行する必要があるので、別々に実装する必要があります。
また、乗り物は複数種類があり、それぞれの種類毎で異なる処理を行う必要があるとします。
 
オブジェクト指向を使用しない場合は、下記に示すようなサンプルコードになります。
 
「乗り物に乗る」という処理と「乗り物を走らせる」という処理は、別々のメソッドとして実装しています。
乗り物の種類毎で処理を分ける必要があり、オブジェクト指向を使用しないサンプルコードではフラグ変数により分けています。
 
注目するべきは、各々のメソッドの中でフラグ変数を参照していることです。
各々のメソッドの中でフラグ変数を見ているため、フラグ変数を参照する箇所はメソッドの数だけ存在することになり、フラグ変数の参照方法の変更(例えば「自転車」を示すフラグが2から3に変更になった場合)が発生した場合の修正箇所が増え、保守性が低下します。
具体的には、一部の箇所だけ修正し忘れるようなバグが発生しやすくなります。
 
オブジェクト指向を使用しない場合のサンプルコード】
・OOPTestMain.java
public class OOPTestMain {

    public static void main(String args) {

        // 自動車を定義
        int vehicleFlag = 1;

        // 乗る動作
        ride(vehicleFlag);

        // 準備(5秒待つ)
        System.out.println("準備中です…");
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
        }

        // 走る動作
        run(vehicleFlag);

    }

    public static void ride(int vehicleFlag) {

        // 自動車の場合
        if (vehicleFlag == 1) {
            System.out.println("ドアを開けて運転席に座る");
        // 自転車の場合
        } else if (vehicleFlag == 2) {
            System.out.println("またがる");
        }

    }

    public static void run(int vehicleFlag) {

        // 自動車の場合
        if (vehicleFlag == 1) {
            System.out.println("アクセルを踏み込む");
        // 自転車の場合
        } else if (vehicleFlag == 2) {
            System.out.println("ペダルをこぐ");
        }

    }

}
 
【サンプルコードの実行結果】
ドアを開けて運転席に座る
準備中です…
アクセルを踏み込む
 
----
 
サンプルコードにオブジェクト指向を適用すると下記のようなコードになります。
 
注目するべきは、継承やポリモーフィズムを使用することで、フラグ変数参照に相当する記述を削減できていることです。
フラグ変数参照に相当する修正としては、クラス名の変更がありますが、クラス名変更が発生したとしても修正箇所は1カ所に留まるため、保守性が向上します。
 
オブジェクト指向を使用する場合のサンプルコード】
・OOPTestMain.java
public class OOPTestMain {

    public static void main(String args) {

        // 自動車を定義
        OOPTestVehicle vehicle = new OOPTestCar();

        // 乗る動作
        vehicle.ride();

        // 準備(5秒待つ)
        System.out.println("準備中です…");
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
        }

        // 走る動作
        vehicle.run();

    }

}
 
・OOPTestVehicle.java
public interface OOPTestVehicle {

    public void ride();
    public void run();

}
 
・OOPTestCar.java
public class OOPTestCar implements OOPTestVehicle {

    @Override
    public void ride() {
        System.out.println("ドアを開けて運転席に座る");
    }

    @Override
    public void run() {
        System.out.println("アクセルを踏み込む");
    }

}
 
・OOPTestBicycle.java
public class OOPTestBicycle implements OOPTestVehicle {

    @Override
    public void ride() {
        System.out.println("またがる");
    }

    @Override
    public void run() {
        System.out.println("ペダルをこぐ");
    }

}