Strategy Pattern

What is the Strategy Pattern?

Wikipedia describes it as:

In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. The strategy pattern:

  • – defines a family of algorithms
  • – encapsulates each algorithm
  • – makes the algorithms interchangeable within that family

The Strategy Pattern allows you to implement algorithms and usages of those algorithms completely separate. This allows you to have very flexible and reusable code, without the client that is using the code to know anything about how it works. The client can choose which version of the algorithm based on the information that it has; maybe the type of data, where the data came from, maybe user interaction. This can be done in a multitude of ways, including delegates, methods, class instances and interfaces – often using reflection to determine usage.


Let’s look at .NET/Linq’s IList.Sort as an example.

 public class Person {
  public string Name { get; set; }
  public int Age { get; set; }

  public override string ToString() {
    return $"{Name}: {Age}";
  }
}

public class Strategy {
  public static readonly ReadOnlyCollection<Person> People = new List<Person>
  {
    new Person { Name = "RJ", Age = 32 },
    new Person { Name = "Ryan", Age = 30 },
    new Person { Name = "Cassie", Age = 28 },
    new Person { Name = "Robert", Age = 57 }
  }.AsReadOnly();

  public void main() {
    var list = People.ToList();
    // Use a built delegate
    list.Sort(CompareByAge);

    // Use explicit lamda expression
    list.Sort((x, y) => x.Age.CompareTo(y.Age));

    // Implemented Single Method Interface
    list.Sort(new AgeComparer());

    foreach (var person in list) {
      Console.WriteLine(person);
    }
  }

  static int CompareByAge(Person left, Person right) {
    return left.Age.CompareTo(right.Age);
  }

  public class AgeComparer : IComparer<Person> {
    public int Compare(Person left, Person right) {
      return left.Age.CompareTo(right.Age);
    }
  }
}

None of the Comparison methods, know about sorting.

The sort method doesn’t know how to compare.

Clean separation of concerns

Adheres to the SOLID principle, enforcing Single Responsibility for more flexible code.

Using the strategy and implementing the strategy are completely separate.

Linq has endless uses of the Strategy pattern.


My company also takes advantage of this pattern for MVC Controller input model validation. This is just a snippet of it, but we plug in FluentValidation which has an AbstractValidator<T> that registers this Strategy to controllers that have an input model of T. This makes it so the controller does not even need to know about any validation at all. You just register how your Strategy should be applied with a marker interface of IValidated, and it does it. Very easy to add rules and sub-rules, and change it whenever you want, without changing your controller code.


public class AttributesInputModel : FormAttributes, IValidated
{
}

public class AttributesInputModelValidator : AbstractValidator<AttributesInputModel>
{
  public AttributesInputModelValidator(IFormAttributesCache cache)
  {
    CascadeMode = CascadeMode.StopOnFirstFailure;

    RuleFor(x => x.EffectiveDateString)
      .NotNull()
      .WithMessage("The effective date must be provided.");

    RuleFor(x => x.EffectiveDateString)
      .SetValidator(new DateValidator("Must provide a valid effective date."));

    RuleFor(x => x.EffectiveDate)
      .Must(x => x.Day == 1)
      .When(x =>
        !string.IsNullOrEmpty(x.EffectiveDateString)
        && DateValidator.IsValid(x.EffectiveDateString))
      .WithMessage("The effective date must be the first of the month.");

    RuleFor(x => x.ExpirationDateString)
      .SetValidator(new DateValidator("Must provide a valid expiration date, when provided."))
      .When(x => !string.IsNullOrEmpty(x.ExpirationDateString));

    RuleFor(x => x.ExpirationDate)
      .Must((model, x) => !x.HasValue || model.EffectiveDate <= x.Value)
      .When(x =>
        !string.IsNullOrEmpty(x.ExpirationDateString)
        && DateValidator.IsValid(x.ExpirationDateString))
      .WithMessage("Effective date must come before the expiration date.");

    RuleFor(x => x.ExpirationDate)
      .Must((model, x) =>
        !x.HasValue
        || x.Value.Day == DateTime.DaysInMonth(x.Value.Year, x.Value.Month))
      .When(x =>
        !string.IsNullOrEmpty(x.ExpirationDateString)
        && DateValidator.IsValid(x.ExpirationDateString))
      .WithMessage("Expiration date must be the last day of the month.");
  }
}

public class DateValidator : PropertyValidator
{
  public DateValidator(string errorMessage)
    : base(errorMessage) {}

  protected override bool IsValid(PropertyValidatorContext context)
  {
    var dateString = (string)context.PropertyValue;
    return IsValid(dateString);
  }

  public static bool IsValid(string dateString)
  {
    DateTime date;
    var result = DateTime.TryParse(dateString, out date);
    return result;
  }
}

Reminder, Pluralsight has many courses on different design patterns in many different languages and implementations, and they have a FREE trial!

Start a 10-day free trial at Pluralsight