読者です 読者をやめる 読者になる 読者になる

nirasan's tech blog

趣味や仕事の覚え書きです。Linux, Perl, PHP, Ruby, Javascript, Android, Cocos2d-x, Unity などに興味があります。

Unity で Observer Pattern

はじめに

基底部の宣言

  • メッセージの送受信者とメッセージ自体のインターフェース及び、メッセージリスナーとメッセージのハブのクラスを宣言する
  • メッセージの送信者と受信者は任意のクラスでインターフェースを実装して用意する
  • メッセージは処理の目的単位でインターフェースを実装したクラスを作成する想定
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace MyObserver {

	/**
	 * メッセージ送信者と受信者を仲介するハブ
	 * シングルトンで動作する
	 */
	public class ObserverManager {

		// シングルトンなインスタンス
		private static ObserverManager mInstance;
		// 初期化
		public static void Init () { mInstance = new ObserverManager(); }
		// シングルトンなインスタンスの取得
		public static ObserverManager Instance { get { return mInstance; } }

		// メッセージ受信者リスト
		List<Listener> listeners = new List<Listener> ();
		// メッセージ受信者の登録
		public void RegisterListener (Listener l) {
			listeners.Add (l);
		}

		// メッセージの送信
		public void SendToListners (IMessage m) {
			foreach (var f in listeners.FindAll(l => l.eventName == m.eventName)) {
				f.observer.GetType().GetMethod(f.methodName).Invoke(f.observer, new object[]{ m });
			}
		}
	}

	// メッセージ受信者情報
	public class Listener {

		// 受信するイベント名
		public string eventName;
		// イベントに呼応して処理を実行するオブジェクト
		public IObserver observer;
		// イベントに呼応して実行するメソッド
		public string methodName;

		public Listener (string e, IObserver o, string m) {
			eventName = e;
			observer = o;
			methodName = m;
		}
	}

	// メッセージ受信オブジェクトのインターフェース
	public interface IObserver {
	}

	// メッセージ送信オブジェクトのインターフェース
	public interface ISubject {
	}

	// メッセージのひな形
	public interface IMessage {
		// メッセージ送信オブジェクト
		ISubject subject { get; set; }
		// 送信するイベント名
		string eventName { get; set; }
	}

}

個別実装部とテスト

  • メッセージ送信者と受信者、メッセージをそれぞれ実装
  • Unity Test Tools を使って動作確認をしたいので、"Unity Test Tools" を Asset Store からインポートする
  • スクリプトを Unity Test Tools の対象にするため "Editor" というフォルダを作成し、その下に配置する
  • テストの実行は [Unity Test Tools] > [Unit Test Runner] でテスト用のビューを開いて、実行ボタンの押下する
using UnityEngine;
using System.Collections;
using NUnit.Framework;
using MyObserver;

namespace MyObserver.Test {

	/**
	 * テスト用のメッセージ受信者実装
	 */
	public class ObserverA : IObserver {
		private int count;
		public int Count { get { return count; } }
		// インスタンス作成時にメッセージ受信者登録を行う
		public ObserverA () {
			count = 0;
			ObserverManager.Instance.RegisterListener (new Listener ("ObserverA", this, "IncrCount"));
		}
		// ObserverA イベントに呼応して実行されるメソッド
		// 実行する毎にカウントをインクリメントする
		public void IncrCount (MessageA m) {
			count++;
			Debug.Log ("message value is " + m.eventValue);
		}
	}

	/**
	 * テスト用のメッセージ送信者実装
	 */
	public class SubjectA : ISubject {
		// ObserverA イベントのメッセージ送信
		public void SendMessage () {
			ObserverManager.Instance.SendToListners (new MessageA (this, "ObserverA", "send message from SubjectA"));
		}
	}

	/**
	 * テスト用のメッセージ実装
	 */
	public class MessageA : IMessage {
		public ISubject subject { get; set; }
		public string eventName { get; set; }
		public string eventValue { get; set; }
		public MessageA (ISubject s, string n, string v) {
			subject = s;
			eventName = n;
			eventValue = v;
		}
	}


	/**
	 * Unity Test Tools を使ったテスト
	 */
	[TestFixture]
	[Category("MyObserver")]
	internal class ObserverTest {

		[Test]
		public void MyObserverTest () {

			ObserverManager.Init ();

			ObserverA o = new ObserverA ();
			SubjectA s = new SubjectA ();
			s.SendMessage ();

			Assert.That (o.Count == 1);

			s.SendMessage ();
			s.SendMessage ();

			Assert.That (o.Count == 3);
		}
	}
}