C Sharpの基礎 - ポリモーフィズム

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
2021年11月15日 (月) 00:30時点におけるWiki (トーク | 投稿記録)による版 (文字列「<source lang」を「<syntaxhighlight lang」に置換)
ナビゲーションに移動 検索に移動

概要

ポリモーフィズムとはオブジェクト指向プログラミングの概念の1つである。多態性・多様性などと訳される。
同じ名前のメソッドを継承したクラスで使用できるようにし、そのメソッドを通して、暗黙的に複数のインスタンスの動作を切り替えることができるようにする。

C#でポリモーフィズムを実現するには、以下の2つ方法がある。

  • 継承を使う方法
  • インターフェースを使う方法



継承を使用したポリモーフィズム

まず、Superという基底クラスを定義する。
abstractが付加されたクラスは抽象クラスとなり、インスタンス化できないクラスである。
抽象クラスは、継承したサブクラスを定義することが前提となる。

<syntaxhighlight lang="c++">
// Super class
public abstract class Super
{
   public virtual string Hello()
   {
       return "";
   }
}

// Derived 1 class
public class Derived1 : Super
{
   public override string Hello()
   {
       return "Hello";
   }
}

public class Derived2 : Super
{
   public override string Hello()
   {
       return "こんにちは";
   }
}
</source>


各サブクラスでは、overrideキーワードを使用して、Hello()メソッドを再定義している。
これにより、Hello()メソッドを持っているサブクラスを統一的に扱うことができるようになる。

実際に、上記のクラスを使用したコードは以下のようになる。
各サブクラスのインスタンスを格納したList<T>の要素が、それぞれの継承元であるSuperクラスである。
foreachブロックでは、Derived1もDerived2も同じSuperクラスとみなされている。

しかし、呼び出されるHello()メソッドは、SuperクラスのHello()メソッドではなく、実際のインスタンスのものとなる。
つまり、実際の型がDerived1ならDerived1、Derived2ならDerived2のHello()メソッドが呼び出されている。

抽象クラスを使用すると、異なる型のオブジェクトを同一視し、そのオブジェクトの型によって動作が切り替えることができるようになる。
ポリモーフィズムでは、静的型付け言語の厳密性を保ったまま、動的型付け言語の柔軟性を得ることができる。

<syntaxhighlight lang="c#">
using System;
using System.Collections.Generic;

public class Program
{
   static void Main()
   {
      var Deriveds = new List<Super>(){new Derived1(), new Derived2()};
      foreach (Derived character in Deriveds)
      {
         Console.WriteLine(Derived.Hello());
      }
   }
}
</source>

// 結果
Hello
こんにちは



インターフェースを使ったポリモーフィズム

インターフェースを使用した抽象クラスでも同様なことが可能である。
インターフェースは、製品の規格のようなもので、プロパティやメソッドの呼び出し方だけを定めたものである。

<syntaxhighlight lang="c#">
// IGreeting interface
interface IGreeting
{
   string Hello();
}
</source>


下記のIGreetingを継承したクラスでは、Hello()メソッドを必ず定義しなければならないと定められている。
また、インターフェースでは、public等のアクセス修飾子は付けることはできない。(継承したクラスで付加する)
※インターフェースでの定義では、アクセス修飾子は全てpublicに強制される。

次に、IGreetingインターフェイスを実装した各サブクラスを定義する。
インターフェースのメソッドやプロパティの具体的な動作は、ここに記述することになる。

<syntaxhighlight lang="c#">
// IGreetingインターフェースの実装
public class Derived1 : IGreeting
{
   public string Hello()
   {
      return "Hello\n";
   }
}

public class Derived2 : IGreeting
{
   public string Hello()
   {
      return "こんにちは\n";
   }
}
</source>


上記の各クラスを使用したコードは以下のようになる。
呼び出し側のコードは、List<T>の要素がIGreetingに変わっただけでスーパークラスを継承した場合と同じである。

<syntaxhighlight lang="c#">
using System;
using System.Collections.Generic;

public class Program
{
   static void Main()
   {
      var Deriveds = new List<IGreeting>() {new Derived1(), new Derived2()};
      foreach (IGreeting Derive in Deriveds)
      {
           Console.WriteLine(Derive.Hello());
      }
   }
}
</source>



その他

抽象メソッドの修飾子であるvirtualとabstractについて記載する。

抽象メソッドの修飾子がvirtualの場合、継承先のクラスでオーバーライドしなくても構わない。
抽象メソッドの修飾子がabstractの場合、実際の処理を記述しないが、継承先のクラスにて必ずオーバーライドして実際の処理を定義する必要がある。

<syntaxhighlight lang="c#">
// abstractを使用した抽象メソッド
public abstract class Greeting
{
   public abstract string Hello();
}
</source>



抽象クラスとインターフェースの使い分け

ポリモーフィズムは、抽象クラスとインターフェースを2つの方法で実現できた。
では、この2つはどのように使い分けるのだろうか?

抽象クラスと派生クラスはIS-A関係(継承)と呼ばれるのに対して、インターフェースと実装クラスの関係はCAN DO関係と呼ばれる。

抽象クラス インターフェース
具体的な実装 持つことができる 持てない
多重継承 できない できる



ポリモーフィズムの使いどころ

ポリモーフィズムは、入力値のバリデーション(有効 / 無効の確認)やファイルのインポート / エクスポートに使用できる。
下記のサンプルコードでは、入力値のバリデーションを考える。

<syntaxhighlight lang="c#">
// ユーザの入力値がバインドされるモデル
public class Model
{
   public string Name    { get; set; }
   public DateTime Birth { get; set; }
   public bool Married   { get; set; }
}

// 基底クラス(検証クラス)の定義
public abstract class ValidatorBase
{
   protected Model _model;

   public ValidatorBase(Model model)
   {
      this._model = model;
   }

   public abstract bool IsValid();
}

// 基底クラスを継承した派生クラス(名前の検証クラス)を定義
public class NameValidator : ValidatorBase
{
   public NameValidator(Model model) : base(model)
   { }

   // 空白以外の文字列かつ20文字以下なら入力値は有効
   public override bool IsValid()
   {
      if (String.IsNullOrWhiteSpace(this._model.Name))
      {
         return false;
      }

      if (this._model.Name.Length > 20)
      {
         return false;
      }

      return true;
   }
}

// 基底クラスを継承した派生クラス(生年月日の検証クラス)を定義
public class BirthValidator : ValidatorBase
{
   public BirthValidator(Model model) : base(model)
   { }
   // 20歳以上40歳以下なら入力値は有効
   public override bool IsValid()
   {
      var age = DateTime.Now.Year - this._model.Birth.Year;
      if (DateTime.Now < this._model.Birth.AddYears(age))
      {
         age--;
      }

      if (!(20 <= age && age <= 40))
      {
         return false;
      }

      return true;
   }
}

// 基底クラスを継承した派生クラス(独身者の検証クラス)を定義
public class MarriedValidator : ValidatorBase
{
   public MarriedValidator(Model model) : base(model)
   { }

   // 独身者のみ有効
   public override bool IsValid()
   {
      return !this._model.Married;
   }
}
</source>


上記のクラスを使用したサンプルコードを記述する。

<syntaxhighlight lang="c++">
using System;
using System.Collections.Generic;

public class Program
{
   static void Main()
   {
      // 実際には、ユーザの入力値がバインドされることを想定している
      var model = new Model()
                  {
                     Name = "名無しの権兵衛",
                     Birth = new DateTime(1992, 7, 17),
                     Married = false
                  };

      var Validators = new List<ValidatorBase>()
                       {
                          new NameValidator(model),
                          new BirthValidator(model),
                          new MarriedValidator(model)
                       };

      foreach (var validator in Validators)
      {  // 順に検証して処理

      }
   }
}
</source>