The template method pattern

Posted by Kenny Eliasson | Posted in , , | Posted on 11:44

One of my favorite patterns is the "Template method pattern".

Basically its about having a abstract class that defines common behaviour with points that can be customizable.

Yesterday I was refactoring alot of classes tha't didn't make use of the template pattern, but they all inherited from the same base-class. The base-class acted more like a common place for methods that all objects needed.

Let me show you some code that I had before the refactoring.

public class QuestionQuery : QueryBuilder {

public IFluentMdxQuery BuildQuery() {
  return new FluentMdxQuery(_context)
  .Get(x =>
  {
    x.Rowset.ForQuestion(_queryId).WithChildren();
    x.AddMeasure(DefaultMeasure.RespondentCount);
    x.AddMeasure(DefaultMeasure.RespondentShare);

    base.AddComparisonIndex(x);
    base.AddBreakdown(x);
    base.AddTimeBreak(x);
    base.SwapAxes(x);
  })
  .WithFilter(base.FilterManager)
  .Order.By(_properties.OrderBy)
  .TheMeasureIs(currentMeasure)
  .AscendingIf(_properties.OrderBy != OrderBy.Measure)
  .SetupWith(setup => {
    setup.HideTheseIds(q => q.AddRange(_properties.HiddenIds));
    setup.AddHighlight.OnMeasure(DefaultMeasure.RespondentShare);
  })
  .WithRules(rules => {
    rules.AddDefaultRules(_properties.ShowAllAnswers());
    foreach (var rule in base.AddedRules) {
      rules.Add(rule);
    }
    rules.Add(new MakeQuestionLevelTotalAndPlaceLastRule());
  });

Thats alot of base-method calls in there.
Now take into consideration that I've got somewhere between 5 to 10 more classes like this.

So yesterday I was assigned to add new functionality to the system which involved these classes. And because the classes are built as they are, I was forced to add the functionality to all classes, and that tingled my "bad code sense". I was duplicating code all over the place!

So I sat down and realized that the Template method would be a perfect pattern to implement here. Make all common calls in a base-class and add ways of overriding the certain parts of the creation.

So heres my new base class. (Some methods are missing since they not add any value besides of taking up space)
public IFluentMdxQuery BuildQuery(ISetupFluentFilterManager filterQueryManager)
{

  return new FluentMdxQuery(_context)
  .Get(x => {
    SetupDimensionCollector(x);
    AddComparisonIndex(x);
    AddBreakdown(x);
    AddTimeBreak(x);
    SwapAxes(x);
  })
  .WithFilter(AddFilter(filterQueryManager))
  .SetupOrder(SetupOrder)
  .SetupWith(AddSetupRules)
  .WithRules(ruleCollector => {
    ruleCollector.AddDefaultRules();
    foreach (var rule in _addedRules) {
      ruleCollector.Add(rule);
    }
    AddExtraRules(ruleCollector);
  });
}

  protected abstract void SetupDimensionCollector(IDimensionCollector dimensionCollector)

  protected virtual void SetupOrder(IFluentMdxSortCommand sortCommand)
  {
    sortCommand.UseDefault();
  }

  protected virtual void AddExtraRules(IRuleCollector ruleCollector) { }


Its almost looks the same as the old one, but this time I've added the metods to the base class, implemented the default behaviour and made them virtual so they can be overriden in the child classes. I also made some methods abstract to force the implementing classes to add them.

So, how did these changes affect my QuestionQuery class?

protected override void SetupDimensionCollector(IDimensionCollector dimensionCollector)
{
  dimensionCollector.Rowset.ForQuestion(_queryId).WithChildren();
  dimensionCollector.AddMeasure(DefaultMeasure.RespondentCount);
  dimensionCollector.AddMeasure(DefaultMeasure.RespondentShare);
}

protected override void SetupOrder(IFluentMdxSortCommand sortCommand)
{
  sortCommand.By(_querySettings.OrderBy);
  sortCommand.TheMeasureIs(GetCurrentMeasure());
  sortCommand.AscendingIf(_querySettings.OrderBy != OrderBy.Measure);
}


Damn! Only 2 methods needed to be overriden. Everything else was default behaviour. Of course not all my classes we're as simple as this, but they all got more to the point and I avoided alot of duplication.

These changes also make it easy for me to add functionality to all query classes easy, just add it to the base class and we're done :)

Shout it

Comments (0)

Skicka en kommentar