What do romantic comedies and software development have in common?
But while the rom-com has a fascination with ensuring that the coupling has occurred, and that it will be everlasting (until the sequel) software runs away screaming at the very idea. But how far can we take that principle?
A number of years ago, as someone fairly new in my adopted country, I needed to call an ambulance. I grew up with 999, but that's for back home. Similarly 911 isn't going to work everywhere. It turns out in Belgium you need to know a lot of numbers. Police? 101. Fire? 100. Ambulance? 105. There's been an arson attack and you need to report it to the police, but there are injuries? I don't know, maybe it's additive, maybe you call 306. Isn't that a Peugeot?
When I needed to call for an ambulance I was missing the coupling to get the help I needed, I just didn't know the number. I ended up reverting to basics, I found someone in the street and I yelled. (It turns out "blind panic" is just another word for delegation.)
And it's the same for software. A completely uncoupled architecture just isn't realistic. A load of objects just floating around completely isolated from each other will achieve precisely nothing. Things have to know about things, otherwise those things can't get other things to do something, possibly to another thing.
Which (very slowly) brings me to my question. How far can we, and how far should we, decouple the code in our app? Some form of coupling has to be desirable. So, what can we do to inform, say, our visual elements about changes in the model? Idealistically the view and model are elements we want to decouple as much as possible.
Generally speaking, changes to the view are propagated through a delegate, changes to the model through a notification. And that makes sense for the model, which may have multiple dependencies that need to know about those changes, so a notification broadcasting within the app can be read by each dependency and dealt with accordingly.
Ok, so problem solved. We have a model, it changes, the model yells out a notification like a foreigner not knowing the number for an ambulance, and one or more passing objects knows what to do with the information. The model has done its duty, the other objects take over.
Here's what keeps me up at night: where is that notification's name defined? Should it be specified by the object that will post it, or the object that will observe it, or something else?
The model defines the notification.
This makes a lot of sense because the model is the first to know about a change, so you can argue that it should define what it's going to announce. It should be for other objects to observe that notification. In this case the notification is defined by the object that will post it.
Your dependencies are coupled to the Model in the sense that they need to know the notification name to listen for, and potentially the contents of a user info dictionary.
However, if you decide you want your model to define the notifications you may have just introduced notifications into your watch app as well as the iOS app. Did you mean to do that? Possibly not. Is it a big deal? Probably not, but it kinda just creeped in there and that doesn't feel right.
The observer defines the notification.
Now it's the model that has to know about the notification in order to post it, but the coupling remains. Here the notification is defined by the entity that will observe it, while the model has to know about the existence of the notification name and how to create the user info.
This is actually the real world. I don't define the emergency services lines of communication, they do that. I post the notification that I need help and they observe it.
But this is problematic. As mentioned above a change in the model might be observed by more than one object. Maybe a view and another object for persistent storage need to know about changes in the model. Which one should define the notification? I'm not going to have them define different notifications and have my model post twice. Or maybe I should, because the view and the persistence layer might need different user info dictionaries, instead of a general one.
Something in between
We can create an intermediate object that exists in between the model and the view that coordinates the changes. The model yells out something has happened, the intermediate object knows what that means, and informs the view.
I know what you're thinking. This is something between the view and the model, it's obviously the view model. NO. Leave it alone, you animal. Hasn't the view model suffered enough? It has a specific (-ish) job and doesn't need to become a dumping ground like the view controller used to be. Seriously, listen to fatalerror.fm parts 1 and 2 on the view model and never darken my door again. Anyway, what we want is a solution that can be applied to any architecture you wish to use, whether that is MVC, MVVM, VIPER or "Rory's proprietary UI based embedded device architecture." (assuming your name is Rory.)
The truth is the notification centre is this intermediate object. But it's its interface that's the issue. The notification name and user info dictionary design is forcing us to find a place to define these notifications that result in a compromise no matter where we define them.
An alternative NotificationCenter
Let's take a leaf out of another solution I've written about before, Coordinators. The coordinator pattern takes the app flow knowledge out of the individual controllers and puts it in a central location, where decisions are made on what controller to present next.
We can design a similar NotificationCoordinator that does the same job as NotificationCenter, but removes the need for the poster and observer to know the details of the notification name and user info. The model can inform the notification coordinator that something has happened, and it will make decisions on what objects need to be notified, and with what data. Here's a description of what that would look like
We have a coordinator that registers listeners and deals with notifications to update those listeners from a notification poster. Nothing earth shattering.
Here's a simple model that will ask the notification coordinator to handle an update
And here's a listener that handles incoming notifications of updates
One thing that should be obvious here is neither the poster nor the listener know anything about each other. They don't need to know about the type of notification they were posting or observing, nor about user info. The listener expects the incoming notification coordinator to have prepared the user info in a way that makes sense to the listener.
Here's our coordinator
You can see in the notify(with:) method the coordinator has the domain knowledge that links the specific poster to a given listener, or listeners. On top of that the coordinator takes data from the poster and converts it to the required form for the listener.
But here's the thing. This might be cleaner from the perspective of the poster and the listener, but is it better? I'm not so sure. I honestly didn't know where I was going with this blog post when I started. I knew I didn't like the NotificationCenter pattern and the coupling it results in, nor do I like the fact that's it's a singleton which makes unit testing a pain.
But still, all I've done is moved the coupling to a new location. That shouldn't be a surprise, a whole lot of refactoring is just moving code from one location to another, the trick is that the new location should be a better choice.
The question is, is this a better choice? The more we decouple our architecture, the more we have to provide the intermediates whose job it is to know about the coupling. Coordinators, such as above but also those for app flow, have a problem with bloat, all the knowledge is in one place. NotificationCenter has the advantage that its interface is so general that it doesn't need any such domain knowledge.
Once again, I've over analysed something to the point where the simple solution, as inelegant as it is, is starting to look like a whole lot less work.
Or, y'know, you could just be reactive. I guess. I should have thought of that before I started.