【Java】ポケモンで例える抽象クラスとインタフェースの使い方
概要/背景
概要
・Javaの抽象クラス/インタフェースについて例えを入れながら説明する記事となってます。
・例え話にポケモンを使っています。法律に触れそうならご一報ください😭
・Javaの環境はJava8となっています。
対象とする読者
記事を書くきっかけ
仕事で引き継いだカオスコードの運用保守したり、自分の昔書いたコードを見ていると
もっとルールを統一化した書き方ってできないんか🤔?と疑問を持ちました。
そこで色々調べていると、
抽象クラスとインタフェースにぶち当たったので学習したという感じです。
ルールは学習すれば分かるけど、使い道が分からなかったので
ポケモンを使って整理したところ
あぁ、こういう使い方かも?って思ったので自分のメモも兼ねて書き記しています。
1. 抽象クラスを作ってみよう
抽象クラスって?
ふむふむ、自分で書いててアレなんですがこれで分かったらこの記事読む必要ないです!
とりあえず、「抽象メソッド」ってメソッドがあれば
抽象クラスなんだなと覚えておけば今はOKです!
ポケモンで例えた抽象クラス
今回は、ポケモンの共通要素として下記を定義します。
ピカ様はボール入らんやろ!ふざけんな!!
・ポケモンはモンスターボールに入ることができる
・ポケモンはモンスターボールから出ることができる
・ポケモンは鳴くことができる
・ポケモンはHP(体力)を持つ
以上を踏まえて実装した抽象クラス↓↓
package pokemon;
/**
* ポケモンを司る親クラス(抽象クラス)
*/
public abstract class AbstractPokemonClass {
// ポケモンのHP
int hp;
// コンストラクタ
AbstractPokemonClass(int hp){
this.hp = hp;
}
/**
* ポケモンは共通してモンスターボールに入ることができる
*/
void monsterBallIn() {
System.out.println("モンスターボールに入るよ!");
}
/**
* ポケモンは共通してモンスターボールから出ることができる
*/
void monsterBallOut() {
System.out.println("モンスターボールから出るよ!");
}
/*
* ポケモンは鳴くことができる
*/
abstract void speak();
}
ここでポイントになるのは『鳴く』メソッドです。
『モンスターボールに入る/出る』メソッドはお馴染みの書き方ですが、
『鳴く』メソッドはメソッドの具体的な処理を記述していません。
なぜなら、鳴き声はポケモンによって違うからです。
ピカチュウは「ぴかー!」
ポッチャマは「ぽちゃー!」
というようにポケモンそれぞれが持つ鳴き声は違いますよね!
なので、具体的な処理を記述することはできませんよね。
一方、『モンスターボールに入る/出る』動きはどんなポケモンでも共通です。
なので、具体的な処理を記述することができます。
まとめると、
・お馴染みの書き方をするメソッドは『具体的』な処理を記述している。
→ 具体的な処理を書くメソッド:『具象メソッド』
・今回出てきた抽象メソッドは『具体的』な処理を記述していない。
→ 具体的な処理を書かない(抽象的な)メソッド」:『抽象メソッド』
2. 抽象クラスを継承して子クラスを作ってみよう
継承って?
ピカチュウとポッチャマ2種類のクラス
package pokemon;
/*
* ピカチュウを司るクラス
*/
public class Pikachu extends AbstractPokemonClass {
// コンストラクタ
public Pikachu(int hp) {
super(hp);
}
@Override
void speak() {
System.out.println("ぴかー!");
}
}
package pokemon;
/*
* ポッチャマを司るクラス
*/
public class Pottyama extends AbstractPokemonClass {
// コンストラクタ
public Pottyama(int hp) {
super(hp);
}
@Override
void speak() {
System.out.println("ぽちゃー!");
}
}
Mainクラス
package pokemon;
public class MainClass {
public static void main(String[] args) {
// 私のピカチュウを定義(HP:100)
Pikachu myPikachu = new Pikachu(100);
// 私のポッチャマを定義(HP:50)
Pottyama myPottyama = new Pottyama(50);
// 出てこい!ピカチュウ!!
myPikachu.monsterBallOut();
// ぴかー!
myPikachu.speak();
// 出てこい!ポッチャマ!!
myPottyama.monsterBallOut();
// ぽちゃー!
myPottyama.speak();
}
}
無事実装ができて、Mainの世界に私のポケモンを定義することができました!
3. インタフェースを作ってみよう!
さて、ここまでで抽象クラスはなんとなく分かったと思います。
次はインタフェース(インターフェース)のお話をしていきます!
インタフェースって?
・・・🤔???????????????
抽象クラスと似ている気もしますが、『多重継承』の有無は明確な違いですね。
ではでは、ポケモンに例えていきたいと思います。
ポケモンで例えたインタフェース
ポケモンはどのシリーズも始めに炎・水・草の3種いずれかをパートナーにする風習があります。
このポケモン達を俗に『御三家』と言います。
御三家には以下のような特徴があり、その特徴をインタフェースで定義したいと思います。
・究極技を覚えることができる(ハイドロカノン/ブラストバーン/ハードプラント)
・誓い技を覚えることができる(水の誓い/炎の誓い/草の誓い)
以上を踏まえて実装したインタフェース↓↓
package pokemon;
/*
* 御三家インタフェース
*/
public interface Gosanke {
// 究極技
abstract void kyukyokuWaza();
// 誓い技
void tikaiWaza();
}
ポッチャマに御三家インタフェースを定義
package pokemon;
public class Pottyama extends AbstractPokemonClass implements Gosanke {
// コンストラクタ
public Pottyama(int hp) {
super(hp);
}
@Override
void speak() {
System.out.println("ぽちゃー!");
}
@Override
public void kyuukyokuWaza() {
System.out.println("ハイドロカノンを使った!");
}
@Override
public void tikaiWaza() {
System.out.println("水の誓いを使った!");
}
}
ポッチャマクラスに御三家インタフェースを定義したことにより、
究極技と誓い技ができるようになりました!
抽象クラスの説明でもあったように、
抽象メソッドは再定義しないとコンパイルエラーになります。
そのため、究極技と誓い技を再定義してルールを統一化することができます!
4. 抽象クラスとインタフェースの使い分け
抽象クラスとインタフェースってほぼ同じ?
感のいい方はお気づきになったかもしれません。
『抽象クラス』と『インタフェース』って似てない?と...
私もこのことが気になって夜しか眠れなくなっていましたが、
ポケモンのおかげで朝でも昼でも仕事中でも寝ることができるようになりました!
※ 仕事中は寝たらダメだよ!
さて、インタフェースと抽象クラスの違いはこのようになっていましたよね。
多重継承ができる → インタフェース
多重継承ができない → 抽象クラス
この、『多重継承』ができるメリットが分かれば使い分けができそうですね!
ポケモンで例えた多重継承
ポケモンにはタイプという概念があります。ということで、タイプをインタフェース化します。
・でんきタイプ → でんきタイプはびりびりできる
・みずタイプ → みずタイプは泳ぐことができる
package pokemon;
/**
* 電気タイプ
*/
public interface DenkiType {
// 電気をびりびり
void bilibili();
}
package pokemon;
/**
* 水タイプ
*/
public interface MizuType {
// すいすい泳ぐ~
void swim();
}
ポッチャマにみずタイプ,御三家を多重継承する
package pokemon;
public class Pottyama extends AbstractPokemonClass implements Gosanke,MizuType {
// コンストラクタ
public Pottyama(int hp) {
super(hp);
}
@Override
void speak() {
System.out.println("ぽちゃー!");
}
@Override
public void hidenWaza() {
System.out.println("ハイドロカノンを使った!");
}
@Override
public void tikaiWaza() {
System.out.println("水の誓いを使った!");
}
@Override
public void swim() {
System.out.println("すいすい~");
}
}
『多重継承』ができることでポッチャマは3つの要素を持つことができました!
5. ポリモーフィズム(多態性)、カプセル化について
ポリモーフィズム
タイトルからはみ出るので詳しくご紹介はしませんが、
抽象メソッドを理解することでポリモーフィズムに触れる事ができました。
package pokemon;
import java.util.ArrayList;
import java.util.List;
public class MainClass {
public static void main(String[] args) {
// 私のピカチュウ
Pikachu myPikachu = new Pikachu(100);
// 私のポッチャマ
Pottyama myPottyama = new Pottyama(50);
// 私のポケモンリスト
List<AbstractPokemonClass> myPokemonList = new ArrayList<>();
// インスタンスの型は違うけど、継承しているのでエラーが起きない(多態性)
myPokemonList.add(myPottyama);
myPokemonList.add(myPikachu);
// 抽象メソッドで『鳴く』メソッドを定義しているため、
// インスタンスの型が違う場合でもエラーが起きない
speakListAllPokemon(myPokemonList);
}
/**
* 引数のリストにいる全てのポケモンが鳴く
* @param pokemonList ポケモンリスト
*/
private static void speakListAllPokemon(List<AbstractPokemonClass> pokemonList) {
pokemonList.forEach(v -> v.speak());
}
}
カプセル化
一方、カプセル化については上手にに実装できていません。
なぜなら、ポッチャマは究極技でなんでもできてしまうからですね。
※ 究極技でハードプラントをしたり、10万ボルトをできたりしてしまう。
このことに関してはあまりしっくりくる解答がまだ出せてないです・・・。
6. まとめ
私がJavaを学習したのは大学生の頃だったのですが、その時は抽象クラスやインタフェースのメリットを感じることはありませんでした。
しかし、社会人になってカオスなコードと戦っているうちにルールの統一化を促進させる
「インタフェース」「抽象クラス」は物凄い重要な要素なんだなと思いました。
また、書いてる間に本当にあっているのか・・・?という疑問も持ってしまいました。
触れられてない部分(protectedが定義できないとか)もあり
記事として不完全な部分もあるのは反省点です。
発信者が間違った知識を発信するのはあってはならないことだと思いますが、
気づかないものは気づけないのでその時はご指摘いただければと思います(2回目)
というわけで、長々とありがとうございました!!
これからも学習を続けてチームを引っ張れるような
つよつよエンジニアになっていきたいなと思います!でわでわ。
Discussion