はじめに
様々な言語で「デザインパターン」の本が世の中にありますが、筆者個人の経験では
いまいちピンとこない例
いまいちピンとこないコード
で説明されてることが多く、
結局これっていつ使うの?
という疑問に答えるには仕事仲間等との議論をしないと
辿り着けないことが多々ありました。
そこで特に「ゲーム開発ではどう使うか?」にフォーカスを当てて、実践的な例を交えて
デザインパターンの説明の需要があると思い記事を作りました。
デザインパターンを学ぶ理由
デザインパターンを学ぶ理由としては
- 車輪の再発明の防止
- 長文で読みにくいコード(可読性の低いコード)を減らす
- コードを疎結合にして変更に強くなる(変更時のコスト・変更箇所を減らす)
- モジュールとして使いまわせるように、コードの再利用性を高める
といった効果を期待できます。
対象読者
Unity 全くの初心者(インストールしただけで触ったことがないような方)はお断りです。
最低限以下のことは理解・経験を積んでおくことが必須になります。
- MonoBehaviour 継承クラスでコードを書いたことがある
- C# のピュアクラスを用いた自作クラスを作ったことがある
- クラスの継承という概念は知っている
そのため、脱・初心者
中級者へのステップアップ
として デザインパターンを学ぶ
のが良いと思います。
デザパタ記事リンク
生成系
構造系
様態・ふるまい系
- Chain of Responsibility パターン
- Command パターン
- Interpreter パターン
- Iterator パターン
- Mediator パターン
- Memento パターン
- Observer パターン
- State パターン
- Strategy パターン
- TemplateMethod パターン
- Visitor パターン
Decorator パターンについて
Decorator パターンとは、ある関数を引数にもらい、その関数の機能を拡張させ、同名関数として返すような機能の事です。
偽コードで解説すると以下のような形です。
function SomeDecorator( function funcA )
{
return ()=>{
PreMethod();
funcA();
PostMethod();
};
}
何かの関数に対して前処理や後処理を追加した状態で関数を返す機能です。
C# におけるDecoratorパターン
C#でDecorator パターンは残念ながら実装しづらいです。
一応関数の override によって実現は可能なのですが、他の言語に比べて使用感がイマイチであることは否めません。
やりたいこととしては 前処理や後処理を追加
したいので、以下のような実装になります。
public abstract class BaseDecorator
{
private BaseDecorator m_internalDecorator = null;
private Action m_mainFunc = null;
public BaseDecorator(Action mainFunc )
{
m_mainFunc = mainFunc;
}
public BaseDecorator(BaseDecorator deco)
{
m_internalDecorator = deco;
}
public void Invoke()
{
PreMethod();
m_internalDecorator?.Invoke();
m_mainFunc?.Invoke();
PostMethod();
}
protected abstract void PreMethod();
protected abstract void PostMethod();
}
public class LogDecorator : BaseDecorator
{
private string m_name = "";
public LogDecorator(BaseDecorator deco, string name): base(deco)
{
m_name = name;
}
public LogDecorator(Action act, string name): base(act)
{
m_name = name;
}
protected override void PreMethod()
{
Debug.Log("[Pre]:"+m_name);
}
protected override void PostMethod()
{
Debug.Log("[Post]:"+m_name);
}
}
これを用いて以下のようにMonoBehaviour 継承クラスで実行してみましょう。
public class DecoratorTest : MonoBehaviour
{
void Start()
{
Action mainProcess = () => { Debug.LogWarning("Main処理だよ"); };
LogDecorator log1 = new LogDecorator(mainProcess, "Log1");
LogDecorator log2 = new LogDecorator(log1, "Log2");
Debug.Log("==== DO LOG1 ====");
log1.Invoke();
Debug.Log("==== DO LOG2 ====");
log2.Invoke();
}
}
結果はこのように表示され、Decoratorによって処理が積み重なっているのがわかります。
(参考)Python によるDecorator
他の言語の例として Python はどうでしょうか。
PythonではDecoratorを用意しておけば、関数やクラスに対して @Hoge
という冒頭に@
をつけるだけでデコレータを適用できます。
def logger(tag):
def _logger(function):
def wrapper(*args, **kwargs):
print("[Pre]:"+tag)
function(*args, **kwargs)
print("[Post]:"+tag)
return wrapper
return _logger
def sample_api(message: str):
print("MainMessage:"+message)
@logger("LOG1")
def sample_api02(message: str):
print("MainMessage:"+message)
@logger("LOG1")
@logger("LOG2")
def sample_api03(message: str):
print("MainMessage:"+message)
def main():
sample_api("This is Sample 01")
print()
sample_api02("This is Sample 02")
print()
sample_api03("This is Sample 03")
if __name__ == "__main__":
main()
先ほどのC#のようにlogger という名前のデコレータを用意しました。
sample_api ではデコレータなし、02、03とデコレータの数を増やしたサンプルです。
このように既存の関数定義の直前に @~~
を追加するだけでデコレータを適用できるため、C#よりはもっとカジュアルに利用可能です。また、デコレータ自体の再利用性も高く、基底クラスに縛られたり、設計に縛られるようなこともありません。
ゲームにおける Decorator
先ほどの例でも分かる通り C# ではデコレータは少し設計的な問題があるためカジュアルには使われないことが多いでしょう。
そのためゲーム開発でもデコレータを利用した設計はあまりなく、現場では即戦力になることは珍しいと思います。
ちなみに、Pythonのようにカジュアルにデコレータが使える環境だと、処理フローの可視化ログの適応などはとても簡単に実装できますね。
まとめ
言語使用上、C#でdecoratorパターンを実装しようとするとクラス設計に縛りを設けることになります。
そのため関数のoverride等で実現することになるため、virtualキーワードをつけた仮想関数として公開されない限りはデコレータのよる機能拡張はできません。
ただし、他言語ではカジュアルにできるため、知識としては覚えておくと良いでしょう。