++C++; // 未確認飛行 C 避けて通れない「非同期処理」を克服しよう

Top総合 目次C# によるプログラミング入門

抽象メソッド、抽象クラス

このエントリーをはてなブックマークに追加

目次

キーワード

概要

抽象メソッドとは、実装を持たず、メソッドの意味(規約)だけを定義したメソッドです。 抽象メソッドの実装は基底クラスでは行わず、派生クラスで行います。

また、抽象クラスとは、 インスタンスを生成出来ないクラスのことで、 継承して使うことを前提としたクラスのことです。

ポイント
  • 抽象メソッド: 基底クラスでは実装せず、メソッドの意味(規約)だけを定義して、派生クラスで具体的な実装を行うようなメソッド。
  • (C++ では純粋仮想関数と呼ばれていたものです。)
  • 抽象メソッドを1個でも持つクラス(抽象クラス)は、インスタンスを生成することができません。
  • クラスやメソッドの前に abstract キーワードを付ける。

抽象化

多態性」 で、 仮想メソッドの利用例として Person クラスを挙げました。 この Person 基底クラスには、 Age というプロパティがありますが、 このプロパティ自体は意味のある値を返さず、 実装は派生クラスの Age プロパティで行っていました。

class Person
{
  // ここではあんまり関係ないんで name は省略。
  protected int age;

  public Person(int age){this.age  = age;}

  public virtual int Age
  {
    // 基底クラスでは特に意味のない値を返す。
    // 意味のある実装は派生クラスで行います。
    get
    {
      return 0;
    }
  }
}

しかし、Person クラスのように、 意味のない値を返すメソッドを持つクラスのインスタンスが生成されてしまうというのはあまり好ましいことではありません。

この問題を解決するためには2つの方法があります。 1つは基底クラスにデフォルトの動作を定める方法です。 すなわち、 性善説を信じて Person がデフォルトで正直な答えを返すようにするか、 性悪説を信じて Person がデフォルトで鯖を読むようにするか、 とにかく、PersonAge プロパティが何らかの意味を持つ値を返すようにします。

class Person
{
  protected int age;

  public Person(int age){this.age  = age;}

  public virtual int Age
  {
    // 性善説を信じてみる。
    // 普通の人はみんな正直に年齢を答えてくれるに違いない。
    get
    {
      return this.age;
    }
  }
}

そして、もう1つの方法は、Person クラスのインスタンスを生成出来ないようにすることです。 例えば、Person クラスのコンストラクタを protected にしてしまえば、Person クラスのインスタンスは外部から生成できなくなります。

class Person
{
  protected int age;

  // ↓ protected なので外部からコンストラクタを呼べない。
  //    Person は継承して使う専用のクラスになります。
  protected Person(int age){this.age  = age;}

  public virtual int Age{get{return 0;}}
}

これで Person クラスのインスタンスが作られることはなくなるんですが、 まだ Person クラスに意味のないメソッドの実装が残っています。 これは意味のないものをわざわざ書かなくてはいけないので無駄になりますし、 サブクラスでちゃんとオーバーライドしなければ無意味な値が返されてしまうという問題があります。

この問題を解決するため、 C# にはインスタンスを作成できないクラスや、 実装のない(派生クラスで必ずオーバーライドしなければならない)メソッドを定義するための構文が用意されています。

インスタンスを作成できないクラスは抽象クラス(abstract class)と呼ばれています。 抽象クラスを作成するには、クラスの定義時に abstract 修飾子を付けます。

abstract class Person
{
  protected int age;

  // 抽象クラスなので、コンストラクタが public であってもインスタンスは生成できない。
  public Person(int age){this.age  = age;}

  public virtual int Age{get{return 0;}}
}

また、実体を持たず、意味だけを定義し、実装は派生クラスで行うメソッドは抽象メソッド(abstract method)と呼ばれています。 抽象メソッドを作成するには、メソッドの定義時に abstract 修飾子を付けます。 抽象メソッドは抽象クラス中でしか定義できません。

ちなみに、プロパティも、内部的に見るとメソッドのようなものなので、 abstract を付けて抽象プロパティにすることができます。

abstract class Person
{
  protected int age;

  public Person(int age){this.age  = age;}

  public abstract int Age{get;} // 抽象メソッドや抽象プロパティには定義は要らない
}

サンプル

いままで例に挙げてきた Person クラスの最終形です。

using System;

abstract class Person
{
  protected string name;
  protected int age;

  public Person(string name, int age)
  {
    this.name = name;
    this.age  = age;
  }

  public string Name{get{return this.name;}}
  public abstract int Age{get;} // 抽象メソッドには定義は要らない
}

/// <summary>
/// 正直者。
/// 年齢を偽らない。
/// </summary>
class Truepenny : Person
{
  public Truepenny(string name, int age) : base(name, age){}

  public override int Age
  {
    get
    {
      // 実年齢をそのまま返す。
      return this.age;
    }
  }
}

/// <summary>
/// 嘘つき。
/// 鯖を読む(しかも、歳取るにつれ大幅に)。
/// </summary>
class Liar : Person
{
  public Liar(string name, int age) : base(name, age){}

  public override int Age
  {
    get
    {
      // 年齢を偽る。
      if(this.age < 20) return this.age;
      if(this.age < 25) return this.age - 1;
      if(this.age < 30) return this.age - 2;
      if(this.age < 35) return this.age - 3;
      if(this.age < 40) return this.age - 4;
      return this.age - 5;
    }
  }
}

/// <summary>
/// いいかげん。
/// 大体の歳しか答えない。
/// </summary>
class Equivocator : Person
{
  public Equivocator(string name, int age) : base(name, age){}

  public override int Age
  {
    get
    {
      // 年齢を四捨五入した値を返す。
      return ((this.age + 5) / 10) * 10;
    }
  }
}

/// <summary>
/// いくつになったって気持ちは17歳。
/// </summary>
class Seventeenist : Person
{
  public Seventeenist(string name, int age) : base(name, age) { }

  public override int Age
  {
    get
    {
      // 「おいおい」って突っ込み入れてあげてね。
      return 17;
    }
  }
}

class PolymorphismTest
{
  static void Main()
  {
    Introduce(new Truepenny  ("Ky Kiske"  , 24)); //正直者のカイさん24歳。
    Introduce(new Liar       ("Axl Low"   , 24)); //嘘つきのアクセルさん24歳。
    Introduce(new Equivocator("Sol Badguy", 24)); //いい加減なソルさん24歳。
    Introduce(new Seventeenist("Ino"       , 24)); // 時空を超えるイノさん24歳。
  }

  /// <summary>
  /// p さんの自己紹介をする。
  /// </summary>
  static void Introduce(Person p)
  {
    Console.Write("My name is {0}.\n", p.Name);
    Console.Write("I'm {0} years old.\n\n", p.Age);
  }
}
My name is Ky Kiske.
I'm 24 years old.

My name is Axl Low.
I'm 23 years old.

My name is Sol Badguy.
I'm 20 years old.

My name is Ino.
I'm 17 years old.

演習問題

問題 1

多態性問題 1Shape クラスを抽象クラス化せよ。

解答

[お問い合わせ](q)