Functional(ish) C# & MVVM: Single-Responsibility and Code-as-Data

Function

I’ve spent the last year working in C# and WPF. Over a few blog posts, I have alluded to a particular pattern of structuring code, referring to it as “stateless single responsibility” objects or “stateless SRP” objects. For the purposes of this post I will call the pattern “stateless single responsibility principle” (SSRP) objects. I’d like to go into a bit more detail about what the pattern is and why I use it.

I think it will be simplest to begin with an example problem we might use my pattern to solve, then show how I would do it and explain all the parts.

The problem:

Solution steps:

  1. Convert each acceleration to a roll value based on a known orientation.
  2. Average the roll values together, smoothing out edge cases.

The Solution

Below is the full solution, with a few of the lengthier math calculations left as comments. Excuse the length, but I think seeing the whole picture first helps bring the techniques into focus.

Vector.cs

public class Vector 
{
  public double X { get; private set; }
  public double Y { get; private set; }
  public double Z { get; private set; }

  public Vector(double x, double y, double z)
  {
    X = x;
    Y = y;
    Z = z;
  }

  // standard vector functions omitted for space (dot product, cross product, etc.)
}

Orientation.cs

public class Orientation 
{
  public Vector Up;
  public Vector Forward;
  public Vector Left { get { return Up.CrossProduct(Forward); }}
}

RollAverageCalculator.cs

public interface IRollAverageCalculator
{
  double CalculateAverage(IList accelVectors, Orientation ori);
}

public class RollAverageCalculator : IRollAverageCalculator
{
  private readonly IAccelToRollCalculator _rollCalc;
  private readonly IRollAverager _rollAverager;
  public RollAverageCalculator(IAccelToRollCalculator rollCalc, 
                               IRollAverager rollAverager)
  {
    _rollCalc = rollCalc;
    _rollAverager = rollAverager;
  } 

  public double CalculateAverage(IList accelVectors, Orientation ori)
  {
    return _rollAverager.WeightedAverage(accelVectors.Select(accelVector => 
      _rollCalc.CalculateRoll(accelVector, ori)));
  }
}

AccelToRollCalculator.cs

public interface IAccelToRollCalculator 
{
  double CalculateRoll(Vector accel, Orientation ori);
}

public class AccelToRollCalculator : IAccelToRollCalculator
{
  public double CalculateRoll(Vector accel, Orientation ori)
  {
    // use dot product to determine difference between accel and gravity
    // use cross product of accel and gravity to determine left v. right roll
    return rollValue;
  }
}

RollAverager.cs

public interface IRollAverager
{
  double WeightedAverage(IEnumerable values);
}

public class RollAverager : IRollAverager
{
  public double WeightedAverage(IEnumerable values)
  {
    // calculate weighted average somehow, 
    // such as preferring first value and removing outliers
    return averagedValue;
  }
}

A Few Notes

1. Data Classes

I’ll comment first on the “data” classes — Vector and Orientation. I’m a big believer in the KISS principle when it comes to stateful classes. I try to keep as little logic in stateful classes as I can.

The only logic I usually include is calculated properties that provide “intrinsic” values (such as the length of a vector), and occasionally logic to enforce that the state stays valid. For example, I don’t enforce it here, but it would be reasonable for Orientation to force that Up and Forward are perpendicular (i.e. have a dot product of 0). In other words, if some state properties are inter-related, I will use code in that class to enforce the proper relation between the properties. I’ve seen inter-related properties that are not tightly coupled cause any number of debugging headaches.

Benefits of Keeping Data Classes Simple

  • Easy to test
  • Always valid
  • Easy to create instances as inputs for other tests

2. Stateless Single Responsibility Principle Classes

Next we have RollAverageCalculator. You can see that it implements interface IRollAverageCalculator. This is really just for testing purposes, as mocking the function CalculateAverage requires either the virtual keyword on the function, or the class to implement some interface. I have chosen the latter option, though either is valid. Mainly I can’t stand the pain of worrying about constructors in mocked classes. And as I intend for my class to be essentially a function object, referring to it elsewhere as an interface with only one function feels like a more reasonable approximation than a concrete class with a real constructor and a virtual function.

RollAverageCalculator has very minimal logic. It mostly strings together the usage of its dependencies: IAccelToRollCalculator and IRollAverager. AccelToRollCalculator and RollAverager do all the heavy lifting. This is in keeping with a class dependency pattern that resembles a tree structure. In essence, all “real” calculation occurs at leaves, and the branch nodes exist mostly to coordinate the work of leaves (or other branch nodes). The rationale for this structure is the “Single Responsibility Principle” — RollAverageCalculator is not responsible for turning acceleration vectors into rolls, nor is it responsible for how to average the list of rolls. Instead it delegates those responsibilities to its dependencies. We’ll come back to RollAverageCalculator in a bit, but first let’s look at its dependencies.

AccelToRollCalculator performs the complicated math of determining the roll from an acceleration vector and an orientation. Because it has no dependencies, we can legitimately call it stateless. Even if CalculateRoll had side effects, those effects cannot alter AccelToRollCalculator. Accordingly, we can create one Singleton instance of the class and re-use it every time an IAccelToRollCalculator is wanted.

RollAverager is much like AccelToRollCalculator in structure. It has no dependencies and no state, so we can create a Singleton.

Because AccelToRollCalculator and RollAverager are stateless, and because those are the only two dependencies of RollAverageCalculator, we can say that RollAverageCalculator is effectively stateless, even though it has member variables. You could imagine a function:

public double CalculateAverageFull(IAccelToRollCalculator accelToRollCalculator, 
                                   IRollAverager rollAverager,
                                   IList accelVectors, Orientation ori);

The existing function CalculateAverage is essentially a partial application of the above function:

var calculateAverage = new Func,Orientation,double>((accelVectors, ori) => 
  CalculateAverageWithDependencies(_accelToRollCalculator, _rollAverager, 
                                   accelVectors, ori));  

Because we have seen that RollAverageCalculator is also stateless, we can again set up our dependency injection to provide it as a Singleton instance.

Unit testing these classes is very straightforward. The “leaf” classes will create the simple data objects required as arguments, and test outputs directly without using mocks. “Branch” classes will use mocks to guarantee the right relationships between their children (dependencies).

Benefits of Stateless Single-Responsibility Principle classes and Dependency Hierarchy Tree

  • Easy to mock functionality without mocking state
  • Separates unit tests into cleanly divided categories
  • Straightforward dependency injection
  • Creates code that adheres to the Interface Segregation Principle

3. Skinny ViewModels

The WPF app I spent the last year creating required a large number of ViewModels, many of them dynamically created and having a one-to-one relationship with my models (as all ViewModels were bound to a visible View at the same time). The combination of this necessity and my use of the Reactive library for delivering updates to bound properties led to an additional innovation, what I’ll call “skinny” ViewModels. In essence, I started treating my dynamic ViewModels the same way I treat simple data classes: make them easy to instantiate and contain mostly data, not logic. SSRPs created the data stream sources for each ViewModel.

This all worked great, except for Buttons. ICommand — and specifically, MVVM Light’s implementation RelayCommand — basically expects you to provide a member function on the ViewModel or at least a lambda in order to specify behavior of a button click (as well as whether the button is enabled). So suddenly I was back in the land of mingling state and business logic. It really felt at odds with the simple property binding I was doing, like the data was trying to move in two directions.

When I found the answer, it felt obvious. Along with the streams of data which would set view model properties, I also pass in an Action and a Func<bool> for each button, to serve as the Execute and CanExecute callback, respectively. To handle dynamic buttons (where the meaning of the button or the enable state of the button changes), I turned once again to my trusty sidekick, the reactive library. Now a button can be described in terms of IObservable<Action> and IObservable<Func<<bool>>. Suddenly my button logic was nestled alongside my streaming property value logic, where it belonged. It seemed a little strange at first, but really it is just an application of the functional pattern of treating code as data. That is, the Action to perform when the button is clicked is just data (or state) that is passed into the stateful ViewModel object.

Benefits of Skinny ViewModels

  • Simpler ViewModel code that is easier to test
  • Keeps business logic in SSRPs, with the benefits listed above
  • Also separates business logic from WPF constructs, which helps encapsulate the UI framework (i.e. easier to swap out WPF for something else later if you want)

Going Forward

This pattern worked really well on my project, but I think it could be streamlined a little, albeit by deviating farther from "standard" C#. Instead of having a bunch of interfaces with only one function, I might use delegates instead. Ninject allows you to bind to delegates, and Moq allows you to mock them, so trying it out might not be too hard. It was simply too late in the project for me to attempt the switch. Then again, my overall trend is in a functional direction, so maybe the next time around I'll go straight for F# instead.
 

Conversation
  • Tyler says:

    Could you follow up on how you hid the button commands? It’s probably my lack of experience but how did you decouple state and business logic using the IObservable interface?

    Thanks

    • Will Pleasant-Ryan Will Pleasant-Ryan says:

      The commands aren’t hidden, per se. It’s more that whether a button is enabled (which is part of its state), what it does (also state, in the sense of a lambda / Action), and sometimes even the button’s text (still state) are no longer defined the ViewModel class. Following my pattern, those bits of state are provided by IObservable streams.

      So for (a contrived) example, imagine a button that causes business logic to rotate among three states, “Foo”, “Bar”, and “Baz”. The text of the button reflects the current state (i.e. it reads “Foo” when the state is “Foo”, “Bar” when the state is “Bar”, etc.). The Action (click callback) for the button needs to call out to business logic to rotate state. And let’s also say that it takes some non-negligible amount of time to switch to the next state. So when you click the button it becomes disabled until the transition is complete. Under “normal” circumstances you would handle all this logic in the ViewModel (or at best delegate to other classes to handle the logic). However using the IObservable method, the button is set up so that the text, the Action callback, and the enabled state are driven by three IObservables (of string, Action, and boolean respectively). So for example, say the Button is currently in the “Bar” state. A click of the button will fire the current Action for the button, which is a lambda that causes the state to advance to “Baz”. But the button only knows it fired an Action. The business logic of the button’s current Action (defined elsewhere) will first cause false to come down the “enabled” IObservable stream, do whatever logic is necessary to change to “Baz” state, trigger “Baz” to come down the “text” stream, and then send true down the “enabled” IObservable stream. Finally the “Action” IObservable stream should now also update so that the code for the button will trigger the transition from “Baz” to “Foo”.

      As I mentioned this example is a bit contrived, but it shows how my approach works. This approach pays dividends especially when a) Buttons depend on the a streaming data source also used by other widgets on the screen or b) other external conditions alter the Action or Enabled state of the button. Where normally you might have to listen to multiple events or a message buses (each of which require explicit desubscription), with IObservables you end up with one and only one subscription for each data point, which is easier to manage. Lastly, it is worth noting that if, under the “conventional” method, you did delegate off to helper classes for your button’s business logic, those classes would be exposed as dependencies to the ViewModel somehow (probably constructor arguments). But when your ViewModel just consumes IObservable streams, it becomes oblivious those dependencies and is a pure data object. This highlights the fact that my approach is most helpful when your ViewModels are dynamically created (e.g. a custom ViewModel for each item in a dynamic list).

  • Comments are closed.