C# オブジェクト指向プログラミング②(ポリモーフィズム)

こんにちは、ゆたんぽです。

今回は、オブジェクト指向における、ポリモーフィズムについて説明していきます。

C#におけるポリモーフィズムは、同じ名前のメソッドやプロパティを異なるクラスで定義し、それらを同じように呼び出すことができる仕組みです。これにより、異なるクラスのオブジェクトに対して同じ操作を実行することができます。

最初説明してもわからないと思いますが、これがかなり便利になってきます。

以下で詳しく記事を書いていきます。

ポリモーフィズムとは

ポリモーフィズムは、クラス継承の概念に基づいて実現されます。具体的には、親クラスが定義したメソッドやプロパティを子クラスが継承し、必要に応じて子クラスで再定義することで実現されます。子クラスで再定義されたメソッドやプロパティは、親クラスで定義されたものと同じ名前を持ちますが、異なる実装を持つことができます。

例えば、以下のようなAnimalクラスがあるとします。

public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("The animal makes a sound");
    }
}

このAnimalクラスには、MakeSoundという仮想メソッドが定義されています。仮想メソッドとは、子クラスで再定義できるメソッドのことです。Animalクラスを継承したDogクラスを定義し、MakeSoundメソッドを再定義してみましょう。

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("The dog barks");
    }
}

このように、DogクラスではAnimalクラスのMakeSoundメソッドを再定義しています。この時、親クラスのAnimalクラスのインスタンスを作成し、子クラスのDogクラスのインスタンスを作成して、それぞれMakeSoundメソッドを呼び出してみると、以下のような結果が得られます。

Animal animal = new Animal();
Dog dog = new Dog();

animal.MakeSound(); // The animal makes a sound
dog.MakeSound(); // The dog barks

このように、親クラスと子クラスで同じ名前のメソッドを定義することで、異なるクラスのオブジェクトに同じ操作を実行することができます。このような仕組みがポリモーフィズムと呼ばれます。

C#においては、ポリモーフィズムを実現するために、メソッドのオーバーロード、仮想メソッド、抽象クラス、インターフェースなどが用意されていますので、以下で説明していきます。

メソッドのオーバーロード

メソッドのオーバーロードとは、同じ名前のメソッドを複数の引数型や引数数で定義することです。引数の型や数が異なれば、同じ名前のメソッドでも異なる処理を実行することができます。

例えば、以下のようなCalcクラスがあったとします。

class Calc
{
    public int Add(int a, int b)
    {
        return a + b;
    }
}

このクラスに対して、以下のようにAddメソッドをオーバーロードすることができます。

class Calc
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public double Add(double a, double b)
    {
        return a + b;
    }
}

このようにすることで、整数の足し算にはAdd(int a, int b)メソッドを、小数点数の足し算にはAdd(double a, double b)メソッドを使用することができます。

同じメソッドを呼ぶ際に引数によって使い分けることができますね。

仮想メソッド(オーバーライド)

仮想メソッドとは、サブクラスでオーバーライド可能なメソッドのことです。オーバーライドだと、ひとつ前に説明したオーバーロードと名前が似ているのであえて仮想メソッドと呼んで使い分けています。仮想メソッドは、基本クラスで定義されたメソッドを、サブクラスで再定義することができます。1番初めに説明したサンプルクラスと同様ですが、もう一度詳しく説明していきます。

例えば、以下のようなAnimalクラスがあったとします。

class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("...");
    }
}

このクラスに対して、以下のようにDogクラスを定義し、Speakメソッドをオーバーライドすることができます。

class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("ワンワン!");
    }
}

このようにすることで、Animal型の変数にDogクラスのオブジェクトを代入してSpeakメソッドを呼び出すと、DogクラスでオーバーライドされたSpeakメソッドが実行されます。

ポリモーフィズムでは、この仮想メソッド(オーバーライド)の仕組みがとても重要です。親でBaseのクラスを作成して、それを継承したクラスがそれぞれの特徴を仮想メソッド(オーバーライド)で上書きし、呼び出す側は中身を知らなくてもそれぞれのクラスだけ呼べば、同じように実行できる、というのがポリモーフィズムのメリットだと思います。

抽象クラス

抽象クラスとは、インスタンス化できないクラスで、サブクラスに対して共通のメソッドやフィールドを定義するために使用されます。抽象クラスは、抽象メソッドを持つことができます。抽象メソッドとは、実装されたコードを持たず、サブクラスで必ずオーバーライドする必要があるメソッドのことです。

例えば、以下のようなShapeクラスがあったとします。

abstract class Shape
{
    public abstract double GetArea();
}

このクラスには、GetAreaメソッドが定義されていますが、このメソッドは抽象メソッドであり、実装されたコードを持たず、サブクラスでオーバーライドする必要があります。また、このクラスはabstractキーワードで宣言されているため、インスタンス化することができません。サブクラス側で書き換え前提で用意されたメソッドということです。

以下のように、RectangleクラスとTriangleクラスを定義し、Shapeクラスを継承してGetAreaメソッドをオーバーライドすることができます。

class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public override double GetArea()
    {
        return Width * Height;
    }
}

class Triangle : Shape
{
    public double Base { get; set; }
    public double Height { get; set; }

    public override double GetArea()
    {
        return Base * Height / 2;
    }
}

このようにして、長方形と三角形の面積を計算仕組みをGetAreaにそれぞれが書き換えることができます。

インターフェース

インターフェースとは、実装されたコードを持たず、メソッドやプロパティ、イベントなどの定義のみを持つ型のことです。インターフェースは、サブクラスが実装しなければならない契約として機能し、複数のインターフェースを実装することができます。

例えば、以下のようなIPlayableインターフェースがあったとします。

interface IPlayable
{
    void Play();
    void Pause();
    void Stop();
}

このインターフェースを継承するクラスは、Playメソッド、Pauseメソッド、Stopメソッドを実装しなければなりません。

class MP3Player : IPlayable
{
    public void Play()
    {
        Console.WriteLine("MP3を再生します");
    }

    public void Pause()
    {
        Console.WriteLine("MP3を一時停止します");
    }

    public void Stop()
    {
        Console.WriteLine("MP3を停止します");
    }
}

このようにして、インターフェースを継承したクラスがそれぞれのメソッドを自分のクラスように書き換えていくわけです。

これにより、ルール付けができるので、「IPlayable」を継承しているかどうかを確認して、継承していればあるメソッドが呼べると考えて、メソッドを呼ぶようなことができます。

このように、ポリモーフィズムを実現するために、C#ではメソッドのオーバーロード、抽象クラス、インターフェースの機能が利用されます。

ポリモーフィズムの例

最後に復習がてらもう一つ例を出します。

以下の例では、Shapeクラスを継承したRectangleクラス、Triangleクラス、Circleクラスを定義し、それぞれのGetAreaメソッドをオーバーライドします。また、DrawAllShapesメソッドでは、Shapeクラス型の配列を受け取り、配列内の各オブジェクトのGetAreaメソッドを呼び出して面積を計算し、Consoleに表示します。

class Shape
{
    public virtual double GetArea()
    {
        return 0;
    }
}

class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public override double GetArea()
    {
        return Width * Height;
    }
}

class Triangle : Shape
{
    public double Base { get; set; }
    public double Height { get; set; }

    public override double GetArea()
    {
        return Base * Height / 2;
    }
}

class Circle : Shape
{
    public double Radius { get; set; }

    public override double GetArea()
    {
        return Math.PI * Radius * Radius;
    }
}

class Program
{
    static void DrawAllShapes(Shape[] shapes)
    {
        foreach (var shape in shapes)
        {
            Console.WriteLine("面積: " + shape.GetArea());
        }
    }

    static void Main(string[] args)
    {
        Shape[] shapes = new Shape[]
        {
            new Rectangle { Width = 10, Height = 5 },
            new Triangle { Base = 8, Height = 3 },
            new Circle { Radius = 4 }
        };

        DrawAllShapes(shapes);
    }
}

実行結果:

面積: 50
面積: 12
面積: 50.2654824574367

この例では、Shapeクラス型の配列に、Rectangleクラス、Triangleクラス、Circleクラスのインスタンスを格納しています。DrawAllShapesメソッドでは、配列内の各オブジェクトのGetAreaメソッドが呼び出され、それぞれの面積が計算されています。

まとめ

ポリモーフィズムは、オブジェクト指向プログラミングにおいて重要な概念の1つであり、同じメソッドやプロパティなどを持つ異なるクラスを統一的に扱えるようにすることができます。C#では、メソッドのオーバーロード、抽象クラス、インターフェースの機能が利用され、ポリモーフィズムを実現することができます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です