Vala reactive programming: part 3

igordsm

Igor Montagner

Posted on January 2, 2022

Vala reactive programming: part 3

The last article showed how we could create a Window with two different forms with clear separation between application logic and GUI code. Most of our work consisted of binding properties and using signals to send information out of components. Although this seems to work well with our little example, our application didn't have any global state. One could argue that global state is a code smell (and a lot of people do), but the fact is that lot's of applications use it to some degree.

One of the problems of global state is that it increases coupling. Essentially, coupling represents the amount of interdependency between two or more modules. Frequently, in situations with large coupling a change in one module snowballs into lots changes in the related modules as well. This is why coupling is seen as bad: there's the possibility of a butterfly effect in which a small change in one module produces a large change overall.

We could simply add properties to our Application class pass them along to our components. This could be done either through bindings, via constructor parameters or by referencing the parent component using (Application) this.parent.property_name. All these options are fine, in the sense that they work but might cause coupling problems.

The first two may require passing a reference to or binding Application to basically every class you write. Not only this is annoying but it also makes testing harder. We might need to mock Application for basically every class we test, since we are introducing a hard dependency on Application existing even in functions where it's not used.

We could circunvent this by using parent references. However, we can easily get to a point where code like this is written: parent.parent.property_name = .... Not only this type creates highly coupled components, it's also very brittle. A component might break because of a change in the hierarchy of an apparently unrelated set of components. It might also be a nightmare to test, since we might need to reconstruct whole hierarchies of objects

It's possible to avoid this problems by being extra careful. The issue here is that it's easy to shoot yourself in the foot when using these approaches. So we'll try something different.

In this post we'll explore using a Singleton to store shared data and logic. A Singleton is a class that only has one instance which is shared. Although *Singleton*s look like classes with all static properties and methods, some key differences exist:

  1. *Singleton*s can implement interfaces
  2. *Singleton*s can lazy load resources
  3. A Singleton instance can be passed a argument to other functions or methods

Building a singleton in Vala is easy:

class SingletonExample {
    private SingletonExample () {
        // TODO: initialize all resources here
    }

    private static SingletonExample? _instance;

    public static SingletonExample getInstance () {
        if (_instance == null) {
            _instance = new SingletonExample ();
        }
        return _instance;
    }

    // shared data is created using public properties

    // shared logic is created using public methods 
}
Enter fullscreen mode Exit fullscreen mode

Using it is even easier:

SingletonExample.getInstance ().method_here ();
SingletonExample.getInstance ().property_name = XXX;
Enter fullscreen mode Exit fullscreen mode

Make no mistake, if we need shared data we are going to have to deal with coupling.

By using a Singleton all components depend on the Singleton **, which helps us to **avoid components depending on other components.

The advantage of using a Singleton is that it concentrates the dependencies into a single class. Instead of using parent or passing references deep into our component hierarchies, we can access SingletonExample.getInstance () anywhere in our code. We can also bind to SingletonExample's properties and connect to signals it emits. Thus, our reactive components can now react to local as well as global state changes.

Some situations where using *Singleton*s may bring advantages include

  1. sharing a list of opened folders/files in a text editor
  2. saving/retrieving user settings from dot files and making them available to the rest of the application
  3. storing a list media folders in a music/videos application

Global state should be minimized and good design helps both decreasing coupling and creating testable and quality code. However, sometimes using shared state leads to simpler design and less code. If this is the case, you might want to consider using Singleton in your next project.

💖 💪 🙅 🚩
igordsm
Igor Montagner

Posted on January 2, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related