抽象クラス

抽象クラスとは

応用編第3日目で説明したとおり、C#では、あるクラスを継承したクラスを作ることができました。ここでは、継承の概念をさらに発展させた、抽象クラスという概念について説明します。

類似する概念の集約

サンプルプログラム

抽象クラスについて説明する前に、まずは以下のクラスを入力してみてください。

プロジェクト:SampleEx401/Program.cs
using 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();
        }
    }
}
Crow.cs
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; }
        }
    }
}
Sparrow.cs
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プロパティは、どちらのクラスにも共通であり、中身も変わりません。つまり、これらのクラスは、共通する処理を行う部分と、異なる処理を行う部分があります。このように、部分的な類似する処理があり、同一名の異なる処理があるときに有用になるのが、抽象(ちゅうしょう)クラスです。

抽象クラス

サンプルプログラム

では、サンプルプログラムを用いると、この処理はどのようになるのでしょう?それが、以下のサンプルです。まずは、入力して実行してみてください。

プロジェクトSampleClassEx202/Program.cs
using 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();
        }
    }
}
Crow.cs
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("カーカー");
        }
    }
}
Sparrow.cs
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("チュンチュン");
        }
    }
}
Bird.cs
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.「鳥」は、あくまでも抽象的な概念

抽象クラスのイメージ
抽象クラスの定義
abstract class (クラス名){
    …
}

しかし、鳥には「カラス」や「すずめ」といったような具体的な名前があり、また「鳴く」という動作をすることも共通です。そこで、Birdクラスには、そういったメンバが付加されています。

抽象メソッド

また、Bird.csの24行目を見てください。ここには、Sing()というメソッドが定義されていますが、処理が実装されていません。その上、先頭にクラスと同様、abstractという修飾子が先頭についています。このようなメソッドを、抽象メソッドと言います。

抽象メソッドはの実装は、抽象クラスを継承したサブクラスで実装されます。たとえば、Crow.csの16行目、およびSparrow.csクラスの16行目で、Sing()メソッドが実装されています。それぞれ動作は違いますが、どちらも「カラス」および「すずめ」が鳴く動作になっています。

抽象メソッドの定義
abstract (戻り値)(メソッド名)(引数);

なお、このメソッドを子クラスで実装する場合、一般のクラスで、親クラスのメソッドをオーバーライドする際のように、override修飾子をつける必要があります。

抽象メソッドの実装
override (戻り値)(メソッド名)(引数);

このように、抽象クラスとは、抽象メソッドを持ち、動作をサブクラスで行われるようなクラスのことを言います。すでに述べた通り、抽象クラスがインスタンスを持つことはできません

抽象クラスのインスタンスを生成することはできない
× Bird b = new Bird("すずめ");

親クラスのコンストラクタの呼び出し

ところで、Birdクラスには、8行目から11行目で引数付きのコンストラクタが定義されていますが、このコンストラクタはどこから呼び出されるのでしょう。このコンストラクタは、サブクラスのコンストラクタから呼び出されています。

Crow2.javaの6行目、およびSparrow2.javaクラスの6行目に出ている、superを見てください。これは、親クラスのコンストラクタを呼び出す処理です。引数として、それぞれの鳥の名前を与えていますが、これを引数として、Birdクラスのコンストラクタが呼び出されます。これにより、親クラスのフィールドnameに値が設定されます。(図4-2.参照)

図4-2.superによる、親クラスのコンストラクタの呼び出し

親クラスのコンストラクタの呼び出し

抽象プロパティ

サンプルプログラム

抽象クラスで用いることができるのは、抽象メソッドだけではありません。プロパティも、抽象プロパティを持つことができます。以下のサンプルを実行してみてください。

プロジェクトSampleEx403/VectorBase.cs
using 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;
        }
    }
}
Vector.cs
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; }
        }
    }
}
Program.cs
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);
        }
    }
}
実行結果
v=(0.1,0.2)

Vectorクラスは、抽象クラスVectorBaseを実装しています。抽象プロパティも、抽象メソッドと同様、abstract修飾子がついており、中身には、setおよび、getのみが記述され、実装はありません。

それを実装しているのが、Vectorクラスです。メソッドの場合と同様に、プロパティにも、overrideをつければ、プロパティとして使用できます。

抽象クラスを用いることのメリット

クラスの抽象化

では、抽象クラスを用いることのメリットは何なのでしょう?このサンプルの場合、一見プログラムが却って長くなり、面倒な感じがします。しかし、「カラス」や「すずめ」のほかに、「にわとり」や「つばめ」などといった、新しい「鳥」のクラスを作ろうとした場合、どうでしょうか?

抽象クラスでは、あらかじめ「鳥」全体の共通の処理は実装されていますから、わざわざ新たに同じ処理を何度も記述する必要がありません。例えば、各クラスでは、それぞれの鳥に独特の特徴を記述すればよいのです。

抽象クラスとしてインスタンスを持つ

SampleEx402.csの、Program.csを以下のように変えてみてください。

プロジェクトSampleEx402/Program.cs
using 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.