Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Observer pattern Hell

Discussion in 'Scripting' started by corax, Dec 28, 2013.

  1. corax

    corax

    Joined:
    Jun 4, 2012
    Posts:
    34
    Hi guys,

    In the last couple of days I have been struggling with a pretty simple implementation of observer pattern. I tought that was an easy way to hook an object to another and notify it without defining specific delegates in the subject class.

    The implementation is very simple, we have two pretty standard interfaces:

    Code (csharp):
    1. public interface ISubject
    2. {
    3.  
    4.      void Register(IObserver observer);
    5.  
    6.      void Unregister(IObserver observer);
    7.  
    8.      void Notify();
    9.  
    10. }
    11.  
    12. public interface IObserver
    13. {
    14.     void getUpdate();
    15. }

    and the actual classes are even simpler:


    Code (csharp):
    1. public class Menu : GameMenubase, ISubject
    2.  {
    3.  
    4. private List<IObserver> observers = new List<IObserver>();
    5.    
    6.         public void OnMenuOpen()
    7.         {
    8.            
    9.             Notify();
    10.            
    11.         }
    12.        
    13.         public void Register(IObserver observer)
    14.         {
    15.             if(!observers.Contains(observer))
    16.                 observers.Add(observer);
    17.         }
    18.        
    19.         public void Unregister(IObserver observer)
    20.         {
    21.             if(observers.Contains(observer))
    22.                 observers.Remove(observer);
    23.         }
    24.  
    25.        
    26.         public void Notify()
    27.         {
    28.             foreach(IObserver observer in observers)
    29.                 observer.getUpdate();
    30.         }
    31. }
    32.  
    33. public  class Listener : MonoBehaviour,IObserver
    34. {  
    35.    
    36.     private ISubject subject
    37.     {
    38.         get
    39.         {
    40.             return (ISubject) GameManager.Instance.Menu;
    41.         }
    42.     }
    43.    
    44.     public virtual void Start()
    45.     {
    46.         RegisterThis ();
    47.     }
    48.  
    49.    
    50.     public void getUpdate()
    51.     {
    52.         // do stuff
    53.     }
    54.  
    55.     void RegisterThis ()
    56.     {
    57.         if (subject == null)
    58.             return;
    59.         subject.Register (this);
    60.     }
    61. }
    The Subject is an hidden menu with a reference in the GameManager and is disabled at game start. When I press a button the Menu is dispayed and its children (the listener : iObserver) are enabled too. The problem is that when the button call the method:

    Code (csharp):
    1. EnableMenu()
    2. {
    3. NguiTools(GameManager.Instance.Menu,true);
    4. GameManager.Instance.Menu.OnMenuOpen();
    5. }
    I get no observer in the list! I guess that the problem here is that NGUI enable the menu but the observer are still not active when we call OnMenuOpen (there are a lot of them nested in a quite deep hierachy).

    I tried a couple of solution but none of them is really good:

    1) Delay the OnMenuOpen => quite stupid because force a unecessary slowness in the code even if you have one observer
    2) Turn the logic upside down: the menu search for the observer in the hierachy and if found any register it => the code cycle trough a lot of objects even if they are not relevant and moreove Unity is not able to search on disabled gameobject (GetComponentinchildren doesnt work)
    3) Switch to a hybrid Mediator: A singleton with lazy load. When someone needs to change its state will call the Mediator and ask for a specific signature. At same time if some specific event occur in the system the Mediator will now (if relevant) and notify the specific objects. => I love this solution but it is huge to implement.

    I'm pretty much out of option. The last solution I can imagine is some sort of proxy that will not call OnMenuOpen() untill the full hierachy is enabled. But even this solution smells a lot to me. Any advice?
     
    Last edited: Dec 28, 2013
  2. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    Unity won't call the observer's Start method until the currently-executing entry point (whatever called EnableMenu) returns. You could use OnEnable instead of Start, which should get called straight away.

    If you generally want to be tolerant of an observer connecting late, and want it to still get an immediate notification, then you could just make Register call Notify directly on any new observer - or make the observer itself just know that it needs to immediately notify itself because it has missed anything that has happened already.

    Also, though not the cause of the problem, you might want to use events instead - your Register/Unregister/getUpdate structure is mostly just a manual implementation of C#'s events, and it would be clearer if you just used them.
     
  3. corax

    corax

    Joined:
    Jun 4, 2012
    Posts:
    34
    Man thank you for the advice. I will start right away with the events implementation and then use OnEnable. I will post my findings after a couple of tests.
     
  4. corax

    corax

    Joined:
    Jun 4, 2012
    Posts:
    34
    After a couple of try I have this now in the Observer class:

    Code (csharp):
    1. private ISubject subject
    2.     {
    3.         get
    4.         {
    5.             return (ISubject) MenuManager.Instance.activeMenu;
    6.         }
    7.     }
    8.    
    9.     public virtual void OnEnable()
    10.     {
    11.         subject.OnPublishing += OnPublishing;
    12.     }
    13.  
    14.     public virtual void Disable()
    15.     {
    16.         subject.OnPublishing -= OnPublishing;
    17.     }
    The pros are:

    • clean code
    • small classes
    • very easy to extend

    The cons are:

    • The problem is still there.