asmdef内クラスからasmdef外クラスのメソッドを呼びたい!
という状況に遭遇することがあります。
たとえば、
- 利用しているサードパーティー製のライブラリがasmdefに対応していない
- 古めの自社コードがasmdefに対応しておらず、すぐに対応させるのも困難
みたいな状況です。
結論から書きます。
asmdef内クラスからasmdef外クラスのメソッドを 直接呼ぶことは不可能です!
しかし、 間接的にメソッドを呼ぶことはできます!
asmdefがなければ何も気にしなくていい
まず、asmdefがない状況を考えます。
たとえば、ユーザーの身長・体重の値を更新したら、BMIを計算する処理を実装したいとします。
具体的には、以下のように、 UserProfile
から BmiUtility
を参照したい!という状況です。
public class UserProfile
{
public double Height { get; private set; }
public double Weight { get; private set; }
public double Bmi { get; private set; }
public UserProfile(double height, double weight)
{
Height = height;
Weight = weight;
}
public void SetHeight(double height)
{
Height = height;
Bmi = BmiUtility.CalcBmi(Height, Weight);
}
public void SetWeight(double weight)
{
Weight = weight;
Bmi = BmiUtility.CalcBmi(Height, Weight);
}
}
public static class BmiUtility
{
public static double CalcBmi(double height, double weight)
{
// 体重[kg] ÷ (身長[m])^2
return weight * 10000 / (height * height);
}
}
asmdefがなければ、直接参照することができます!
asmdef内クラスからasmdef外クラスのメソッドは呼べない!
しかし、たとえば、
-
UserProfile
がasmdef内 -
BmiUtility
がasmdef外
の場合、参照エラーが発生してしまいます!
困りました!
asmdef外からasmdef 内を参照するようにしてみる
発想を逆転させてしまいましょう!
asmdef外に中継クラスを作って、asmdef外からasmdef内を参照するようにしてみるんです!
図にするとこんな感じです!
まず、以下のように UserProfile
へ Action
を導入しましょう!
using System;
public class UserProfile
{
public double Height { get; private set; }
public double Weight { get; private set; }
public double Bmi { get; private set; }
public Action OnSetHeight();
public Action OnSetWeight();
public UserProfile(double height, double weight)
{
Height = height;
Weight = weight;
}
public void SetHeight(double height)
{
Height = height;
// アクションを発火する
OnSetHeight?.Invoke();
}
public void SetWeight(double weight)
{
Weight = weight;
// アクションを発火する
OnSetWeight?.Invoke();
}
public void SetBmi(double bmi)
{
Bmi = bmi;
}
}
次に、 UserBehaviour
というクラスをasmdef外に作って、 以下のように UserProfile
を参照するようにします。
using UnityEngine;
public class UserBehaviour : MonoBehaviour
{
public UserProfile User { get; set; }
private void Awake()
{
User.OnSetHeight += CalcBmi;
User.OnSetWeight += CalcBmi;
}
private void OnDestroy()
{
User.OnSetHeight -= CalcBmi;
User.OnSetWeight -= CalcBmi;
}
private void CalcBmi()
{
var bmi = BmiUtility.CalcBmi(User.Height, User.Weight);
User.SetBmi(bmi);
}
}
これで、身長・体重が更新されると、 UserBehaviour.CalcBmi()
が実行されるようになります!
asmdef内から間接的にasmdef外のメソッドを呼ぶことができました!
public static class BmiUtility
{
public static double CalcBmi(double height, double weight)
{
// 体重[kg] ÷ (身長[m])^2
return weight * 10000 / (height * height);
}
}
BmiUtility
は、とくに変更する必要はありません。
Action
を利用することで、クラスの参照方向を逆転させることができました!
図にまとめると、こんな感じです!
デメリット1: Actionにメソッドを登録するまでは、メソッドを呼べない
すごく便利ですが、デメリットもあります。
一番のデメリットは、Actionにメソッドが登録されるまでは、アクションを発火させても何も起きないということです。
つまり、
User.OnSetHeight += CalcBmi;
が実行されるよりも前に、
OnSetHeight?.Invoke();
が実行されてしまうと、どうしようもないのです。
たとえば、
public UserProfile(double height, double weight)
のコンストラクターが呼ばれた時点では、Actionにメソッドが登録されていないため、コンストラクター内でActionを発火させても、 BmiUtility.CalcBmi
を実行することはできません。
クラス設計する際に、このあたりの実行順には、かなり気を使う必要があります。
デメリット2: OnDestroy書くの面倒
今回のサンプルコードだと、 UserBehaviour
は MonoBehaviour
を継承しています。
そこで、
private void Awake()
{
User.OnSetHeight += CalcBmi;
}
と書いたなら、
private void OnDestroy()
{
User.OnSetHeight -= CalcBmi;
}
も同時に書いた方が安全です。
しかし、書かなくても動くので、書き忘れやすいというデメリットがあります。
(もっとスマートな方法ないかな)
デメリット3: 技術的負債になる可能性がある
もし、リファクタリングがあり、 BmiUtility
がasmdef内クラスになった場合、 UserProfile
から直接参照できるようになるはずですが、そのことに気づかなければ以下の図のようになります。
この状態でも動作はするので、そのまま放置される可能性があります。
そして長い月日が経つと、中継クラスが追加された経緯を知る人がいなくなり、技術的負債となります。
これが3つ目のデメリットです!
もし、この状態に気づけたら、中継クラスをばっさりと廃止しましょう!
すごくシンプルになりましたね!
さいごに
Actionを使えば、間接的にasmdef内から外のメソッドを呼べるよ!というお話でした!
ご参考になれば幸いです!