Edited at

抽象クラスを使うメリット

More than 1 year has passed since last update.


抽象クラスとは

抽象クラスとはクラスの一種で、次の特徴を持つ抽象的な概念です。


  • 専ら他のクラスに継承されることによって使用される。


  • インスタンスを持たない

抽象クラスのメンバ関数、メソッドを仮想関数と言います。すなわち抽象クラスはクラスの中でも上位概念にあるものを言います。


具体例

割とよくある例ですが、ここではcarクラスとmotorcycleクラスがあるとして、その基底クラスとしてvehicleという抽象クラスを実装します。

「自動車」も「自動二輪車」も「乗り物」の一種で、発進と停止ができなければなりません。ですがそれぞれ発進も停止も方法が異なっています。なので、vehicleクラスでstartとstopを宣言し、carとmotorcycleでそれぞれ実装します。


C++の例

C++では「=0となる仮想関数を持つものが抽象クラス」というようになっています。キーワードを使うわけではないので直感的ではありませんね。

またテスト部では、ポリモーフィズムを使うためにあえてポインタ変数で定義しています。別に実インスタンスとして呼び出しても構いません。


smp01.cpp

#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というライブラリを使います。


smp02.py

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でも、実は抽象クラスの定義や継承ができます。

https://qiita.com/Bluepost59/items/8d4b7d7713676fe06472


メリット


多相性

抽象クラスを使うことでインスタンスの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クラスで定義されているメンバ関数startstopがすべて同じ形で実装されている必要があります。


vehicleがなければ、bicycleを作る人は発進をstartではなくdepartというメンバ関数で実装してしまうかもしれません。競輪が好きな人はstopを実装しないことさえあり得ます。


smp03.cpp

class bycycle{

public:
int depart(void);
};

そうとは知らず、インスタンスを使う人がcarmotocycleと同じ感覚でbicycle.start()とすると、「そんなものはない」とコンパイルがはじかれます。

さらに困るのは「start」という同じ名前のメンバが違った引数や戻り値で書かれている場合です。今回はすべて同じファイルに書いているので大したことないように思えますが、実際は実装は分けて書いたりするのでそうなると大混乱です。

しかし、抽象クラスvehicleが定義されていれば、2つのメンバ関数void start(void)void stop(void)がこの形で定義されていなければコンパイルが通りません。こうしてmainを実装する人は安心して発進と停止ができるわけです。


まとめ


  • 抽象クラスはクラスのメンバ、すなわち構造を定める。

  • 抽象クラスを使えばポリモーフィズムを実装できる。

これらのメリットはクラスを複数扱うような大規模なコードで明らかになってきます。ポリモーフィズムが強調されているwebサイトやテキストが多いですが、個人的にはもう一つの役割の方が重要だと思います。

サンプルコード:https://gist.github.com/Bluepost59/79dbd90285d07c312a5326c4121cc86a