Observer pattern in Unity

Tulenber 26 June, 2020 ⸱ Intermediate ⸱ 4 min ⸱ 2019.4.0f1 ⸱

Let’s add another important (and what else they are) template to our piggy bank.

An observer is an indispensable template for building lightweight loosely coupled systems, which can significantly simplify life, from an architectural perspective. We were already faced with the use of an observer when we considered a new input system but did not focus on this. Unity has several options for implementing this template, and, to be honest, an unprepared user can cause some confusion, so let’s look at everything from the basics.

Theory

Roughly speaking, an observer divides the world into two types of objects: publishers and subscribers. Subscribers can subscribe (and unsubscribe) to receive event notification from the publisher, and the publisher notifies all signatories of the event. Thus, the opportunity created for interaction between objects without a rigid connection between them. More theory can be found in this article or this chapter of Robert Nystrom’s book.

Unity

The delegate keyword is the primary mechanism that implements the observer. Unity (to be more precise .Net) also provides objects of the event type to ensure greater security.

Delegate

We can say that delegate is a type of variable that points to a function, not a value. If you’ve ever heard of C ++, then it’s a function pointer, a very common mechanic.

The second property worth mentioning is that adding several functions to this pointer will cause their execution to call of this pointer, which makes it an implementation of the template we are considering in this article.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DelegateTest {
    // Declare a pointer to a function of type void with an argument of type string
    public delegate void Print(string val);

    public DelegateTest()
    {
        // Assignment to p of a function having the signature described above
        Print p = PrintValue;
        // Add another subscriber to execute
        p += PrintData;
        // Call all subscribers p
        p("Delegate");
    }

    private void PrintData(string s)
    {
        Console.WriteLine("PrintData " + s);
    }

    public void PrintValue(string s)
    {
        Console.WriteLine("PrintValue " + s);
    }
}

Execution Result:

1
2
PrintValue Delegate
PrintData Delegate

System.Action

You should also become familiar with the System.Action class, which is nothing more than the definition of public delegate void Action (); and allows you to avoid defining a delegate to a function without returning parameters. If necessary, function arguments can be added by using the generics Action <T, …>, which means that you can list the required number of function arguments with the given types.

Event

The event is an add-on for the delegate and, for security reasons, blocks the delegation’s setup from outside the class directly (setup is allowed inside the class with event declaration), providing access only to the functions of adding/removing subscribers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class EventTest {
    // Event announcement
    // Action <string> - declared in the System package as public delegate void Action <in T> (T obj);
    // Assigning an empty delegate delegate {} will avoid additional checking for null when the event is called, since in the absence of listeners, the call will throw a NullReferenceException
    public event Action<string> print = delegate { };

    public DelegateTest()
    {
        // Add Subscriber
        print += PrintValue;
        // Add Subscriber
        print += PrintData;
        // Call all subscribers
        p("Event");
    }

    private void PrintData(string s)
    {
        Console.WriteLine("PrintData " + s);
    }

    public void PrintValue(string s)
    {
        Console.WriteLine("PrintValue " + s);
    }
}

Execution Result:

1
2
PrintValue Event
PrintData Event

Static event

It is easy to see that using the combination public static event Action print = delegate {}; can be an excellent alternative to singleton, when you only need a mechanism to alert subscribers.

Caution

Be sure to delete unnecessary subscriptions, as this may interfere with the garbage collector and cause memory leaks.

Conclusion

The observer is an essential pattern that will reduce the interconnectedness of objects and increase flexibility. As always, it will not be for free, and you will have to sacrifice the evidence of connections and state. However, this flexibility can provide the ability to divide the project into submodules, which is important for large projects. See you next time! =)



Privacy policyCookie policyTerms of service
Tulenber 2020