About me

Michael L Perry

Improving Enterprises

Principal Consultant

@michaellperry

User login

Function Closure as IoC

imageWith my style of MVVM, you end up with several layers of view model, each one taking a reference to a different part of the model. The view model hierarchy mirrors the model hierarchy. When a view model lower in the hierarchy needs a new reference, I have to plumb that parameter through all of the constructors.

At least, I used to. Now I use a simple functional IoC pattern.

Hierarchy of view models

MyCon is a template for conference web sites and mobile applications. In the Windows 8 app, the sessions for the conference appear in a GridView, grouped by track. The GridView is data bound to the TracksViewModel. This view model exposes a collection property called Tracks. It is a collection of TrackViewModel. This view model in turn exposes a collection of SessionHeaderViewModel through a property called Items.

public class TracksViewModel
{
    public IEnumerable<TrackViewModel> Tracks
    {
        get
        {
            return
                from track in _synchronizationService.Conference.Tracks
                orderby track.Name
                select new TrackViewModel(track);
        }
    }
}

public class TrackViewModel
{
    public IEnumerable<SessionHeaderViewModel> Items
    {
        get
        {
            return
                from sessionPlace in _track.CurrentSessionPlaces
                orderby sessionPlace.Place.PlaceTime.Start
                select new SessionHeaderViewModel(sessionPlace);
        }
    }
}

Each view model constructor takes a reference to the part of the model that it represents. So the TracksViewModel takes a reference to the SynchronizationService – the top-level object that provides the reference to the Conference. The TrackViewModel references a Track. And the SessionHeaderViewModel references a SessionPlace – a model object that knows where and when a session will be given.

public class TracksViewModel
{
    private readonly SynchronizationService _synchronizationService;
        
    public TracksViewModel(SynchronizationService synchronizationService)
    {
        _synchronizationService = synchronizationService;
    }
}
public class TrackViewModel
{
    private readonly Track _track;
        
    public TrackViewModel(Track track)
    {
        _track = track;
    }
}
public class SessionHeaderViewModel
{
    private readonly SessionPlace _sessionPlace;
        
    public SessionHeaderViewModel(SessionPlace sessionPlace)
    {
        _sessionPlace = sessionPlace;
    }
}

Add a selection model

The application also has a SelectionModel. This class records which session is currently selected. The SessionHeaderViewModel needs to set a property on the SelectionModel when it has been clicked. So let’s add the SelectionModel reference to the SessionHeaderViewModel constructor.

public class SessionHeaderViewModel
{
    private readonly SessionPlace _sessionPlace;
    private readonly SelectionModel _selectionModel;
        
    public SessionHeaderViewModel(SessionPlace sessionPlace)
    {
        _sessionPlace = sessionPlace;
        _selectionModel = selectionModel;
    }
}

After doing this, we have broken its parent view model – TrackViewModel. It needs to call the SessionHeaderViewModel constructor, so it needs to pass in the new parameter.

public class TrackViewModel
{
    private readonly Track _track;
    private readonly SelectionModel _selectionModel;
        
    public TrackViewModel(Track track, SelectionModel selectionModel)
    {
        _track = track;
        _selectionModel = selectionModel;
    }

    public IEnumerable<SessionHeaderViewModel> Items
    {
        get
        {
            return
                from sessionPlace in _track.CurrentSessionPlaces
                orderby sessionPlace.Place.PlaceTime.Start
                select new SessionHeaderViewModel(sessionPlace, _selectionModel);
        }
    }
}

Now it takes the SelectionModel and passes it on through. But guess what. Now we’ve broken its parent – TracksViewModel. So again, we have to plumb the parameter through the parent.

public class TracksViewModel
{
    private readonly SynchronizationService _synchronizationService;
    private readonly SelectionModel _selectionModel;
        
    public TracksViewModel(SynchronizationService synchronizationService, SelectionModel selectionModel)
    {
        _synchronizationService = synchronizationService;
        _selectionModel = selectionModel;
    }

    public IEnumerable<TrackViewModel> Tracks
    {
        get
        {
            return
                from track in _synchronizationService.Conference.Tracks
                orderby track.Name
                select new TrackViewModel(track, _selectionModel);
        }
    }
}

And so it goes with every constructor parameter I need to add. If a child view model needs to know about it, then the parent view model needs to provide it. And it bubbles up the hierarchy. Every level needs to know about all of these parameters, even if they don’t use them directly.

Functional IoC to the rescue

This looks like the sort of problem that an IoC container solves. IoC – Inversion of Control – is a pattern that takes construction and dependency management out of the hands of business objects, like view models. It places that responsibility in the hands of a container. The IoC container creates all of the business objects, so that they don’t have to create each other. And it figures out how to resolve their dependencies so that the intermediate objects don’t need to worry about it.

There are many great open source IoC containers available. One that would be particularly adept at solving this problem is AutoFac. But we’re not going to use AutoFac, or any other IoC container library. Instead, we are going to use a feature of the language: function closure.

Closure simply means that a function in C# will encapsulate any variables that are referenced within the function body. So we can declare a function that creates a SessionHeaderViewModel like so:

SelectionModel selectionModel = new SelectionModel();

Func<SessionPlace, SessionHeaderViewModel> newSessionHeaderViewModel = s =>
    new SessionHeaderViewModel(s, selectionModel);

The variable selectionModel is declared outside of the scope of the lambda, but since it is referenced inside, that variable is encapsulated within the function. Anyone who calls the newSessionHeaderViewModel function will get back an object with that same SelectionModel reference.

Instead of letting the parent TrackViewModel call the SessionHeaderViewModel directly, we’ll just give it this function.

public class TrackViewModel
{
    private readonly Track _track;
    private readonly Func<SessionPlace, SessionHeaderViewModel> _newSessionHeaderViewModel;

    public TrackViewModel(Track track, Func<SessionPlace, SessionHeaderViewModel> newSessionHeaderViewModel)
    {
        _track = track;
        _newSessionHeaderViewModel = newSessionHeaderViewModel;
    }

    public IEnumerable<SessionHeaderViewModel> Items
    {
        get
        {
            return
                from sessionPlace in _track.CurrentSessionPlaces
                orderby sessionPlace.Place.PlaceTime.Start
                select _newSessionHeaderViewModel(sessionPlace);
        }
    }
}

Continuing this trend upward, we’ll define a function that creates TrackViewModels. Again, we’ll use functional closure to encapsulate the parameters we need.

SelectionModel selectionModel = new SelectionModel();

Func<SessionPlace, SessionHeaderViewModel> newSessionHeaderViewModel = s =>
    new SessionHeaderViewModel(s, selectionModel);

Func<Track, TrackViewModel> newTrackViewModel = t =>
    new TrackViewModel(t, newSessionHeaderViewModel);

In this case, the newTrackViewModel function is encapsulating the newSessionHeaderViewModel function. So we change the TracksViewModel to use this function.

public class TracksViewModel
{
    private readonly SynchronizationService _synchronizationService;
    private readonly Func<Track, TrackViewModel> _newTrackViewModel;

    public TracksViewModel(SynchronizationService synchronizationService, Func<Track, TrackViewModel> newTrackViewModel)
    {
        _synchronizationService = synchronizationService;
        _newTrackViewModel = newTrackViewModel;
    }

    public IEnumerable<TrackViewModel> Tracks
    {
        get
        {
            return
                from track in _synchronizationService.Conference.Tracks
                orderby track.Name
                select _newTrackViewModel(track);
        }
    }
}

A containerless container

All of the functions related to a given top-level view model need to be managed in the same scope. That is what allows those functions to be encapsulated into each other via closure. And so I like to declare a static class in my view model namespace called Container. This static class contains a static method that creates a top-level view model.

public static class Container
{
    public static TracksViewModel CreateViewModel(
        SelectionModel selectionModel,
        SynchronizationService synchronizationService)
    {
        Func<SessionPlace, SessionHeaderViewModel> newSessionHeaderViewModel = s =>
            new SessionHeaderViewModel(s, selectionModel);

        Func<Track, TrackViewModel> newTrackViewModel = t =>
            new TrackViewModel(t, newSessionHeaderViewModel);

        return new TracksViewModel(synchronizationService, newTrackViewModel);
    }
}

Then if I need to add a parameter to any of the view models in this hierarchy, I simply add that parameter to this function. It is encapsulated into the functions that need it. The view models that don’t need it do not need to change.

Without resorting to an IoC container library, I have defined a container that creates instances of view models. It manages the dependencies of each particular view model, so that the parent view models don’t have to. It does so simply by using the language feature of function closure.

You can see the complete source code at https://github.com/michaellperry/MyCon. Open the FacetedWorlds.MyCon project the WinRT folder.