[第14回] 抽象クラス(abstract)の使い方を学ぶ|Unityで学ぶC#入門
「Unityで学ぶC#入門」の連載第13回は「リストの基本的な使い方を学ぶ」でした。
第14回の今回は抽象クラスの使い方を学びます。
抽象クラスや抽象メソッドと呼ばれるものはオブジェクト指向プログラミングには欠かせない考え方の一つです。
抽象クラスは通常のクラスと違って制約の多いクラスで使う意味がわからないという方が多くいらっしゃいます。
そこで今回は抽象クラスについて何ができて、いったいどのようなメリットが存在するのかをわかりやすく解説していきます。
クラスの概念を理解していないと抽象クラスを理解するのに苦労してしまうかもしれませんので、もし心配な方は以下記事をご参考ください。
参考記事)クラスの基本的な使い方を学ぶ
抽象クラスについて
抽象クラス(abstractクラス)とは「抽象メソッドを1つ以上持つクラス」のことです。
抽象メソッドとは中身の処理が記述されていないメソッドのことで、基本的に以下の情報を持っています。
- メソッド名
- 戻り値の型
- 引数の型
- 引数名
これだけではよくわからないので抽象クラスについてのイメージ図をご覧ください。
抽象クラスは図のようにサブクラスと呼ばれるクラスに継承され利用されます。つまり、サブクラスが利用しない限りこのクラスを利用することはできません(直接インスタンス化できません)。
継承とは、オブジェクト指向プログラミングにおいてよく利用される言葉ですのであえて使用しますが特別難しい意味はありません。ここでは単純に「抽象クラスを利用すること」という意味で使用しています。
サブクラスとは抽象クラスXのような親クラスを持つクラスのことです。また、サブクラスは複数の親クラスを持つことができません。
図ではクラスXがA~Cのクラスに継承されています。継承先のクラスでは抽象クラスで宣言された抽象メソッドを具体化(オーバーライド)する必要があります。また、サブクラスでは抽象クラスのもつメソッド以外のメソッドを定義することも可能です。
以上のことから、この抽象クラスには次のような特徴があることがわかります。
- 直接インスタンス化できない(単体利用不可)
- 抽象クラスのメソッドをサブクラスで必ずオーバーライドする
- 複数の親クラスを持つことができない
抽象クラスを利用するメリット
抽象クラスが利用される大きなメリットの一つとして実装の仕方を決めることができる点にあります。
抽象クラスに記述されている抽象メソッドを必ずオーバーライドする必要があるので実装忘れが減ります。つまり、抽象クラスに実装してほしいメソッドを全て準備しておくことで抽象メソッドを作成した人以外が実装する場合にも必ずそのメソッドがサブクラスで実装されるということです。これにより実装の仕方を決定することができます
それだけでなく、クラス間の関係がわかりやすくなるのでプログラム自体がスッキリします。
抽象クラスの使い方
次にC#での抽象クラスの使用方法をご紹介します。
まずは抽象クラスの記述方法です。
1 2 3 4 |
abstract class ClassX { public abstract int X-1(); } |
classの前に「abstract」をつけることでそのクラスを抽象クラスにすることができます。
抽象メソッドを宣言する場合は、戻り値の型の前に「abstract」をつけます。
次に作成した抽象クラスを利用する方法です。
1 2 3 4 5 6 7 8 9 10 11 12 |
class ClassA : ClassX { public override int X-1() { // 処理を記述 } public int A-1() { // 処理を記述 } } |
クラス定義の後ろに「: 継承したいクラス」をつけることで指定したクラスを継承することができます。
また、オーバーライドするメソッドには「override」をつける必要があります。
以上が抽象クラスの実装方法です。ここから具体例をみていきましょう。
RPGで抽象クラスを考える
具体例として簡易的なRPGゲームを考えてみましょう。
今回はプレイヤー側のみを考えます。
RPGのプレイヤー側は4人のキャラクターで構成されています。
それぞれ職業を持っていて、攻撃方法も異なります。このキャラクター達を作成するときに抽象クラスを利用した場合とそうでない場合を考えていきます。
まずは抽象クラスを利用しない場合を考えます。
抽象クラスを使用しない場合
こちらはコードに記述した方がわかりやすいので実際にコードにしてみましょう。
ここでは勇者クラスである「Brave.cs」を確認してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Brave : MonoBehaviour { private string name; // 名前 private string job = "brave"; // 職業 private int hp; // HP private int mp; // MP // コンストラクタ public Brave(string name){ this.name = name; this.hp = 100; this.mp = 100; } // 攻撃メソッド public void Attack(){ Debug.Log(this.name + "の攻撃!"); Debug.Log("剣で攻撃!"); } // 名前と職業を表示するメソッド public void ShowProf(){ Debug.Log("私の名前は" + this.name); Debug.Log("職業は" + this.job.ToString()); } } |
コンストラクタで名前を設定でき、攻撃メソッドによって攻撃できます。(ログ出力のみ)
次にこのPersonを生成するクラス「GameController.cs」を確認してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameController : MonoBehaviour { private Brave brave = new Brave("Hanako"); void Start(){ brave.ShowProf(); brave.Attack(); } } |
勇者をインスタンス化して各メソッドを呼び出しています。
同じような考え方で「Mage」「Monk」「Warrior」クラスを作成することでそのほかの職業も作成します。
その際、抽象クラスを利用していない場合に起こりうることとして、以下のようなことが挙げられます。
- どのキャラクターにも存在しなければならないHPやMP、攻撃メソッドを実装し忘れる
- 攻撃メソッドの命名にブレが生じる(「Attack()」や「attack()」)
- 同じキャラクターであるのに実装方法が大きく異なる
上記のような問題を抽象クラスを用いることによって回避することができます。
抽象クラスを使用する場合
抽象クラスを利用する場合、共通化できる部分を洗い出して一つにまとめます。
今回はCharacterクラスを作成してそれぞれの職業クラスに派生させていきます。(下記イメージ参考)
まずはどのキャラクターにも適用できる抽象クラス「Character」を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
using System.Collections; using System.Collections.Generic; using UnityEngine; // 職業Enum public enum Job{ brave, // 勇者 mage, // 魔法使い monk, // 僧侶 warrior // 戦士 } public abstract class Character : MonoBehaviour { private string name; // 名前 private Job job; // 職業 private int hp; // HP private int mp; // MP public Character(string name,Job job){ this.name = name; this.job = job; switch(job){ case Job.brave: this.hp = 100; this.mp = 100; break; case Job.mage: this.hp = 80; this.mp = 120; break; case Job.monk: this.hp = 90; this.mp = 110; break; case Job.warrior: this.hp = 120; this.mp = 80; break; default: Debug.Log("その職業は存在しません"); break; } } // 職業に応じて攻撃方法を変更 public abstract void Attack(); // 名前と職業を表示する public void ShowProf(){ Debug.Log("私の名前は" + this.name); Debug.Log("職業は" + this.job.ToString()); } public string GetName(){ return this.name; } public Job GetJob(){ return this.job; } } |
抽象クラスであるCharacterにはキャラクターが持つべきHPなどのフィールドと、抽象メソッドであるAttackメソッドがあります。
また、各情報にアクセスするためのGetterとプロフィールを出力するメソッドも実装してあります。
HPやMPに関してはSwitch文で値を設定できるように実装してあります。
それではこのCharacterクラスを親にもつクラス「Brave」を確認してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Brave : Character { public Brave(string name):base(name,Job.brave){ // コンストラクタに必要な処理を記述 } public override void Attack(){ Debug.Log(GetName() + "の攻撃!"); Debug.Log("剣で攻撃!"); } } |
Braveでは抽象メソッドの処理を記述します。また、コンストラクタでは親クラスのフィールドに値を設定するため、「base()」を利用して親クラスのコンストラクタを利用します。
コントローラークラスは先ほどと同じように利用することができます。
以下に勇者以外の実装方法も記述しておきます。
↓ Mage.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Mage : Character { public Mage(string name):base(name,Job.mage){ // コンストラクタに必要な処理を記述 } public override void Attack(){ Debug.Log(GetName() + "の攻撃!"); Debug.Log("魔法で攻撃!"); } } |
↓ Monk.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Monk : Character { public Monk(string name):base(name,Job.mage){ // コンストラクタに必要な処理を記述 } public override void Attack(){ Debug.Log(GetName() + "は攻撃することができない!"); } public void recovery(){ Debug.Log(GetName() + "は回復魔法を使った!"); } } |
Monkクラスのように、抽象クラスで宣言したメソッド以外のメソッドも実装可能です。
↓ Warrior.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Warrior : Character { public Warrior(string name):base(name,Job.brave){ // コンストラクタに必要な処理を記述 } public override void Attack(){ Debug.Log(GetName() + "の攻撃!"); Debug.Log("斧で攻撃!"); } } |
以上のように実装することで各クラスがシンプルになります。
もしAttackメソッドをオーバーライドし忘れてしまっても、以下のようなエラーが発生するので記述忘れの心配もいりません。
まとめ
いかがでしたでしょうか。
今回は抽象クラスの使い方を学びました。
抽象クラスを利用することでクラスをわかりやすくまとめたり、実装ミスを減らしたりすることができます。
また、外部ライブラリなどを利用する際にも抽象クラスを介して利用するものも存在しますので、使用方法を理解しておいて損はありません。
今回さりげなく使用してしまったEnumについても次回以降の連載で解説する予定ですので乞うご期待ください。
他の回の記事について気になる方はこちらをクリック
そのほかの参考記事)abstract (C# リファレンス) (Microsoft公式)
列挙型enumについて)[第15回] 列挙型(Enum)の使い方を学ぶ

この記事はいかがでしたか?
もし「参考になった」「面白かった」という場合は、応援シェアお願いします!