継承とポロモーフィズムについてまとめてみた

に公開

OOPの概念について学んではいるけど、なかなか言語化したり体系的にアウトプットする機会がなかったので、まとめます。

自分が継承とポリモーフォズムの概念について腑に落ちていったプロセスを残せたらいいなと思います。

定義

この定義をこの記事を読み終わってから理解できるようになっていたら良いと思う。

継承

親クラスのフィールドやメソッドを子クラスで定義・利用できる仕組み

ポリモーフィズム

同じ型やメソッド呼び出しでも、オブジェクトに応じて振る舞いが変わるという効果

違いは?

継承 = 仕組み。コードの書き方や設計方法を指す。

ポリモーフィズム = 効果。継承やオーバーライドによって起こる現象を指す。

ここで大事なのは継承は仕組み、ポリモーフィズムは現象であること。
ここら辺はいるもまとまって説明されているから、何がなんなのか分かりにくかったけどまずはここを抑えると頭に入ってきやすかった。

コードで確認

継承の仕組みを理解

class Parent {
    public void greet() {
        System.out.println("Hello from Parent");
    }
}

class Child extends Parent {}

public class Main {
    public static void main(String[] args) {
        Parent p = new Parent();
        Parent c = new Child();

        p.greet();
        c.greet(); 
    }
}

出力

Hello from Parent
Hello from Parent

これが継承。
文字通り、親のクラスを継承して小クラスでも同じメソッド(greet())を利用することができている。


オーバーライドでポリモーフィズムの効果を確認

class Parent {
    public void greet() {
        System.out.println("Hello from Parent");
    }
}

class Child extends Parent {
    @Override
    public void greet() {
        System.out.println("Hello from Child");
    }
}

public class Main {
    public static void main(String[] args) {
        Parent p = new Parent();
        Parent c = new Child();

        p.greet();
        c.greet();
    }
}

出力

Hello from Parent
Hello from Child

同じ型(Parent)やメソッド(greet())を呼び出してもオブジェクトによって振る舞いが変わる効果が現れている。
この効果のことをポリモーフィズムという。

どんな時に役に立つのか

ここまでで継承とポリモーフィズムの定義について理解した。ここで私が思ったのは、

だからなんなん

です。

これらの概念を理解し使用すると何が良いのか、どう便利になるのかをまとめてみます。

継承とオーバライドでポリモーフィズムの効果を使うと何がいいのか

ポリモーフィズムは、同じ型、メソッド呼び出してもオブジェクトによって振る舞いが変わる効果なので、たくさんのオブジェクトを作成するときに効果を発揮すると考えられる。

色々なところで動物を例に上げたものは見てきたので、ここでは支払い手段をポリモーフィズムの効果を利用してメソッドの振る舞いを変えてみる。

class Payment {
    void pay(int amount) {
        System.out.println(amount + "円を支払いました。(デフォルト処理)");
    }
}

class CreditCardPayment extends Payment {
    @Override
    void pay(int amount) {
        System.out.println(amount + "円をクレジットカードで支払いました。");
    }
}

class PayPalPayment extends Payment {
    @Override
    void pay(int amount) {
        System.out.println(amount + "円をPayPalで支払いました。");
    }
}

class BankTransferPayment extends Payment {
    @Override
    void pay(int amount) {
        System.out.println(amount + "円を銀行振込で支払いました。");
    }
}

public class Main {
    public static void main(String[] args) {
        Payment[] payments = {
            new CreditCardPayment(),
            new PayPalPayment(),
            new BankTransferPayment(),
            new Payment() // デフォルト処理をそのまま使う場合
        };

        for (Payment p : payments) {
            p.pay(5000);
        }
    }
}
5000円をクレジットカードで支払いました。
5000円をPayPalで支払いました。
5000円を銀行振込で支払いました。
5000円を支払いました。(デフォルト処理)

同じ型、メソッド呼び出してもオブジェクトによって振る舞いというのは、このコードMainクラスの中でnewして色々なPaymentをインスタンス化していますが、これの中でpay()というメソッドを使用したときに、オブジェクトどことに同じような呼び出しをしているが、出力結果は違うものが出力されているということです。

これはどのくらい便利なのか

実際にこの処理をIf文を使って条件分岐で書いてみたとします、どんな違いがあるのか確認します。

IF文を使っ他条件分岐で上のコードを再現

class Payment {
    void pay(int amount) {
        System.out.println(amount + "円を支払いました。(デフォルト処理)");
    }
}

class CreditCardPayment extends Payment {
    @Override
    void pay(int amount) {
        System.out.println(amount + "円をクレジットカードで支払いました。");
    }
}

class PayPalPayment extends Payment {
    @Override
    void pay(int amount) {
        System.out.println(amount + "円をPayPalで支払いました。");
    }
}

class BankTransferPayment extends Payment {
    @Override
    void pay(int amount) {
        System.out.println(amount + "円を銀行振込で支払いました。");
    }
}

public class Main {
    public static void main(String[] args) {
        Payment[] payments = {
            new CreditCardPayment(),
            new PayPalPayment(),
            new BankTransferPayment(),
            new Payment()
        };

        for (Payment p : payments) {
            if (p instanceof CreditCardPayment) {
                ((CreditCardPayment) p).pay(5000);
            } else if (p instanceof PayPalPayment) {
                ((PayPalPayment) p).pay(5000);
            } else if (p instanceof BankTransferPayment) {
                ((BankTransferPayment) p).pay(5000);
            } else {
                p.pay(5000); // デフォルト処理
            }
        }
    }
}

if文で書いた場合、クラスが増えるたびにmainの中に記載するif文の数が増えていき、条件分岐が複雑になってきます。
それに対してポリモーフィズムの効果を利用できると、クラスの数が増えてもmainの中に追加するコードはありません。

このように、メンテナンス性が高く、変更時に変更する場所が少ないことで可読性を上げ、ミスなどを減らすことができるのがポリモーフィズムの効果を利用する利点といえます。

ただ動くだけではなく、将来のメンテナンスのことまで考えたコード設計にすることは、 AIが台頭した今でも、まだ求められる能力だと思うのでコードを綺麗に書く力を身につけていきたいと思います。

Discussion