2 Comments

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<Vector> 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<Vector> 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<double> values);
}
 
public class RollAverager : IRollAverager
{
  public double WeightedAverage(IEnumerable<double> 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<Vector> accelVectors, Orientation ori);

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

var calculateAverage = new Func<IList<Vector>,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.