© Dmitri Nesteruk 2020
D. NesterukDesign Patterns in .NET Core 3https://doi.org/10.1007/978-1-4842-6180-4_24

24. Template Method

Dmitri Nesteruk1  
(1)
St. Petersburg, c.St-Petersburg, Russia
 

The Strategy and Template Method design patterns are very similar, so much so that, just like with factories, I would be very tempted to merge those patterns into some sort of “Skeleton Method” design pattern. I will resist the urge.

The difference between Strategy and Template Method is that Strategy uses composition (whether static or dynamic), whereas Template Method uses inheritance. But the core principle of defining the skeleton of an algorithm in one place and its implementation details in other places remain, once again observing OCP (we simply extend systems).

Game Simulation

Most board games are very similar: the game starts (some sort of setup takes place), players take turns until a winner is decided, and then the winner can be announced. It doesn’t matter what the game is – chess, checkers, something else – we can define the algorithm as follows:
public abstract class Game
{
  public void Run()
  {
    Start();
    while (!HaveWinner)
      TakeTurn();
    WriteLine($"Player {WinningPlayer} wins.");
  }
}
As you can see, the run() method , which runs the game, simply uses a set of other methods and properties. Those methods are abstract and also have protected visibility so they don’t get called from the outside:
protected abstract void Start();
protected abstract bool HaveWinner { get; }
protected abstract void TakeTurn();
protected abstract int WinningPlayer { get; }

To be fair, some of the preceding members, especially void-returning ones, do not necessarily have to be abstract. For example, if some games have no explicit start() procedure , having start() as abstract violates ISP since members which do not need it would still have to implement it. In the “Strategy” chapter we deliberately made an interface, but with Template Method, the case is not so clear-cut.

Now, in addition to the preceding, we can have certain protected fields that are relevant to all games – the number of players and the index of the current player:
public abstract class Game
{
  public Game(int numberOfPlayers)
  {
    this.numberOfPlayers = numberOfPlayers;
  }
  protected int currentPlayer;
  protected readonly int numberOfPlayers;
  // other members omitted
}
From here on out, the Game class can be extended to implement a game of chess:
public class Chess : Game
{
  public Chess() : base(2) { /* 2 players */ }
  protected override void Start()
  {
    WriteLine($"Starting a game of chess with {numberOfPlayers} players.");
  }
  protected override bool HaveWinner => turn == maxTurns;
  protected override void TakeTurn()
  {
    WriteLine($"Turn {turn++} taken by player {currentPlayer}.");
    currentPlayer = (currentPlayer + 1) % numberOfPlayers;
  }
  protected override int WinningPlayer => currentPlayer;
  private int maxTurns = 10;
  private int turn = 1;
}
A game of chess involves two players, so that’s the value fed into the base class’ constructor. We then proceed to override all the necessary methods, implementing some very simple simulation logic for ending the game after ten turns. We can now use the class with new Chess().Run() – here is the output:
Starting a game of chess with 2 players
Turn 0 taken by player 0
Turn 1 taken by player 1
...
Turn 8 taken by player 0
Turn 9 taken by player 1
Player 0 wins.

And that’s pretty much all there is to it!

Functional Template Method

As you may have guessed, the functional approach to Template Method is to simply define a stand-alone function runGame() that takes the templated parts as parameters. The only problem is that a game is an inherently mutable scenario, which means we have to have some sort of container representing the state of the game. We can try using a record type:
type GameState = {
  CurrentPlayer: int;
  NumberOfPlayers: int;
  HaveWinner: bool;
  WinningPlayer: int;
}
With this setup, we end up having to pass an instance of GameState into every function that is part of the template method. The method itself, mind you, is rather simple:
let runGame initialState startAction takeTurnAction haveWinnerAction =
  let state = initialState
  startAction state
  while not (haveWinnerAction state) do
    takeTurnAction state
  printfn "Player %i wins." state.WinningPlayer
The implementation of a chess game isn’t a particularly difficult affair either, the only real problem being the initialization and modification of internal state:
let chess() =
  let mutable turn = 0
  let mutable maxTurns = 10
  let state = {
    NumberOfPlayers = 2;
    CurrentPlayer = 0;
    WinningPlayer = -1;
  }
  let start state =
    printfn "Starting a game of chess with %i players" state.NumberOfPlayers
  let takeTurn state =
    printfn "Turn %i taken by player %i." turn state.CurrentPlayer
    state.CurrentPlayer <- (state.CurrentPlayer+1) % state.NumberOfPlayers
    turn <- turn + 1
    state.WinningPlayer <- state.CurrentPlayer
  let haveWinner state =
    turn = maxTurns
  runGame state start takeTurn haveWinner

So, just to recap, what we’re doing here is initializing all the functions required by the method/function right inside the outer function (this is completely legitimate in both C# and F#) and then pass each of those functions into runGame. Notice also that we have some mutable state that is used throughout the subfunction calls.

Overall, implementation of Template Method using functions instead of objects is quite possible if you’re prepared to introduce record types and mutability into your code. And sure, theoretically, you could rewrite this example and get rid of mutable state by essentially storing a snapshot of each game state and passing that in a recursive setting – this would effectively turn a Template Method into a kind of templated State pattern. Try it!

Summary

Unlike the Strategy, which uses composition and thus branches into static and dynamic variations, Template Method uses inheritance, and as a consequence, it can only be static, since there is no way to manipulate the inheritance characteristics of an object once it’s been constructed.

The only design decision in a Template Method is whether you want the methods used by the Template Method to be abstract or actually have a body, even if that body is empty. If you foresee some methods unnecessary for all inheritors, go ahead and make them empty/nonabstract so as to comply with ISP.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.150.59