Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Which one do you prefer? Events vs publisher/subscriber pattern

Discussion in 'Scripting' started by mahdiii, Oct 30, 2018.

  1. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    855
    Please do not say unityEvent!

    Code (CSharp):
    1.  
    2. public interface IListener
    3. {
    4.     void Listen();
    5. }
    6.  
    7. public class Listener1 : IListener
    8. {
    9.     private IPublisher _publisher;
    10.  
    11.     public Listener1(IPublisher publisher)
    12.     {
    13.         _publisher = publisher;
    14.         _publisher.AddListener(this);
    15.     }
    16.     public void Listen()
    17.     {
    18.     }
    19. }
    20.  
    21. public interface IPublisher
    22. {
    23.     void Raise();
    24.     void AddListener(IListener listener);
    25.     void RemoveListener(IListener listener);
    26. }
    27.  
    28. public class Publisher1 : IPublisher
    29. {
    30.     public List<IListener> Listeners;
    31.  
    32.     public Publisher1()
    33.     {
    34.         Listeners=new List<IListener>();
    35.  
    36.     }
    37.     public void AddListener(IListener listener)
    38.     {
    39.         Listeners.Add(listener);
    40.     }
    41.     public void RemoveListener(IListener listener) {
    42.         Listeners.Remove(listener);
    43.     }
    44.  
    45.     public void Raise()
    46.     {
    47.         Listeners.ForEach(listener => listener.Listen());
    48.     }
    49. }
    50.  
    51. //----------------------------------------------------------------------
    52. public class Listener2 : IListener
    53. {
    54.     private IPublisherHandler _publisher;
    55.  
    56.     public Listener2(IPublisherHandler publisher)
    57.     {
    58.         _publisher = publisher;
    59.         _publisher.PublishHandler += Listen;
    60.     }
    61.  
    62.     public void Listen()
    63.     {
    64.     }
    65. }
    66.  
    67. public delegate void PublishHandler();
    68. public class Publisher2 : IPublisherHandler
    69. {
    70.  
    71.     public event PublishHandler PublishHandler;
    72.  
    73.     public void Raise()
    74.     {
    75.         PublishHandler();
    76.     }
    77. }
    78.  
    79. public interface IPublisherHandler
    80. {
    81.     event PublishHandler PublishHandler;
    82.     void Raise();
    83. }
    84.  
    85.  
    86.  
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,195
    Both of those both seem way over engineered, and forces the Listener to have a reference to the event publisher. The interfaces also doesn't do much unless you need to keep references to the objects. I'd just go with:

    Code (csharp):
    1. public class Listener {
    2.     public void Listen() {
    3.         Debug.Log("Received the message!");
    4.     }
    5. }
    6.  
    7. // use:
    8. private event Action SendMessage;
    9.  
    10. private void CreateListener() {
    11.     var listener = new Listener();
    12.     SendMessage += listener.Listen;
    13. }
    C# delegates allows you to be generic over method signatures rather than types, which allows you to ditch a ton of interfaces and allows you to keep a bunch less references to other objects, both of which are large boons.
     
    mvinc006 likes this.
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Publisher & Subscriber, otherwise known as the Observer pattern, is the same thing as events.

    They're just 2 different implementations of the same design concept.

    In one an object subscribes to an event by implementing a function and subscribing to the publisher by passing a reference to that function in the form of an interface.

    In the other an object listens to an event by implementing a function and listening to the event dispatcher by passing a reference to that function in the form of a delegate.

    I would argue the difference is that if you prefer the 'publisher/subscriber' model using interfaces, you probably have a strong Java background. Since Java primarily has used that model for years because it hasn't really had delegates like C#.

    As you can see here, Oracle has a lesson about this very thing, and they call it "event listeners":
    https://docs.oracle.com/javase/tutorial/uiswing/events/index.html
     
    Last edited: Oct 30, 2018
    mahdiii likes this.
  4. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    855
    Thank you. Yes both are the same but different dependency.
    If I use the observer pattern, I will probably have bidirectional dependency. Listeners have a publisher field and add themselves to listen. In the publisher, when raising an event, the publisher invokes Listen function of listeners/subscribers.
    If I use events and eventHandlers in publishers, only listeners/subscribers have to add Listen function to the event but we need to define specific event in the publisher as well.

    If we use events, can we say that we can have more loosely coupled or not?
    I think observer interfaces and events are similar in terms of dependency.

    If we use events, we can subscribe any functions to it until the return type and parameters are the same but if we use the observer pattern, we have to apply a method with specific name like Listen/Subscribe.
     
    Last edited: Oct 30, 2018
  5. mahdiii

    mahdiii

    Joined:
    Oct 30, 2014
    Posts:
    855
    Yes. if it is static, we can define an array of listeners and initialize them in the publisher constructor or inspector (in unity) or use unityEvent and do not need to define a publisher in listeners.


    Code (CSharp):
    1. public class Publisher3 : MonoBehaviour
    2. {
    3.     [SerializeField] public GameObject[] listenerObjs;
    4.     private IListener[] listeners;
    5.     // we can use UnityEvent instead
    6.     public void Start()
    7.     {
    8.         for (var i = 0; i < listenerObjs.Length; i++)
    9.         {
    10.             listeners[i] = listenerObjs[i].GetComponent<IListener>();
    11.         }
    12.     }
    13.  
    14.     public void Raise()
    15.     {
    16.         listeners.ToList().ForEach(listener => listener.Listen());
    17.         // unityEvent.Invoke();
    18.     }
    19. //----------------------------------------------------------------------------------------------------
    20.  
    21. public class Controller{
    22.    public Controller(){
    23.       var listener=new Listener();
    24.       var publisher=new Publisher();
    25.       publisher.Add(listener);
    26.    }
    27. }
    28. public class Publisher{
    29.    public Publisher(){
    30.       _listeners=new List<IListener>();
    31.    }
    32.    public Publisher(List<IListener> listeners){
    33.       _listeners=listeners;
    34.    }
    35.    public void Add(IListener listener){
    36.       _listeners.Add(listener);
    37.    }
    38. }
    39. }
     
    Last edited: Oct 30, 2018
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Technically speaking in the 'delegate/event' model, the publisher still has a reference to the listener in the form of the delegate.

    As you can see here, the System.Delegate object stores a reference to the object its targeting:
    https://docs.microsoft.com/en-us/dotnet/api/system.delegate.target?view=netframework-4.7.2

    You just never really see it because you usually have a reference to the MultiCastDelegate (because its a list of delegates).

    You can even see it here in this decompiled version:
    Code (csharp):
    1.  
    2. // Decompiled with JetBrains decompiler
    3. // Type: System.Delegate
    4. // Assembly: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
    5. // MVID: C3E4200C-CDA7-4037-B5FC-8C34C9135930
    6. // Assembly location: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscorlib.dll
    7.  
    8. using System.Globalization;
    9. using System.Reflection;
    10. using System.Runtime.CompilerServices;
    11. using System.Runtime.InteropServices;
    12. using System.Runtime.Serialization;
    13. using System.Security;
    14. using System.Threading;
    15.  
    16. namespace System
    17. {
    18.   [ClassInterface(ClassInterfaceType.AutoDual)]
    19.   [ComVisible(true)]
    20.   [__DynamicallyInvokable]
    21.   [Serializable]
    22.   public abstract class Delegate : ICloneable, ISerializable
    23.   {
    24.     [SecurityCritical]
    25.     internal object _target; //THIS HERE - this is a reference to the object that the function is part of
    26.     [SecurityCritical]
    27.     internal object _methodBase;
    28.     [SecurityCritical]
    29.     internal IntPtr _methodPtr;
    30.     [SecurityCritical]
    31.     internal IntPtr _methodPtrAux;
    32.  
    33.     [__DynamicallyInvokable]
    34.     public MethodInfo Method
    35.     {
    36.       [__DynamicallyInvokable] get
    37.       {
    38.         return this.GetMethodImpl();
    39.       }
    40.     }
    41.  
    42.     [__DynamicallyInvokable]
    43.     public object Target
    44.     {
    45.       [__DynamicallyInvokable] get
    46.       {
    47.         return this.GetTarget();
    48.       }
    49.     }
    50. //.. snip
    51.  
     
    mahdiii likes this.
  7. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    UnityEvent! UnityEvent! UnityEvent!

    Sorry, had to be done. Will now go back and read the rest of the post.

    ****

    OP seems to be massively over complicating things. The real question appears to be the difference between Publisher.AddListener and Listener.AddPublisher. There is also a third case case where some other class acts as an intermedery. Or even shorter "Which structure should be dependent, the event or the listener?"

    My answer is this thinking is backwards. Ones data structures should govern dependency. Instead dependency should be driven by the underlying dependency in the system. Which means sometimes one way will be the best, and sometimes another.

    Forget publisher vs subscriber for a moment. And think about how your program is going to be structured in general.
     
    mahdiii and Baste like this.
  8. MithrilMan

    MithrilMan

    Joined:
    Apr 24, 2014
    Posts:
    2
    I stopped here, no No NO.

    If you want to see the differences, there are lot of articles around, or pattern explained, one of them is this
    https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c

    I'm a C# programmer since its inception and I work with Java too, patterns here, patterns there, every turn you take a pattern pops out. Some are similar, some are the same just with different names but no, these two are not the same.

    I just want to state the most important aspect of pub/sub (let's call it event bus, or anything alike): you decouple publisher from subscriber.
    THIS is the most important thing, because now you can publish any event and anyone, even without knowing that ComponentX is the publisher, can consume it.

    Well that article explains nicely all this and the implications are huge.

    P.S.
    I'm talking about design/architecture, not performance
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    So the difference is that there is a helper object between the 2... like an event object.

    Yeah... in the end it's the same business. Sorry if I wasn't pedantically specific.

    Enough to warrant no, NO NO. Calm down.

    As the conclussion of the very article you post says:
    We're talking about an apples and oranges are fruit type scenario.

    Note if you read my posts I do get into the slight nuances of things. And that the statement you quote is more of a generalized statement proposed before the nuances.

    Your article really only suggests into the nuances, but even concludes with the same general, "but in the end, they're effectively similar".
     
    Last edited: Apr 5, 2019
  10. MoribitoMT

    MoribitoMT

    Joined:
    Jun 1, 2013
    Posts:
    301
    It is matter of choice of same purpose

    Observer patten
    - Much faster ( 2,3 times some says, however this is not even noticable difference )
    - Harder to implement, harder to maintain, can cause ripple effect in all system if it needs to changed in future

    Events
    - Slower
    - Easier to implement, easier to maintain, less code, less clutter, but must be careful about forgetting unsubscribing.