🔧

オブジェクト指向とは(Unity初心者向け)

に公開

2025/05/14

始めに

『override?多態性?カプセル化?何のために…』
初めてオブジェクト指向に触れたとき、そんな疑問を持ったことがある方も多いのではないでしょうか?
私はありました。

この記事では、「初心者でも感覚で分かるオブジェクト指向」をテーマに、
カプセル化・継承・多態性などの基本概念を、Unityでの開発向けにざっくり紹介します。

開発効率が上がる理由

[オブジェクト指向]とは平たく言うと[現実の物のように、プログラムの中でも物として扱う ]
というシステム開発における重要且つ基本的な考え方の1つです。

処理を部品化し、組み合わせる事によってコードを作る方法です。

以下の Scratch の例を見てみましょう。
Scratchでは処理を“部品”として組み合わせるスタイルが、オブジェクト指向と似ています。

オブジェクト指向の考え方により、
再利用性・拡張性が高まり、結果として開発効率も向上します。

Scratchのブロック

スプライトが移動するScratchのスクリプト
(画像: Scratchのブロック)


オブジェクト指向の[4つの柱]

オブジェクト指向プログラミング(OOP)では、以下の4つが基本的な考え方とされています:

  • カプセル化
  • 多態性(ポリモーフィズム)
  • 継承
  • 抽象化

以下から、各柱について具体的なコード例とともに解説します。


カプセル化とは

平たく言うと「データを壁で守り、必要な出入り口を作る仕組み」です。

もし変数を誰でも何処でも読み書きができてしまったら、
原因不明のバグが発生しやすくなり、修正が難しくなってしまいます。

以下のコード例のように、
BadSampleでは Valueを直接読み書きが可能ですが、
GoodSampleでは_valueはprivateに守られており
外部からは読み取り専用のプロパティ GetValueを通じてアクセスします。
これにより、勝手な書き換えを防ぎ、安全なコード設計ができます。

BadSample:データがむき出し

// カプセル化していない
public class BadSample
{
		// 外部から直接読み書きできる
    public int Value = 0;
}

GoodSample:中を隠して安全に

// カプセル化してある
public class GoodSample
{
		// クラス内部のみで読み書きできる
    private int _value = 0;
    
    // プロパティを通じて読み取りのみ可能
    public int GetValue { get => _value; }
    
    // 初期値を代入
    public GoodSample(int value)
    {
        _value = value;
    }
}

長所

  • データの保護
  • バグの原因究明しやすくなる
  • 変更に強くなる

注意点

  • 手間が増える
  • 設計力が必要

つまりカプセル化は、安全で保守しやすいコードを書くために欠かせません!


多態性(ポリモーフィズム)とは(virtual / override)

平たく言うと「同じ関数名で、中身の処理だけを切り替える仕組み」です。

以下のコード例のように、
BadSampleは動物ごとに鳴く関数名Bark()やMeow()等がバラバラですが、
GoodSampleは鳴く関数名がSpeak()に統一されており、
Animal型の変数にDogやCatを代入しても、それぞれの処理が呼び出されます。

  • BadSample:統一されてない関数名

    //関数名統一していない
    
    // 犬クラス(鳴く関数名はBark)
    public class Dog
    {
        public void Bark()
        {
            Debug.Log("ワン");
        }
    }
    
    // 猫クラス(鳴く関数名はMeow)
    public class Cat
    {
        public void Meow()
        {
            Debug.Log("ニャー");
        }
    }
    
    // 動物ごとに個別で扱う必要があり、共通処理がしづらい
    // 新しい動物を追加するたびに条件分岐が必要になる
    
  • GoodSample:統一された関数名

    // 多態性を活用している。
    
    // 動物の共通クラス
    public class Animal
    {
        // 派生クラスで上書きできる仮想関数(virtual)
        public virtual void Speak() { }
    }
    // 犬クラス(Animal を継承)
    public class Dog : Animal
    {
        // Speak メソッドを上書き(override)
        public override void Speak()
        {
            Debug.Log("ワン");
        }
    }
    // 猫クラス(Animal を継承)
    public class Cat : Animal
    {
        // Speak メソッドを上書き(override)
        public override void Speak()
        {
            Debug.Log("ニャー");
        }
    }
    
  • Animal型でDogやCatを代入

    public void AddSpeak
    {
    	List<Animal> animals = new List<Animal>
    	{
    	    new Dog(),
    	    new Cat()
    	};
    
    	// すべてAnimal型として扱いながら、それぞれのSpeakが呼ばれる
    	foreach (var animal in animals)
    	{
        animal.Speak(); // 実行時に Dog か Cat の中身に応じて切り替わる
    	}
    }
    

長所

  • 可読性・保守性の向上
  • コードの再利用性向上
  • 柔軟な設計ができる

注意点

  • 処理の追跡が難しくなる
  • 誤用による設計の混乱

つまり多態性(ポリモーフィズム)は、
「同じインターフェースで異なる処理を実行する」柔軟な設計を可能にします。


継承とは

平たく言うと「親を基に子を作る遺伝」のようなものです。
複数のクラスに共通する処理を「親クラス」にまとめることで、重複をなくし、メンテナンス性を高めることができます。

Unityを使っている人ならよく見る MonoBehaviour も、この「継承」の仕組みを使っています。
以下のコード例では、Sample クラスが MonoBehaviour を継承しているため、Unityが自動的に Start() や Update() を呼び出してくれるようになります。

MonoBehaviourの例

using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start() { }
    private void Update() { }
}

そして継承の例として、以下のコード例のように、
BadSample では犬・猫・人のそれぞれに同じ Walk() と Dash() の処理を個別に書いていますが、
GoodSample では、共通の Animal クラスに Walk() と Dash() をまとめて書き、
各クラスがそれを 継承することで、重複するコードを書かずに実装できます。

  • BadSample:重複があるコード

    // 再利用がなく、コードの重複がある悪い例(BadSample)
    
    // 犬クラス
    public class Dog
    {
        public void Walk() { }
        public void Dash() { }
    }
    
    // 猫クラス
    public class Cat
    {
        public void Walk() { }
        public void Dash() { }
    }
    
    // 人間クラス
    public class Human
    {
        public void Walk() { }
        public void Dash() { }
    }
    
  • GoodSample:再利用性が高いコード

    // 継承を使って重複を避け、再利用性を高めた良い例(GoodSample)
    
    // 共通の動物クラス(Animal)
    public class Animal
    {
        // 歩く動作(共通)
        public void Walk() { }
    
        // ダッシュする動作(共通)
        public void Dash() { }
    }
    
    // 犬クラス(Animal を継承)
    public class Dog : Animal
    {
        // 特別な処理があればここに追加できる
    }
    
    // 猫クラス(Animal を継承)
    public class Cat : Animal
    {
        // 特別な処理があればここに追加できる
    }
    
    // 人間クラス(Animal を継承)
    public class Human : Animal
    {
        // 特別な処理があればここに追加できる
    }
    

長所

  • コードの再利用性が高い
  • 保守性が上がる
  • 一貫性のある設計ができる
  • 多態性(ポリモーフィズム)と組み合わせやすい

注意点

  • 親クラスに依存しすぎると柔軟性が落ちる
  • 設計が硬直化しやすい
  • classの多重継承が使えない(C#では)
  • 変更の影響範囲が大きい

つまり継承は共通する処理の重複を減らし、保守性や再利用性を高める仕組みです。
さらに、多態性(ポリモーフィズム)との組み合わせによって、柔軟で拡張性のある設計が可能になります。


抽象化とは(abstract / interface)

平たく言うと「複数のクラスに共通する処理や性質(例:ダメージを受ける、回復するなど)だけを取り出してまとめる」ものです。
その手段として、abstract や interface を使うことがあります。

例えるなら、「自動販売機のボタン」だけが見えていて、中の機械や配線がどうなっているかは知らなくてもいい、という状態です。
抽象化を用いることで、使い方と中身を分けられるので、バグを減らすことができ、拡張がしやすくなります。

以下のコード例のように、
BadSampleでは、Player と Enemy の両方に同じような AddDamage() や AddCure() 関数が個別に定義されています。
これでは、処理の重複が増え、メンテナンスが難しくなってしまいます。
一方、GoodSampleでは「ダメージを受ける」「回復する」といった共通の振る舞いを
abstract class や interface として抽出し、それを継承・実装することで重複を避けています。

BadSample:重複があるコード

public class Player
{
    public void AddDamage(int value) { }
    public void AddCure(int value) { }
}
public class Enemy
{
    public void AddDamage(int value) { }
    public void AddCure(int value) { }
}

abstractでの良い例

以下のコード例のように、共通機能は親クラスである Entityクラスにまとめ、
個別の処理は子クラスに任せる
ことで、変更に強い設計になります。

これにより、共通の処理を統一して管理できるため、バグ修正や機能追加が1箇所で済み、メンテナンス性が向上します。

  • GoodSample_abstract:共通点が分かりやすい

    public abstract class Entity
    {
        public abstract void AddDamage(int value);
        public abstract void AddCure(int value);
    }
    public class Player : Entity
    {
        public override void AddDamage(int value) { }
        public override void AddCure(int value) { }
    }
    public class Enemy : Entity
    {
        public override void AddDamage(int value) { }
        public override void AddCure(int value) { }
    }
    

interfaceでの良い例

interface は「複数の役割を持たせたいとき」や「柔軟に設計したいとき」に便利です。
abstract classと異なり、多重実装が可能なため、設計の幅が広がります。

interfaceは複数の機能を持てるので、例えば『攻撃できる』と『回復できる』を別々に実装することができます。

  • GoodSample_interface:機能の付け外しがしやすい

    // Player はダメージも回復もできるが、Enemy はダメージしか受けない、という設計
    
    public interface IDamageable
    {
        void AddDamage(int value);
    }
    public interface ICurable
    {
        void AddCure(int value);
    }
    public class Player : IDamageable, ICurable
    {
        public void AddDamage(int value) { }
        public void AddCure(int value) { }
    }
    public class Enemy : IDamageable
    {
        public void AddDamage(int value) { }
    }
    

特徴をまとめますと、

特徴 abstract class interface
継承数 1つまで 複数OK
用途 「共通の基本機能」がある時 「役割」や「能力」を付与したい時

長所

  • 設計の共通化・整理
  • 拡張や変更がしやすくなる
  • 役割分担が明確になる
  • 保守性の向上

注意点

  • 設計が複雑になることがある
  • 初心者には理解しづらい
  • 処理の追跡が難しいことがある

つまり抽象化は
共通する本質的な性質だけを取り出して整理し、使いやすく・変更しやすい設計を可能にする考え方」です。


オブジェクト指向の長所

  • 再利用性
    既存のクラスを使い回せる
  • 保守性
    修正箇所が限定される
  • 拡張性
    新しい機能追加が容易

オブジェクト指向の注意点

  • 長い設計時間
    構造を考える技術と時間がかかる。
  • 変更に弱い設計
    1つの変更が他にも影響を与える。

最後に

オブジェクト指向は最初こそ難しく感じるかもしれませんが、
「共通点をまとめる」「中身を隠す」「同じ形で振る舞いを変える」といった考え方を意識すれば、少しずつ理解できるようになります。

この記事で紹介したカプセル化・継承・多態性・抽象化は、コードを見やすく・直しやすく・拡張しやすくするための大切な基礎です。
ぜひ、実際のコードの中で使ってみて、感覚をつかんでいってください。

Discussion