抽象クラスを実装するメリットがいまいちよくわからなかったので、抽象クラスとは何か、どんな意図で(どんなメリットがあって)抽象クラスを実装するのかをまとめました。
本記事の内容
- 抽象クラスとは
- 抽象クラスを実装すると何が嬉しいのか
- Template Methodパターン(抽象クラスが用いられるデザインパターン)
- Template Methodパターンの実装例
抽象クラス(AbstractClass)とは
抽象クラスとは、抽象メソッドを1つ以上持つクラスです。
抽象クラスは、自身だけでは意味をもたず、サブクラスに継承されることで初めて機能します。
abstract class クラス名{
抽象メソッド
}
では、抽象メソッドとは何か?
抽象メソッドは、以下の形で書かれるメソッドです。
abstract class クラス名{
abstract 戻り値の型 メソッド名(引数の型 引数);
}
上記のように、abstractの後に、戻り値の型、メソッド名、引数の型、引数の数のみを定義し、実装を持たないのが抽象メソッドです。具体的な実装は、この抽象クラスを継承するサブクラスで実装されます。
抽象クラスの特徴
- 抽象クラスを継承したサブクラスは、抽象クラスにある抽象メソッドのオーバーライド必須
- サブクラスでコンストラクタを記述しなければならない
- 直接インスタンス化できない
- 多重継承はできない
抽象クラスを実装する意図
抽象クラスを実装するメリットは、抽象クラスの特徴1に書いた、"抽象クラスを継承したサブクラスは、抽象クラスにある抽象メソッドを必ずオーバーライドしなければならない" です。
これが意味する具体的なメリットは、、、
複数人で開発を行う場合に実装レベルのルールを作れる! です。
そう言われてもこのメリットは、企業での大規模開発を経験してみないと正直わからないと思います。(私は、さっぱりわかりませんでした)
では、ここで大規模開発(複数人で開発を行なっていて、コードの量は数千〜数十万行に及ぶような開発)を行なっているとして、単純な機能追加や画面追加によるプログラムの修正が必要になったとしましょう。
単純な機能追加の場合、メソッドのロジックは少し違えど、やりたいことは既に実装しているクラスと大体処理が同じ場合が多々あります。そんな場合、同じような処理をしているのにクラスによってメソッド名が異なると、何の処理をしているのか把握するのに時間がかかってしまったりします
それが、抽象クラスの特徴である強制的なオーバーライドにより、必ず書かなければいけないようにすることで、以下のような効果が得られます
- メソッド名を統一し、ロジックを共通化し、大体何の処理をしているか把握しやすくなる
- 共通の処理をいちいち全てのクラスに書き込む必要がなくなり、個別の処理も追加しやすくもなる
- 開発者がサブクラスを定義した際に、メソッドの実装忘れやメソッド名に間違いがあればコンパイルエラーが起き、コーディングミスを防ぐ
抽象クラスを実装すると、このように複数人で開発を行う場合に実装レベルのルールを作れる のです!!
これが抽象クラスを実装する意図であり理由の1つです。
それでは、次に抽象クラスを用いるデザインパターンとサンプルプログラムを紹介します。
Template Methodパターン とは
Template Methodとは、抽象クラスを用いて、スーパークラスで処理の枠組みを定め、サブクラスでその具体的な内容を定めるデザインパターンです。
Template Methodの登場人物
- AbstractClass(抽象クラス)の役
- テンプレートとなるメソッドを抽象メソッドで宣言
- ConcreteClass(具象クラス)の役
- AbstractClassで宣言されている抽象メソッドを具体的に実装
Template Methodの実装例
わかりやすいように、ログを出力するだけのとても簡単なサンプルプログラムです。
Template Methodの登場人物におけるAbstractClassの役にあたるのがPlayerクラスで、ConcreteClassの役にあたるのがMusicPlayerクラスとVideoPlayerクラスです。クラス図は以下の通りです。
では、プログラムを見ていきましょう。
まず、スーパークラスPlayerは以下のとおり、3つの抽象メソッドを持っています。
抽象メソッドの具体的な実装はサブクラスMusicPlayer, VideoPlayerそれぞれで行います。
public abstract class Player {
abstract void read();
abstract void write();
abstract void playback();
}
そして、Playerを継承するMusicPlayerクラスとVideoPlayerクラスはそれぞれ、Playerクラスにある抽象メソッドを全てオーバーライドして処理内容を記述しています。以下のように処理の内容は、サブクラスことに別々にできます。つまり、継承しているメソッド名は同じでも、処理の内容は継承したサブクラスごとに変えることができる、ということです。サブクラスMusicPlayerとVideoPlayerは以下の通りです。
public class MusicPlayer extends Player {
@Override
void read() {
Log.d("Music Player","read");
}
@Override
void write() {
Log.d("Music Player","write");
}
@Override
void playback() {
Log.d("Music Player","playback");
}
}
public class VideoPlayer extends Player {
@Override
void read() {
Log.d("Video Player","read");
}
@Override
void write() {
Log.d("Video Player","write");
}
@Override
void palyback() {
Log.d("Video Player","playback");
}
}
最後にメインクラスです。
ここにポイントがあります!
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MusicPlayer musicplayer = new MusicPlayer();
VideoPlayer videoplayer = new VideoPlayer();
Player[] player = {musicplayer,videoplayer};
for(int i=0; i<player.length; i++) {
player[i].read();
player[i].write();
player[i].playback();
}
}
}
メインクラスでは、MusicPlayerのインスタンスとVideoPlayerのインスタンスをあわせて、 1つのPlayer配列に入れていますね。 そしてforループで回して3つのメソッドを呼び出しています。
この部分で、player[ i ]に格納されているインスタンスが、instanceofなどでサブクラスの種類を調べていないことがポイント
これができるのは、Playerという共通のスーパークラスがあるからです。
このように『スーパークラス型の変数に、サブクラスのインスタンスのどれを代入しても正しく動作するようにする』ことはTemplate Methodパターンに限らない、継承の一般的な原則です。
いわゆるポリモーフィズムです。
もし、Template Methodパターンを使わなかったらどうなるでしょう?
MusicPlayerとVideoPlayerは、ばらばらで各クラスのインスタンスを別々に生成しないといけなくなります。
つまり、
抽象クラス(abstract class)を導入することは、 一つのスーパークラス(Player)の参照変数に他のサブクラス(MusicPlayerとVideoPlayer)のインスタンスを代入できるので、統一した記述方法を使用しながら、それぞれのオーバーライドメソッドを呼び出すことができる! という所が嬉しいのです(^^)
さいごに
自分がよくわからなかったのもあって、なぜ抽象クラスを実装するのかについて記しました。
抽象クラスの実装は、コード量も増えますし、構造を複雑にする要因にもなりますので、何でもかんでも抽象クラスを実装することは望ましくはないですが、こんなメリットがあるんだなということが伝わっていれば嬉しいです。
抽象クラスを用いる Template Method というデザインパターンと実装例も一緒に紹介しましたが、間違っているところなどあればご指摘いただけると嬉しいです。