Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Question Observer Pattern - who should be responsible for finding their other half?

Discussion in 'Scripting' started by c-Row, Apr 12, 2024.

  1. c-Row

    c-Row

    Joined:
    Nov 10, 2009
    Posts:
    853
    I have a rather large UI window in my game whose code has become longer and longer the more information is displayed in its various components. Since updating its contents depends on the entity currently selected I thought it would be a good opportunity to refactor my monolithic class into separate single-responsibility scripts per component using the observer pattern.

    So far things seem to work as expected. However, I was wondering what's the best practice for the Subject and the Observer to find each other in the first place?

    Since the subject class is usually placed at the root of the prefab, let it search through its children and retrieve all IObservers on Awake, then add them to the list of observers? The advantages are that only one game object is responsible to find any potential observer. However, with the observers not knowing their subject they cannot unregister when OnDisable would be called on them (unless the Subject tells them during the registration process).

    On the other hand, the Observers could search for any parent game object whose components implement the Subject interface, putting all responsibility on the observer itself. Since the observer has found the subject on its own it can easily keep a reference to it for those OnEnable/OnDisable methods. However, if the prefab might be missing a component with the ISubject interface the observer might continue its search all the way up to the scene's root (which I think should not be an issue since there aren't that many observers... yet) or find another subject it should not register to (which would be much worse).

    I know I could expose the subject variable in the Observer with the help of Odin for example but that in turn would place the responsibility of wiring things up onto the poor developer (i.e. me) every time.

    So, which of these three do you feel is the best approach?
     
  2. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    730
    ISubject should have a "Destroyed" event that fires when the Subject is destroyed. You can create an abstract BaseSubject MonoBehaviour that fires that event on OnDestroy so you won't have to repeat the same code for all MBs ISubjects. The Observer then listens to it and acts accordingly (remove it from the List and also unregister from all ISubject events, including the Destroyed event). You could also make a separate IDestroyable interface and compose it with ISubject when needed.

    And why not placing the Observer at the top of the hierarchy and makes it find its ISubject children? this will resolve the issue of "Observer missing its ISubjects parent", after all it's the job of the Observer to find its Subjects, not the opposite.
     
    Last edited: Apr 12, 2024
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,560
    The observer pattern:


    The observer pattern exists to decouple the subject from the observer so the observer can react to conditional changes (events) of the subject.

    In UI terms this could be your UI logic class and the UI elements. Your logic observes your elements. An example of an element could be a 'Button'. Now... when writing the code of your Button, said Button does not know where it exists or the logic associated with it. It doesn't know if its click event will result in sending a message to the internet, transitioning to a new window, or playing a sound. Hell... it may very well do nothing.

    All it knows is that it consists of some visuals, and processes input events, and sends off a "Click" event if those input events occur in the correct way in its visual region. And when that occurs it goes to its list of observers and tells it "hey, I was clicked!"

    The code doesn't know who its observers are. It doesn't know if they're children or parents. It doesn't know if its a pure logic class elsewhere that isn't even a UI element (parent and children implying ui element relationship). It just knows that said observers are in its list and have some way for us to call a function on those observers in the shape as defined by our subject's event type.

    Note... C# events is effectively the observer pattern! Same with UnityEvent!

    ...

    So with all that said.

    The subject shouldn't be finding its observers and registering them.

    The observers know about its subjects. That's what makes them observers.

    It's why the 'subject' has a 'registerObserver' and 'unregisterObserver' method on them. Those get called from outside of Subject. There's no reason that a Button's code should know all of the potential observers it may be associated with and make sure it knows how to register them with itself. Let the observer do that, or even a 3rd party (there are scenarios where you have completely decouple your model/view/logic so much that you have 3rd party actually manage linking your logic to your view by subscribing your observers to the subjects for you this way the logic doesn't need to know about its view at all).

    Wait... why? Why is your subject usually at the root of the prefab?

    In UI common subjects are things like... Buttons, InputFields, etc. UI elements that the user interacts with and the logic of your UI wants to know when that happens and what the interaction was. I would think a prefab of a ui visual field (a window for instance) would have the 'Canvas' and logic close to the root, and the subjects/elements inside of it (speaking in Unity UI terms that is).

    This isn't to say your window's logic can't itself also be a subject. But usually that'd be for an outside observer yet above that (a controller that shows a sequence of windows for instance).
     
    Nad_B and tsukimi like this.
  4. c-Row

    c-Row

    Joined:
    Nov 10, 2009
    Posts:
    853
    Because I don't want to react to the UI, I want the UI to react to a change of information. If the selected entity changes - of which the window in turn is notified from outside - the subject should let all its child UI components now that they should update their contents. Right now all those components are exposed in the Inspector but that comes with an endless number of null checks to make sure all those component references are actually set.
     
  5. c-Row

    c-Row

    Joined:
    Nov 10, 2009
    Posts:
    853
    That does not take care of the Observer getting destroyed or disabled, though.
     
    Last edited: Apr 12, 2024
  6. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    730
    What's the problem here? since ISubjects do not know/care about the Observer...
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,560
    Sounds to me like you're looking at this upside down.

    What are your subjects? And what are your observers? Define these things...

    Because as said, in a UI scenario your UI elements generally (and mind you, I say generally because there are scenarios where you might flip this around, it's why I'm asking you what your scenario is). But generally UI elements are the subjects.

    Textfields/buttons/uielements don't generally update themselves because a model has changed its state (model = data/information). Generally there is some controller in between that knows the model has changed and updates the view (your ui elements) with the new information.
     
  8. c-Row

    c-Row

    Joined:
    Nov 10, 2009
    Posts:
    853
    A destroyed observer would leave a null reference in the subject's list of observers.
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,560
    And this is why the observer should unregister itself from the subject (via the unregisterObserver(IObserver observer) function) when it gets destroyed.

    This is 1 of the many reasons the subject doesn't manage the registering of observers.
     
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,560
    Let's say I'm a newspaper company (subject), and I distribute newspapers to my subscribers (observers).

    Which sounds more logical?

    I as a newspaper go out and decide who are my subscribers and automatically sign them up as subscribers to my newspaper? (that's the subject registering observers with itself)

    OR

    Should I as the newspaper let my subscribers come to me and request to be subscribed to my newspaper? (the observer registering with the subject)
     
  11. c-Row

    c-Row

    Joined:
    Nov 10, 2009
    Posts:
    853
    That might help indeed. :D So this is my unit information window.

    upload_2024-4-12_21-9-57.png

    On its root there is a UnitInfoWindow script component which currently holds references to all the various elements on it - the unit's name, its sprite, the various attributes and so on.

    Right now, whenever the UnitInfoWindow is notified that a new unit has been selected it goes through all those references and applies all the necessary changes to them. It replaces the name, the sprite, updates the attributes - all the stuff you would want from an information window.

    Since that script is getting larger with every new element added to it my idea was to let the elements handle their updating themselves and just listen to the UnitInfoWindow notifying them of the change of selected unit.

    I could of course have the UI elements listen to the global event instead but with the above approach I could expose the selected unit's reference and replace that manually during runtime for easier testing.

    So, unless I am mistaken the window (or its underlying script component) would be the subject, and all the UI elements displaying the selected unit's information would be the observers.
     
  12. c-Row

    c-Row

    Joined:
    Nov 10, 2009
    Posts:
    853
    Yes, and I don't disagree. I am just looking for a good way to let the readers know where the newspaper is to actually buy a subscription. ;)
     
  13. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,560
    So I had a feeling this is roughly what you were going to say, and I was in the midst of formulating a post about it. Now that you've said it I'll just respond to this directly.

    I don't think you want to be using the observer pattern.

    So you still have UI Elements, things like your ui Image, TextFields, Buttons, etc.

    What you're wanting to do is you used to have a controller that updated all of these and you want to break your controller up into smaller more manageable pieces. Basically your controller becomes what we would call a "composition" of smaller objects.

    Unity's whole component system is devised around this composition model. That's what components are.

    But sometimes we need to communicate between them. For example you may composite a Collider and a script that does a thing when that Collider collides with something. What we do in Unity for that is we react to the OnCollision or OnTriggerEnter "messages".

    I think you want a "messaging" system.

    Unity has a very simplistic one built in:
    https://docs.unity3d.com/ScriptReference/Component.BroadcastMessage.html
    Your parent controller can "broadcast" a "message" to all components on itself and children of itself (which is the direction you're talking about).

    Unfortunately this approach relies on string methods which is a bit loosey goosey. If you don't like that and prefer an interface approach (your use of the observer pattern suggests to me you do).

    Well there is another way...

    Here is my prebaked version of it:
    https://github.com/lordofduct/space...uppy.core/Runtime/src/Utils/Messaging.cs#L247

    It's loosely based on Unity's UI ExecuteEvents:
    https://docs.unity3d.com/2018.4/Documentation/ScriptReference/EventSystems.ExecuteEvents.html

    But unity's only has execute on the same gameobject, or a bubbling execute that goes up the hierarchy. I wanted something that broadcasted down the hierarchy like BroadcastMessage does previously mentioned.

    Of course that class of mine I linked has a LOT going on in it cause I have TONS of bells and whistles.

    A simpler version is effectively:

    Code (csharp):
    1. public static void Broadcast<T>(this GameObject go, System.Action<T> functor) where T : class
    2. {
    3.     foreach (var receiver in go.GetComponentsInChildren<T>())
    4.     {
    5.         functor(receiver);
    6.     }
    7. }
    8.  
    9.  
    10. public interface IOnDataChangedHandler
    11. {
    12.     void OnDataChanged(object sender);
    13. }
    14.  
    15. this.gameObject.Broadcast<IOnDataChangedHandler>(o => o.OnDataChanged(this));
    Now the destruction of observers and subjects isn't a concern. The receiver collection is naturally results from the hierarchy at moment of dispatch.

    Of course in this simple version there is some garbage involved (the creation of the collection in GetComponentsInChildren, the functor, etc). This is why in my Messaging.cs class I linked from my github is slightly different... I have optimizations to avoid GC and other aspects.

    But yeah, this "messaging system" approach works better for your composition approach because really it's not subjects and observers independent of one another. It's really that you broke a large class into smaller classes... the composition of which makes the original larger class.

    This is how I do a LOT of stuff in my games.

    You'll notice back in my Messaging.cs class I linked I have various versions message types:
    Broadcast - send to components on self and children (name borrowed from unity's own BroadcastMessage)
    Signal - send to components on self (think like unity's built in SendMessage)
    SignalUpwards - send to components on self and parents
    BroadcastEntity - I have a concept of entities in my games where there is a 'root' to something (think prefab root), BroadcastEntity reaches up to find the entity root, then broadcasts down to all of the entity. Think how a hand of a person might reach up to the root of the person and broadcast to all limbs that it did something
    SignalEntity - reaches up to the entity and signals only those components on the root
     
    Last edited: Apr 12, 2024
    c-Row likes this.
  14. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    730
    As @lordofduct said, the Subjects should not be made aware of the Observer, they should not reference it, nor care if it exists or not. If both the Observer and the Subject reference each other, you have a circular dependency, and it's wrong.

    You literally called it an "Observer", which means its job is to observe other objects, not the other way around.
     
  15. VRARDAJ

    VRARDAJ

    Joined:
    Jul 25, 2017
    Posts:
    54
    RE: compilation, it's fine to have the subject and observer reference each other. It's only circular if they each use the other's reference to locate each other at runtime, but clearly that's not what OP intended. The intent was for subject to inject itself into the observers so that the observers can then subscribe without searching. I agree that's not necessarily a textbook observer pattern, but textbook observer patterns are seldom useful in Unity IMO anyway.

    When the parties are known to be up/down the hierarchy, Unity's API enables easy connection in either/both direction(s) so strict observer-ness and difference from messaging is a bit semantic. When the parties are across the hierarchy, it's best to decouple both subject-side and observer-side one way or another, and textbook observer only does one and not the other, so it's insufficient alone.

    That said, all the non-textbook variations are so similar to the observer pattern that I don't personally mind when OP and others call it an observer even though technically it's a variation. I thought the intent was pretty clear.

    There's always a question of dependency versus subscription in observers and other similar patterns. The main defining characteristic of observers is one-to-many IMO. If you have that, you mostly have an observer. Observers need to register. That's required, IMO. But do they need to seek a direct dependency to the subject in order to still be called observers? I don't think so. I decouple them with mediators all the time and still call them observers. If I can do that, I think it's fair for OP to call this an observer when the subject does the opposite, seeking direct dependencies when the textbook says not to.
     
    c-Row likes this.