The Template Method Design Pattern holds the general algorithm in a base class and allows the subclasses to implement some certain steps of that algorithm. This pattern can be used anytime you have a few similar algorithms that differ by some steps.

Imagine that we've got the following requirments from customer:

The system needs to validate text files and images.
Validation for text files:
- User should see "The proposed file content is empty." error message at attempt to upload empty file.
- User should see "The file size is greater than 1 MB" error message for text files at attempt to upload file greater than 1 MB.
Validation for images:
- User should see "The proposed file content is empty." error message at attempt to upload empty file.
- User should see "The file size is less than 1 MB" error message at attempt to upload file less than 1 MB.
- User should see "The file size is greater than 10 MB" error message at attempt to upload file greater than 10 MB.

This task looks like a good candidate to be implemented using Template Method pattern. Let's begin!

Step #1: Preparing ValidationResult class

public class ValidationResult
{
    public ValidationResult()
    {
        IsSuccessful = true;
    }

    public ValidationResult(string error)
    {
        ErrorMessage = error;
        IsSuccessful = false;
    }

    public bool IsSuccessful { get; set; }
    public string ErrorMessage { get; set; }
}

Nothing interesting here. We've defined ValidationResult class that will represent the result of the validation. It can be successful or unsuccessful with some error message.

Step #2: Implementing BaseValidator class

public abstract class BaseValidator
{
    protected readonly FileInfo _fileInfo;

    public BaseValidator(FileInfo fileInfo)
    {        
        _fileInfo = fileInfo;           
    }

    public ValidationResult Validate()
    {
        var validateActions = new List<Func<ValidationResult>>()
        {
            IsFileEmpty,
            IsFileSizeValid
        };

        foreach (var validateAction in validateActions)
        {
            ValidationResult result = validateAction();
            if (!result.IsSuccessful)
            {
                return result;
            }
        }

        return new ValidationResult();
    }

    private ValidationResult IsFileEmpty()
    {
        return _fileInfo.Length > 0 ? 
           new ValidationResult() : 
           new ValidationResult("The proposed file content is empty.");
    }

    protected abstract ValidationResult IsFileSizeValid();
}

Validate method (our template method) is calling IsFileEmpty and IsFileSizeValid one by one. In the real world, there would be much more other methods (IsFileNameValid, IsExtensionValid etc.) but in this example we will proceed only with these two ones.

IsFileEmpty must work in the same way for all file types, because it simply checks whether a file has at least one byte inside. That's why this method is placed in a base class. But IsFileSizeValid method (certain step of template method that will be implemented by subclass) must behave differently for every file type. That's why it's abstract. We allow every future subclass to implement the method in its own way.

Step #3: Implementing TextFileValidator class

public class TextFileValidator : BaseValidator
{
    public TextFileValidator(FileInfo fileInfo) : base(fileInfo) { }

    private const int MAX_FILE_SIZE_BYTES = 1048576;

    protected override ValidationResult IsFileSizeValid()
    {
        return _fileInfo.Length <= MAX_FILE_SIZE_BYTES ?
            new ValidationResult() :
            new ValidationResult("The file size is greater than 1 MB");
    }
}

We've defined TextFileValidator class and implemented IsFileSizeValid according to requirements we have.

Step #4: Implementing ImageFileValidator class

public class ImageFileValidator : BaseValidator
{
    public ImageFileValidator(FileInfo fileInfo) : base(fileInfo) { }

    private const int MIN_FILE_SIZE_BYTES = 1048576;
    private const int MAX_FILE_SIZE_BYTES = 10485760;
    
    protected override ValidationResult IsFileSizeValid()
    {
        ValidationResult result = null;

        if (_fileInfo.Length < MIN_FILE_SIZE_BYTES)
        {
            result = new ValidationResult("The file size is less than 1 MB");
        }
        else if (_fileInfo.Length > MAX_FILE_SIZE_BYTES)
        {
            result = new ValidationResult("The file size is greater than 1 MB");
        }
        else
        {
            result = new ValidationResult();
        }

        return result;
    }
}

We've also defined ImageFileValidator class and implemented IsFileSizeValid. The method has a one more additional condition to check minimum file size.

Step #5: Usage

class Program
{
    static void Main(string[] args)
    {
        FileInfo fileInfo = new FileInfo("test.txt");
        BaseValidator validator = new TextFileValidator(fileInfo);
        ValidationResult result = validator.Validate();
        if (result.IsSuccessful)
        {
            Console.WriteLine("The file is valid.");
        }
        else
        {
            Console.WriteLine(result.ErrorMessage);
        }
    }
}

Usage is quite simple. Just don't forget to put an appropriate file in your Debug folder.

In our particular example Template Method helped us to avoid code duplication.