2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Unity Learn】見たからまとめる(その2)【中級レベルのスクリプティング】

Last updated at Posted at 2021-11-18

#この記事について
タイトル通りの記事
詳細はその1の記事からどうぞ

##ポリモーフィズム
###ポリモーフィズムとは
同じ関数であっても呼び出すクラスが異なる場合異なる挙動をすることが出来る多様性の事

###実例
基底クラスとなるFruitクラスを定義

public class Fruit 
{
    public Fruit()
    {
        Debug.Log("1st Fruit Constructor Called");
    }

    public void Chop()
    {
        Debug.Log("The fruit has been chopped.");        
    }

    public void SayHello()
    {
        Debug.Log("Hello, I am a fruit.");
    }
}

Fruitクラスを継承するAppleクラスを定義

public class Apple : Fruit 
{
    public Apple()
    {
        Debug.Log("1st Apple Constructor Called");
    }

    // Apple には、独自のバージョンの Chop() と SayHello() が存在
    // "new"というキーワードは以降のオーバーライドで出てくる
    public new void Chop()
    {
        Debug.Log("The apple has been chopped.");        
    }

    public new void SayHello()
    {
        Debug.Log("Hello, I am an apple.");
    }
}

上記2つのクラスを使用するクラスを定義

public class FruitSalad : MonoBehaviour
{
    void Start () 
    {
        // ここで、変数 "myFruit" の型は Fruit なのに
        // Apple クラスのオブジェクトへの参照が割り当てられている
        // この場合Fruit is A appleでありAppleは親オブジェクトのFruitを継承しているので
        // Fruitの変数名でAppleクラスのコンストラクタを読んでも問題なく使用できる
        // アップキャスティングと呼ばれる
        // この場合myFruitはApple型ではなくFruit型になることに要注意

        Fruit myFruit = new Apple();

        myFruit.SayHello();
        myFruit.Chop();

        // ダウンキャスティングとの説明
        // Fruit 型の変数 "myFruit" には、実際には Apple への参照が格納されてる
        // よって、Apple 型の変数に戻すことが可能
        // これをダウンキャスティングと呼びこれにより、Apple 型の変数として使用可能となる
        // この処理を行う前は、Fruit 型の変数としてしか使用できなかったことに注意

        Apple myApple = (Apple)myFruit;

        myApple.SayHello();
        myApple.Chop();    
    }
}
/*実行結果(本来入っていないが、見やすくするため改行を入れる)
  Hello, I am a fruit.
  The fruit has been chopped.

  Hello, I am an apple.
  The apple has been chopped.
*/

##メンバーの隠蔽
###隠蔽とは
派生クラスは基底クラスと同じメンバを重複して保持でき、インスタンス化した際のオブジェクトの型でどれが呼び出されるかが決定する
派生クラスの方で基底クラスのメンバを隠蔽する際にはnew修飾子を使用する
###実例
Humanoidクラス(全ての基底クラス)

public class Humanoid
    {
        // 基底クラスのYell()を定義
        public void Yell()
        {
            Debug.Log("Humanoid version of the Yell() method");
        }
    }

Enemyクラス(Humanoidクラスを継承)

public class Enemy : Humanoid
    {
        // newで明示的に基底クラスのYell()を隠蔽
        public new void Yell()
        {
            Debug.Log("Enemy version of the Yell() method");
        }
    }

Orcクラス(Enemyクラスを継承)

public class Orc : Enemy
    {
        // newで明示的に基底クラスのYell()を隠蔽
        public new void Yell() 
        {
            Debug.Log("Orc version of the Yell() method");
        }
    }

上記クラスを使用するWarBandクラス

public class WarBand : MonoBehaviour
    {
        private void Start()
        {
            // それぞれの派生クラスでインスタンス化
            // 基底クラスのYell()はnew修飾子で隠蔽され挙動が変化
            Humanoid human_org = new Humanoid();
            Enemy enemy_org = new Enemy();
            Orc orc_org = new Orc();
            human_org.Yell();
            enemy_org.Yell();
            orc_org.Yell();
            
            Debug.Log("継承元でアップキャストしてインスタンス化");
            
            Humanoid human = new Humanoid();
            Humanoid enemy = new Enemy();
            Humanoid orc = new Orc();

            human.Yell();
            enemy.Yell();
            orc.Yell();
        }
    }

実行結果
キャプチャ.PNG

各派生クラスでインスタンス化した際にしっかりYell()関数の挙動が変化している事が分かる

##オーバーライド
###オーバーライドとは
基底クラスのメソッドの動作を派生クラス内で上書きすること
ポリモーフィズム を実現する際によく利用される
###実例
基底クラスであるFruitクラス

public class Fruit
    {
        public Fruit()
        {
            Debug.Log("1st fruit Constructor Called.");
        }

        // virtual修飾子を付ける事で子クラスでオーバーライド出来るようになる
        public virtual void Chop()
        {
            Debug.Log("The fruit has been chopped.");
        }

        public virtual void SayHello()
        {
            Debug.Log("Hello, I am fruit.");
        }
    }

Fruitクラスを継承する派生クラスAppleクラス

public class Apple : Fruit
    {
        public Apple()
        {
            Debug.Log("1st Apple Constructor Called.");
        }

        // 下記のメソッド群はoverride修飾子が付いているため
        // 親クラスの仮想クラスをオーバーライドしている
        public override void Chop()
        {
            base.Chop();    // baseで親クラスFruitのChop()を呼び出す
            Debug.Log("The apple has been chopped.");
        }

        public override void SayHello()
        {
            base.SayHello();    // baseで親クラスFruitのSayHello()を呼び出す
            Debug.Log("Hello I am an apple.");
        }
    }

上記クラスを実際に使用するFruitSaladクラス

public class FruitSalad : MonoBehaviour
    {
        private void Start()
        {
            Apple myApple = new Apple();
            
            // AppleクラスのメソッドがFruitクラスのメソッドをオーバーライドしており
            // Appleクラスメソッド内でbaseキーワードを使用しているためFruitのメソッドも呼び出される
            myApple.SayHello();
            myApple.Chop();

            // AppleクラスをFruitクラスでアップキャストしてインスタンス化
            Fruit myFruit = new Apple();
            
            // FruitクラスメソッドはvirtualなメソッドでAppleクラス内でオーバーライド
            // されているため、アップキャストしてもAppleクラスのメソッドが使用される
            // この挙動はポリモーフィズムの実現に役に立つ
            myFruit.SayHello();
            myFruit.Chop();
        }
    }

実行結果(baseで基底クラスのメソッドも実行してるから長いよ)
キャプチャ.PNG

##インターフェイス
###インターフェイスとは
クラスが実装すべきメソッド群を記したもの
基底クラスAと派生クラスBの関係はB is Aと記されるが、インターフェイスIとクラスCの関係はC implements Iとなっている
インターフェイスに記載されたメソッドは実装されなければならないが、その内容はなんでも良い

インターフェイスはジェネリクスをとる事もでき、継承する際には型を指定する
継承の際に指定した型は実装するメソッドでも守らなければならない

###要点

  1. インターフェイスを継承したクラスはそのインターフェイスに記述されたメソッドを必ず実装しないといけない
  2. 基底クラスからの継承と違い、インターフェイスの継承はカンマを使用して複数継承可能
  3. インターフェイスは基本1つ1スクリプト
  4. 命名規則としては先頭にIをつけ大文字で始め、クラスが持つ機能を記述したものなので末尾にableを付ける(ただし意味はしっかり考えて命名しないといけない)

###実例
コードに入る前に、インターフェイスの有用性などをまとめた記事があったのでより詳細に知りたい方はコチラをどうぞ

ダメージを受ける事を示したIDamageableインターフェイス

public interface IDamageable<T>
{
    void Damage(T damageTaken);
}

殺される事が可能な事を示したIKillableインターフェイス

public interface IKillable
{
    void Kill();
}

上記二つのインターフェイスを継承したAvatarクラス

public class Avatar : MonoBehaviour, IKillable,IDamageable<float>
    {
        public void Start()
        {
            Damage(30f);
            Kill();
        }

        // ジェネリクスをfloatで宣言しているのでfloat型の引数のメソッドとなる
        public void Damage(float damageTaken)
        {
            Debug.Log(damageTaken + "ダメージを受けた!");
        }

        public void Kill()
        {
            Debug.Log("死んでしまうとは情けない!");
        }
    }

実行結果
キャプチャ.PNG

###実用例
車と壁と言った全く関係ない2つのクラスに、ダメージを与えた際にコンソールに表示する機能を付けたい
しかし車と壁では共通する概念が無いため、基底クラスを定義することが出来ない

そこでIDamageableインターフェイスを使用することで、全く関係ない車と壁というクラスに同じ機能を持たせつつ、他のダメージを与えれる物をまとめて検索する事が出来るようになる

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?