抽象クラス
抽象クラスとは
応用編第3日目で説明したとおり、C#では、あるクラスを継承したクラスを作ることができました。ここでは、継承の概念をさらに発展させた、抽象クラスという概念について説明します。
類似する概念の集約
サンプルプログラム
抽象クラスについて説明する前に、まずは以下のクラスを入力してみてください。
プロジェクト:SampleEx401/Program.csusing System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SampleEx401 { class Program { static void Main(string[] args) { Crow c = new Crow(); // カラスクラス Sparrow s = new Sparrow(); // すずめクラス // カラスがなく Console.Write(c.Name+" : "); c.Sing(); // 雀がなく Console.Write(s.Name + " : "); s.Sing(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SampleEx401 { class Crow { private String name = "カラス"; // カラスがなく public void Sing(){ Console.WriteLine("カーカー"); } // 名前を取得 public String Name{ get{ return name; } } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SampleEx401 { // すずめクラス class Sparrow { private String name = "すずめ"; // カラスがなく public void Sing(){ Console.WriteLine("チュンチュン"); } // 名前を取得 public String Name{ get{ return name; } } } }
すずめ : チュンチュン
このプログラムでは、二つのクラスCrow、Sparrowというクラスを生成し、それぞれに共通するSing()というメソッドを呼び出しています。Crowとは英語で、「カラス」、Sparrowは「すずめ」を意味する言葉です。
どちらも、「鳥」であり、共通する特徴である、「鳴く(Sing)」という行為を行うことができます。このように、類似したクラスが、同一名のメンバを持つことがしばしばあります。
また、Nameプロパティは、どちらのクラスにも共通であり、中身も変わりません。つまり、これらのクラスは、共通する処理を行う部分と、異なる処理を行う部分があります。このように、部分的な類似する処理があり、同一名の異なる処理があるときに有用になるのが、抽象(ちゅうしょう)クラスです。
抽象クラス
サンプルプログラム
では、サンプルプログラムを用いると、この処理はどのようになるのでしょう?それが、以下のサンプルです。まずは、入力して実行してみてください。
プロジェクトSampleEx402/Program.csusing System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SampleEx402 { class Program { static void Main(string[] args) { Crow c = new Crow(); // カラスクラス Sparrow s = new Sparrow(); // すずめクラス // カラスがなく Console.Write(c.Name + " : "); c.Sing(); // 雀がなく Console.Write(s.Name + " : "); s.Sing(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SampleEx402 { class Crow : Bird { public Crow() : base("カラス") { } // カラスがなく public override void Sing() { Console.WriteLine("カーカー"); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SampleEx402 { class Sparrow : Bird { // コンストラクタ public Sparrow() : base("すずめ") { } // すずめがなく public override void Sing() { Console.WriteLine("チュンチュン"); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SampleEx402 { abstract class Bird { // 名前フィールド private String name; // 引数つきコンストラクタ public Bird(String name) { this.name = name; } // 名前を取得 public String Name { get { return name; } } // 鳴く(抽象メソッド) public abstract void Sing(); } }
実行結果は同じなので、省略します。このクラスの共通点を集約して作った親クラスが、Birdクラスです。このクラスの定義の前には、abstractという修飾子が用いられていますが、これが、このクラスが抽象クラスであることを意味します。
抽象クラスとは、それ自身では、インスタンスを生成しないクラスのことを言います。この例でいくと、「カラス」や「すずめ」という鳥は存在しますが、「鳥」という名前の鳥は存在しません。つまり、「鳥」というのは抽象的な概念であり、実在しません。(図4-1.参照)
図4-1.「鳥」は、あくまでも抽象的な概念
…
}
しかし、鳥には「カラス」や「すずめ」といったような具体的な名前があり、また「鳴く」という動作をすることも共通です。そこで、Birdクラスには、そういったメンバが付加されています。
抽象メソッド
また、Bird.csの24行目を見てください。ここには、Sing()というメソッドが定義されていますが、処理が実装されていません。その上、先頭にクラスと同様、abstractという修飾子が先頭についています。このようなメソッドを、抽象メソッドと言います。
抽象メソッドはの実装は、抽象クラスを継承したサブクラスで実装されます。たとえば、Crow.csの16行目、およびSparrow.csクラスの16行目で、Sing()メソッドが実装されています。それぞれ動作は違いますが、どちらも「カラス」および「すずめ」が鳴く動作になっています。
抽象メソッドの定義なお、このメソッドを子クラスで実装する場合、一般のクラスで、親クラスのメソッドをオーバーライドする際のように、override修飾子をつける必要があります。
抽象メソッドの実装このように、抽象クラスとは、抽象メソッドを持ち、動作をサブクラスで行われるようなクラスのことを言います。すでに述べた通り、抽象クラスがインスタンスを持つことはできません。
抽象クラスのインスタンスを生成することはできない親クラスのコンストラクタの呼び出し
ところで、Birdクラスには、8行目から11行目で引数付きのコンストラクタが定義されていますが、このコンストラクタはどこから呼び出されるのでしょう。このコンストラクタは、サブクラスのコンストラクタから呼び出されています。
Crow2.csの6行目、およびSparrow2.csクラスの6行目に出ている、superを見てください。これは、親クラスのコンストラクタを呼び出す処理です。引数として、それぞれの鳥の名前を与えていますが、これを引数として、Birdクラスのコンストラクタが呼び出されます。これにより、親クラスのフィールドnameに値が設定されます。(図4-2.参照)
図4-2.superによる、親クラスのコンストラクタの呼び出し
抽象プロパティ
サンプルプログラム
抽象クラスで用いることができるのは、抽象メソッドだけではありません。プロパティも、抽象プロパティを持つことができます。以下のサンプルを実行してみてください。
プロジェクトSampleEx403/VectorBase.csusing System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SampleEx403 { // スーパークラス(抽象プロパティ持つ) abstract class VectorBase { // 抽象プロパティ public abstract double X { set; get; } public abstract double Y { set; get; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SampleEx403 { class Vector : VectorBase { private double x = 0.0; private double y = 0.0; // プロパティの実装 public override double X { set { x = value;} get { return x; } } public override double Y { set { y = value; } get { return y; } } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SampleEx403 { class Program { static void Main(string[] args) { Vector v = new Vector(); v.X = 0.1; v.Y = 0.2; Console.WriteLine("v=({0},{1})", v.X, v.Y); } } }
Vectorクラスは、抽象クラスVectorBaseを実装しています。抽象プロパティも、抽象メソッドと同様、abstract修飾子がついており、中身には、setおよび、getのみが記述され、実装はありません。
それを実装しているのが、Vectorクラスです。メソッドの場合と同様に、プロパティにも、overrideをつければ、プロパティとして使用できます。
抽象クラスを用いることのメリット
クラスの抽象化
では、抽象クラスを用いることのメリットは何なのでしょう?このサンプルの場合、一見プログラムが却って長くなり、面倒な感じがします。しかし、「カラス」や「すずめ」のほかに、「にわとり」や「つばめ」などといった、新しい「鳥」のクラスを作ろうとした場合、どうでしょうか?
抽象クラスでは、あらかじめ「鳥」全体の共通の処理は実装されていますから、わざわざ新たに同じ処理を何度も記述する必要がありません。例えば、各クラスでは、それぞれの鳥に独特の特徴を記述すればよいのです。
抽象クラスとしてインスタンスを持つ
SampleEx402.csの、Program.csを以下のように変えてみてください。
プロジェクトSampleEx402/Program.csusing System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SampleEx402 { class Program { static void Main(string[] args) { Bird[] b = new Bird[2]; // Birdクラスの変数の配列を生成 b[0] = new Crow(); // b[0]に、Crow2クラスのインスタンスを生成 b[1] = new Sparrow(); // b[1]に、Sparrow2クラスのインスタンスを生成 for(int i = 0; i < b.Length ; i++){ Console.Write(b[i].Name+" : "); b[i].Sing(); } } } }
実行結果は、SampleEx402と同じなので省略します。
Birdクラスとしてデータを集約
プログラムの13行目で、Birdクラスの配列を生成しています。14~15行目でそのインスタンスを生成しています。抽象クラスはインスタンスを生成することはできませんが、このように変数として値を保持することは可能です。ただし、そのインスタンスは、そのクラスを継承したサブクラスに限られています。
そのため、b[0]、b[1]に、Crow、Sparrowクラスのインスタンスを生成し、代入することが可能なのです。これにより、このクラスの各処理が抽象化されるため、for文のループで同様の処理を行うことができます。前述のように、抽象クラスのサブクラスが多数存在する場合、この方法は大変便利です。(図4-3.参照)
図4-3.Birdクラスから、インスタンスクラスのメソッドを呼び出す
練習問題 : 問題4.