UnirxでのObserverパターンを身近な例で例えてみる

0 始めに

この記事ではUnityの外部ライブラリあるUnirxでのObserverパターンを身近な例に例えてみようといった記事です。 今回はピザの宅配サービスに例えてみたいと思います 実際にコードと図を交えて説明いたします。また当ブログ内にUnirxの記事が他にもあるのでそちらも 参考にしていただけると嬉しいです。

1 現実世界の流れ

実際の現実世界の流れを確認します。

1

まずピザ屋が回転されてWeb上などで登録サービスを公開します。

2

ユーザーはそのピザ屋を調べ、Web上で自分のデータ(住所や氏名)を登録します。

3

その後ピザ屋は登録された先に一定の決められたタイミングでピザを送ります。 届けられたピザをユーザーが食べたり、保存できたりします。

4

最後に一定期間たったら店はユーザーに届けるのを中止出来たり、逆にユーザーが 届けられるのを中止したりすることが出来ます。

2 Unirxに置き換えてみる

これをUnirxに置き換えて考えてみるとSubjectがピザ屋、Observerがユーザー、IobservableがWebページに 当たります。これを実際のコードに置き換えてみます。 ここで大切な考え方はSubjectは値を発行(ここではピザを配達する)ことしかせず、実際の処理内容は Observer側に書かれているということです。

3 実際のコード

まずはピザの種類を生成します

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//送るピザの種類
 public enum PizzaType
{
    SeafoodPizza,
    MeatPizza,
    VegetablePizza,
    SweetPizza
}

次にSubject側のクラスを生成します

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UniRx;

//ピザショップ(Subject)のクラス
public class PizzaShop : MonoBehaviour
{
    //Subjectを作ってIobservableだけ公開する・・・①
    private Subject<PizzaType> pizzaSubject = new Subject<PizzaType>();
    public IObservable<PizzaType> OnOrderObservable => pizzaSubject;

    int time;
    int counter;


    void Start()
    {
        time = 0;
        counter = 1;
        StartCoroutine(DayCoroutine());
    }

    IEnumerator DayCoroutine()
    {
        while (true)
        {
            //10回配達したら倒産する・・・④
            if (counter > 10)
            {
                pizzaSubject.OnCompleted();
                pizzaSubject?.Dispose();
                yield break;
            }

            //特定の時間になったらランダムにピザを送りつける・・・③
            if(time == 19)
            {
                int random = UnityEngine.Random.Range(0, 4);
                switch (random)
                {
                    case 0:
                        pizzaSubject.OnNext(PizzaType.MeatPizza);
                        break;
                    case 1:
                        pizzaSubject.OnNext(PizzaType.SeafoodPizza);
                        break;
                    case 2:
                        pizzaSubject.OnNext(PizzaType.VegetablePizza);
                        break;
                    case 3:
                        pizzaSubject.OnNext(PizzaType.SweetPizza);
                        break;
                }
                counter++;
            }

            //0.01秒待って1時間を勧める(0~23時を繰り返す)
            yield return new WaitForSeconds(0.01f);
            if (time < 24) { time++; }
            else { time = 0; }
        }
    }
}

最後に2つObserverを生成します

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UniRx;

public class UserA : MonoBehaviour,IObserver<PizzaType>
{
    //ピザショップをへの参照
    private PizzaShop pizzaShop;
    //購読終了用
    private IDisposable disposable;
    //ピザが届けられた回数
    private int counter;
    void Start()
    {
        //参照を持つようにして配達回数を0にする
        pizzaShop = GameObject.Find("PizzaShop").GetComponent<PizzaShop>();
        counter = 0;
        //公開されているIobservableを使って自身を登録する(AddTo()で自身が破壊されたときにDispose()されるようにする)・・・②
        disposable = pizzaShop.OnOrderObservable.Subscribe(this)
                                                .AddTo(this);
    }

    void Update()
    {
        //100回配達されたら自身を破壊する
        if(counter > 100)
        {
            Destroy(this.gameObject);
        }
    }
    private void OnDestroy()
    {
        disposable?.Dispose();
    }

    public void OnCompleted()
    {
        Debug.Log("Aが契約を解除しました");
    }

    public void OnError(Exception error)
    {
        Debug.Log("エラー");
    }

    //ピザが届けられた時の処理・・・④
    public void OnNext(PizzaType value)
    {
        Debug.Log(value.ToString() + "を食べます");
        counter++;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UniRx;

public class UserB : MonoBehaviour, IObserver<PizzaType>
{
    private PizzaShop pizzaShop;
    private IDisposable disposable;
    private int counter;

    void Start()
    {
        pizzaShop = GameObject.Find("PizzaShop").GetComponent<PizzaShop>();
        counter = 1;

        disposable = pizzaShop.OnOrderObservable.Subscribe(this)
                                                .AddTo(this);
    }

    void Update()
    {
        //回数がAと異なる
        if (counter > 3)
        {
            Destroy(this.gameObject);
        }
    }
    private void OnDestroy()
    {
        disposable?.Dispose();
    }

    public void OnCompleted()
    {
        Debug.Log("Bが契約を解除しました");
    }

    public void OnError(Exception error)
    {
        Debug.Log("エラー");
    }

    //ここがAと異なるので注意する
    public void OnNext(PizzaType value)
    {
        Debug.Log(value.ToString() + "を冷蔵庫に入れます");
        counter++;
    }
}

こんな感じになれば成功です!

4 最後に

今回はUnirxを身近な例で置き換えて考えてみました。 何か間違ったことなどを書いていればご指摘いただけたら光栄です。