One of the problems we currently have is caused by lack of updates - our application model can change without our view-models being notified that the change has happened, leaving our user interface showing stale information. To solve this, we’ll extend our Redux store with subscriptions, to allow each view-models to be proactively notified when things change.
There are a multitude of different ways this could be done.
For example, we could simply publish an event on the redux store, allowing anyone to be notified when the state of the store changes.
This might be a straightforward and simple, but it would also result in a lot of repetitive boilerplate as every ViewModel repeats the same change detection logic. We can do better.
Let us create a dedicated subscription object, one that ensures we only get notifications when things have changed. To our interface, IReduxStore<T>
, add the following declaration:
This method creates a subscription to the content of the store. The reader
function reads a value from the store that can be tested for equality, and the whenChanged
action will be called when the value is different. This follows our established pattern of short circuiting, ensuring we only send notifications when required. By returning IDisposable
, we’re giving our consumers a convenient (and idiomatic) way to release the subscription when they’re done.
To encapsulate the subscription, we’ll create a new class: ReduxSubscription<T, V>
. An initial design for the class might look something like this:
store
is a reference to our owning Redux Store;reader
is a function to read the value of interest from the store state; andwhenChanged
is an action to trigger when the read value changes.
You can imagine how the subscription would use store
to register itself inside the constructor, and would also deregister itself when the subscription was released.
While being a very object oriented design, this approach introduces strong coupling between ReduxStore<T>
and ReduxSubscription<T,V>
. Such coupling makes it harder to unit test ReduxSubscription
in isolation. It also complicates the constructor a little, requiring registration with the store.
Can we lower the coupling between these classes and simplify the constructor at the same time?
Yes, by replacing the store
parameter with an action to call when the subscription is released. We’ll call that whenReleased
.
Our next issue is a common one when working with Generics. The store itself has only one type parameter, one axis of variation. Our subscription class has two type parameters, two axes of variation. For good reasons, we can’t simply declare a List<ReduxSubscription<T,*>>
to wrap them all up in a single list. We need to introduce a common type, one that is only variant on T
.
To this end, I’ve broken the class into two pieces, each with a single responsibility. A parent class, ReduxSubscription<T>
that handles releasing the subscription; and a child class, ReduxSubscription<T,V>
that handles our notifications.
This only has one type parameter, so we will be able to keep them all in a single container within our store.
Our base class has a single responsibility; to handle the lifetime of the subscription. The constructor accepts an action, which we’ll call when the subscription is released.
The Publish()
method will be overridden by our subclass to actually share information.
When we’re finished with the subscription, a call to Release()
will deactivate the subscription so that we’re no longer receiving notifications. We implement Dispose()
as a direct call to this.
We use the Released
property to ensure that we only invoke our whenReleased
action once. For now, we’re using an implementation without locking - if/when we start running multiple threads, we’ll need to add some protection against concurrency errors.
Our subclass is also relatively simple, extending the base class to add smart notifications.
The constraint is to ensure that we can do effective change notifications by only notifying subscribers when the value has actually changed.
Our private members are largely predictable; members used to capture the delegates we’re passed, and the last value we’ve seen.
As usual, our constructor just captures parameter values, though we do have a couple of guard clauses to ensure we don’t have any errant null values floating around.
Once we’ve got everything set up, actual publication is pretty straightforward.
A few highlights worth noting about this method.
-
First time the subscription is published,
lastValue
will be null, and we’ll sent a potentially redundant notification. We could avoid this by invokingreader
during construction, but that would also require us to pass in the current state from our redux store. -
Updates to
_lastValue
happen before we send the notification to short circuit any potential infinite notification loops. If we updated_lastValue
after calling_whenChanged
, we’d be vulnerable if the code we called in turn triggered another call toPublish()
. -
We need to check the
Released
property to ensure we don’t send a notification from a released subscription. It’s possible (even likely) that subscriptions will be released by consumers in response to messages received; we’d prefer our consumers to not have to worry about extra notifications coming in afterRelease()
.
Now we extend our store to allow subscriptions. We begin by providing a way for consumers to create subscriptions:
Next, we add to our Dispatch()
method, so that all the subscriptions are given a chance to update when our state might be updated.
Taking a copy of the set of subscriptions is necessary to avoid getting an exception if any of our subscriptions get removed during publication.
Lastly, we publish the count of our current subscriptions, largely to make it easier to write some unit tests.
Next time, we’ll modify our ViewModels to stay up to date by using subscriptions.
Comments
blog comments powered by Disqus