LoginSignup
1
1

More than 1 year has passed since last update.

オブザーバーパターン

Posted at

デザインパターンの一つであるオブザーバーパターンについてUnity公式のeBookがでており、気になっていた点を中心に調べてみた。

・公式
https://github.com/Unity-Technologies/game-programming-patterns-demo/
・非公式和訳
https://zenn.dev/twugo/books/21cb3a6515e7b8/viewer/e833a7

※下記サンプルはchatGPTで例示してもらった中でも、分かりやすかったものをピックアップしたもの。

サンプル

example.cs
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などと同じアクセス修飾子としての機能を持つみたい。例えば

NG
class Program
{
    static void Main(string[] args)
    {
        var example = new EventExample();
        example.NumberChanged();
    }
}

とすると、eventによりクラス外から参照できなくなるため、NumberChangedデリゲートを実行できない。もちろん、

OK
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を使用することが一般的。

1
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
1
1