Now that we have both VocabularyWord and ‘VocabularySet`, we can turn our attention to defining application model itself, allowing us to capture the overall state of the entire application.

For our first cut at defining this application object, we will have both a VocabularySet and a screen representing the current user interaction. Because its a simple and flexible navigation model, we’ll include a stack of screens to allow for easy transitions between screens without losing important state.

Maintaining a separation between user experience state and core data is important to do when designing your application model. I’ve found this mentioned in numerous tutorials for the JavaScript Redux library.

VocabularySet

The VocabularySet property captures the current set of works available within the application. This is the set that we’ll be modifying when editing, and the set that we’ll use when running a spelling drill.

We start by declaring the property itself:

public class WordTutorApplication
{
    public VocabularySet VocabularySet { get; }

When we create a brand new application model, as a part of application startup, we’ll start with an empty vocabulary set.

    public WordTutorApplication()
    {
        VocabularySet = VocabularySet.Empty;
    }

This gives us a guarantee that the value of VocabularySet is never null, making it easier to write some of our later code.

Following the pattern we’ve already used, we’ll provide a pair of methods to allow manipulation of the property. WithVocabularySet() allows us to completely substitute a new set of words:

    public WordTutorApplication WithVocabularySet(VocabularySet vocabularySet)
    {
        if (ReferenceEquals(VocabularySet, vocabularySet))
        {
            return this;
        }

        return new WordTutorApplication(
            this,
            vocabularySet: vocabularySet 
                ?? throw new ArgumentNullException(nameof(vocabularySet)));
    }

Checking with ReferenceEquals() allows us to take a memory saving shortcut when nothing actually changes.

I’m still considering whether I approve of having the parameter validation for vocabularySet occur quite so late in the method.

On the one hand, we get all the protection we need even though the validation doesn’t happen as the very first step in the method. On the other, common practice is to have all these kinds of checks up front before any actual logic, and I’m not entirely comfortable with moving away from this practice. We get away with it here because there are no side effects to the code run before the validation check.

The UpdateVocabularySet() method allows us to apply a transformation to the set of words we already have.

    public WordTutorApplication UpdateVocabularySet(
        Func<VocabularySet,VocabularySet> transformation)
    {
        if (transformation is null)
        {
            throw new ArgumentNullException(nameof(transformation));
        }

        var set = transformation(VocabularySet);

        if (ReferenceEquals(set, VocabularySet))
        {
            return this;
        }

        return new WordTutorApplication(
            this,
            vocabularySet: set);
    }

Here we have the parameter validation up front, in a more conventional location. Again, we provide a short circuit if the transformation doesn’t actually make any changes to the set.

Both of these transformation methods are based on a private constructor that creates a near clone, a technique we’ve used before.

    private WordTutorApplication(
        WordTutorApplication original,
        VocabularySet vocabularySet = null)
    {
        VocabularySet = vocabularySet ?? original.VocabularySet;
    }
}

Testing

Having good unit tests to make sure that our application model behaves as expected is critical. We don’t want to be trying to troubleshoot a dozen moving parts in one go when we get to the point of interacting with a running application.

I’m partial to using a three part naming convention (unit/scenario/expectation) as promoted in the book The Art of Unit Testing, though with a twist as suggested by Phil Haack of grouping related tests together.

These test names can be treated as a kind of specification for the behavior of the class. For example, here are the tests I wrote for functionality related to the ValidationSet property of our application model:

  • With VocabularySet
    • Given null vocabulary set, throws exception
    • Given vocabulary set, updates property
  • Update vocabulary set
    • Given null, throws exception
    • Given transformation
      • Receives current vocabulary set
      • Updates property to returned value
    • When transformation returns existing set, returns existing app

Screens

To handle the screens, we’ll add two members to our class - a reference to the current screen, and a stack of prior screens that we can restore when we close the current screen.

    private readonly ImmutableStack<Screen> _priorScreens;

    public Screen CurrentScreen { get; }

Of course, our constructor needs to change:

    public WordTutorApplication(Screen initialScreen)
    {
        _priorScreens = ImmutableStack<Screen>.Empty;
        VocabularySet = VocabularySet.Empty;

        CurrentScreen = initialScreen 
            ?? throw new ArgumentNullException(nameof(initialScreen));
    }

Once again, we see parameter validation happening relatively late in the method.

It would be easy to follow the existing pattern with our methods that manipulate screens. For example, we could write UpdateScreen(), PushScreen() and PopScreen().

This would be a mistake.

While we don’t want to implement anything more than necessary, it’s important that our methods provide some useful semantics to govern how our application works. Users don’t think in terms of pushing or popping screens - they think in terms of opening and closing them.

It’s easy to fall into the trap of defining everything in terms of CRUD operations (create, read, update, delete) when a more expressive set of operation provides increased clarity.

    public WordTutorApplication OpenScreen(Screen screen)
    {
        var screens = _priorScreens.Push(CurrentScreen);

        return new WordTutorApplication(
            this,
            currentScreen: screen 
                ?? throw new ArgumentNullException(nameof(screen)),
        priorScreens: screens);
    }

Here we open a new screen, preserving the old one on our stack to recall it later. Closing a window is the obvious inverse.

    public WordTutorApplication CloseScreen()
    {
        if (_priorScreens.IsEmpty)
        {
            // Don't ever want to close the last screen
            return this;
        }

        return new WordTutorApplication(
            this,
            currentScreen: _priorScreens.Peek(),
            priorScreens: _priorScreens.Pop());
    }

Note that we’ve included a check to make sure we never close the last screen; this ensures we will always have a visible UX available to the user.

Lastly, we’ll provide a way to modify the current screen - this might be a small change to the existing screen, or a transition to a completely different screen.

    public WordTutorApplication UpdateScreen(Func<Screen, Screen> transformation)
    {
        if (transformation is null)
        {
            throw new ArgumentNullException(nameof(transformation));
        }

        var screen = transformation(CurrentScreen);
        if (ReferenceEquals(screen, CurrentScreen))
        {
            return this;
        }

        return new WordTutorApplication(
            this,
            currentScreen: screen);
    }

As you might have already guessed, we need to update our private constructor to accommodate screen navigation:

    private WordTutorApplication(
        WordTutorApplication original,
        VocabularySet vocabularySet = null,
        Screen currentScreen = null,
        ImmutableStack<Screen> priorScreens = null)
    {
        _priorScreens = priorScreens ?? original._priorScreens;
        CurrentScreen = currentScreen ?? original.CurrentScreen;
        VocabularySet = vocabularySet ?? original.VocabularySet;
    }

Coupling

As I write this it seems clear that the WordTutorApplication class has very low coupling. On the one hand, we have the vocabulary set and the methods to manipulate it. On the other, a stack of screens and the methods to manipulate them.

The two do not interact.

This is a bit of a code smell, and indicates that we might need to re-factor this class into separate responsibilities in the future. For now however this seems good enough.

Prior post in this series:
Vocabulary Set
Next post in this series:
Commandline Builds

Comments

blog comments powered by Disqus