#抽象クラスとは
抽象クラスとはクラスの一種で、次の特徴を持つ抽象的な概念です。
- 専ら他のクラスに継承されることによって使用される。
- インスタンスを持たない。
抽象クラスのメンバ関数、メソッドを仮想関数と言います。すなわち抽象クラスはクラスの中でも上位概念にあるものを言います。
#具体例
割とよくある例ですが、ここではcar
クラスとmotorcycle
クラスがあるとして、その基底クラスとしてvehicle
という抽象クラスを実装します。
「自動車」も「自動二輪車」も「乗り物」の一種で、発進と停止ができなければなりません。ですがそれぞれ発進も停止も方法が異なっています。なので、vehicleクラスでstartとstopを宣言し、carとmotorcycleでそれぞれ実装します。
##C++の例
C++では「=0となる仮想関数を持つものが抽象クラス」というようになっています。キーワードを使うわけではないので直感的ではありませんね。
またテスト部では、ポリモーフィズムを使うためにあえてポインタ変数で定義しています。別に実インスタンスとして呼び出しても構いません。
#include<iostream>
using std::cout;
using std::endl;
//抽象クラスvehicleの定義
class vehicle{
public:
virtual void start(void)=0;
virtual void stop(void)=0;
};
//vehicleを継承したクラスcarの定義
class car: public vehicle{
public:
void start(void){
cout << "car start" << endl;
}
void stop(void){
cout << "car stop." << endl;
}
};
//vehicleを継承したクラスmotorcycleの定義
class motorcycle: public vehicle{
public:
void start(void){
cout << "motorcycle start" << endl;
}
void stop(void){
cout << "motorcycle stop." << endl;
}
};
//テスト部
int main(void){
vehicle* mycar;
mycar = new car();
(*mycar).start();
(*mycar).stop();
return 0;
}
##Pythonの例
PythonではABC
というライブラリを使います。
from abc import ABCMeta
from abc import abstractmethod
#抽象クラスvehicleの定義
class vehicle(metaclass = ABCMeta):
@abstractmethod
def start(self):
pass
@abstractmethod
def stop(self):
pass
#vehicleを継承したクラスcarの定義
class car(vehicle):
def start(self):
print("car start.")
def stop(self):
print("car stop")
#vehicleを継承したクラスmotorcycleの定義
class motorcycle(vehicle):
def start(self):
print("moto start.")
def stop(self):
print("moto stop")
#テスト部
if __name__ == "__main__":
mycar = car()
mycar.start()
mycar.stop()
##fortranの例
よく古典語扱いされるfortranでも、実は抽象クラスの定義や継承ができます。
#メリット
多相性
抽象クラスを使うことでインスタンスのtypeによって挙動を変えるということができます。
C++ではポインタとしてインスタンスを定義したときに、その中身で呼ばれるメンバが自動的に変わります。例えばsmp01.cppのテスト部をmycar=new motorcycle()
とすれば、下の実行部を変えることなくmotorcycleのstartとstopを呼び出すことができます。
pythonはそもそも動的型付けなのですが、smp02.pyでmycar = motorcycle()
としてもきちんとmotorcycleのstartとstopが呼ばれて動きます。
##サブクラスの満たすべき要件を規定できる
そうはいっても「別にtypeof
とif分岐とかで処理すればいいんじゃないの」という気がします。しかも結局実装はサブクラスで行っているので、コーディング量が減るわけでもありません。pythonの例に至ってはvehicleを丸々カットしても何も動作は変わりません。そうなると抽象クラスを使うメリットはどこにあるのでしょう。
答えはサブクラスの持つべき要件を定められることにあります。抽象クラスを定めることでサブクラスが持つべきメンバを定めることができます。抽象クラスの持つメンバは必ずサブクラスでオーバーライドしなければいけません。
vehicle
の例に戻ります。仮に新しくbicycle
クラスを作ったとしましょう。bicycle
クラスを追加するにあたって、vehicle
クラスで定義されているメンバ関数start
、stop
がすべて同じ形で実装されている必要があります。
vehicle
がなければ、bicycle
を作る人は発進をstart
ではなくdepart
というメンバ関数で実装してしまうかもしれません。競輪が好きな人はstop
を実装しないことさえあり得ます。
class bycycle{
public:
int depart(void);
};
そうとは知らず、インスタンスを使う人がcar
やmotocycle
と同じ感覚でbicycle.start()
とすると、「そんなものはない」とコンパイルがはじかれます。
さらに困るのは「start」という同じ名前のメンバが違った引数や戻り値で書かれている場合です。今回はすべて同じファイルに書いているので大したことないように思えますが、実際は実装は分けて書いたりするのでそうなると大混乱です。
しかし、抽象クラスvehicleが定義されていれば、2つのメンバ関数void start(void)
とvoid stop(void)
がこの形で定義されていなければコンパイルが通りません。こうしてmainを実装する人は安心して発進と停止ができるわけです。
#まとめ
- 抽象クラスはクラスのメンバ、すなわち構造を定める。
- 抽象クラスを使えばポリモーフィズムを実装できる。
これらのメリットはクラスを複数扱うような大規模なコードで明らかになってきます。ポリモーフィズムが強調されているwebサイトやテキストが多いですが、個人的にはもう一つの役割の方が重要だと思います。
サンプルコード:https://gist.github.com/Bluepost59/79dbd90285d07c312a5326c4121cc86a