デザインパターンの一つであるオブザーバーパターンについてUnity公式のeBookがでており、気になっていた点を中心に調べてみた。
・公式
https://github.com/Unity-Technologies/game-programming-patterns-demo/
・非公式和訳
https://zenn.dev/twugo/books/21cb3a6515e7b8/viewer/e833a7
※下記サンプルはchatGPTで例示してもらった中でも、分かりやすかったものをピックアップしたもの。
サンプル
using System;
public class EventExample
{
public event Action<int> NumberChanged;
private int _number;
public int Number
{
get => _number;
set
{
if (_number != value)
{
_number = value;
OnNumberChanged();
}
}
}
private void OnNumberChanged()
{
NumberChanged?.Invoke(_number);
}
}
public class Subscriber1
{
public void Subscribe(EventExample example)
{
example.NumberChanged += OnNumberChanged;
}
public void Unsubscribe(EventExample example)
{
example.NumberChanged -= OnNumberChanged;
}
private void OnNumberChanged(int number)
{
Console.WriteLine($"Subscriber1: Number changed to {number}");
}
}
public class Subscriber2
{
public void Subscribe(EventExample example)
{
example.NumberChanged += OnNumberChanged;
}
public void Unsubscribe(EventExample example)
{
example.NumberChanged -= OnNumberChanged;
}
private void OnNumberChanged(int number)
{
Console.WriteLine($"Subscriber2: Number changed to {number}");
}
}
class Program
{
static void Main(string[] args)
{
var example = new EventExample();
var subscriber1 = new Subscriber1();
var subscriber2 = new Subscriber2();
subscriber1.Subscribe(example);
subscriber2.Subscribe(example);
example.Number = 42;
subscriber1.Unsubscribe(example);
example.Number = 99;
}
}
アウトプット
Subscriber1: Number changed to 42
Subscriber2: Number changed to 42
Subscriber2: Number changed to 99
メリット
1.あるオブジェクトが他のオブジェクトを直接参照することなく、他のオブジェクトに通知できる
EventExampleクラスはSubscriber1クラスを持たないが、EventExampleのNumberプロパティに更新があると、Subscriber1のメソッドが呼ばれるようになっている。
2.「一対多」の関係を簡単に作れて、疎結合を保ちやすい
仮にSubscriber3,Subscriber4と増やしてもEventExampleクラスを編集する必要がない。
event修飾子について
public event System.Action<int> NumberChanged;
このeventはprivateやinternalなどと同じアクセス修飾子としての機能を持つみたい。例えば
class Program
{
static void Main(string[] args)
{
var example = new EventExample();
example.NumberChanged();
}
}
とすると、eventによりクラス外から参照できなくなるため、NumberChangedデリゲートを実行できない。もちろん、
class Program
{
static void Main(string[] args)
{
example.NumberChanged += OnNumberChanged;
}
}
は通るので、そこがpublic eventがprivateとで違う点。
Invoke()
NumberChanged?.Invoke(_number);
これはInvokeを使っているが、
if(NumberChanged != null)
NumberChanged(_number);
と、使わなくても同じ結果になる。nullチェックが簡単にできるのが良さそう。
delegate
public event Action<int> NumberChanged;
の部分をdelegateで置き換できる。その場合、
public delegate void NumberChangedEventHandler(int number);
public class EventExample
{
private NumberChangedEventHandler _numberChangedHandlers;
public event NumberChangedEventHandler NumberChanged
{
add => _numberChangedHandlers += value;
remove => _numberChangedHandlers -= value;
}
...
}
と、明示的にデリゲートを宣言する必要がある。
C#言語のバージョンが上がるにつれて、System.ActionやFuncなどのデリゲート型が登場したため、delegateを使用する必要性が少なくなっている。
EventHandler
System.ActionとEventHandlerは、どちらもイベントハンドラーとして使用されることがあるが、使用する場面や目的が異なる。
例えば、Windows FormsやWPFなどのUIアプリケーションにおいて、Buttonクラスなどのコントロールには、Clickイベントがある。このClickイベントのハンドラとしては、EventHandlerを使用することが一般的。