Software Design Patterns: Miscellaneous

·12 min read·Graham Mace
Software Design Patterns: Miscellaneous

As ever, the source code for these patterns is available in the accompanying design-patterns GitHub repository:

maceage/design-patterns

These are patterns I’ve discovered throughout my career that are not officially in the Gang of Four design patterns but I thought they deserved a mention anyway.

Model-View-Presenter pattern

The Model-View-Presenter (MVP) pattern is a UI design pattern that separates the presentation logic from the business logic, making it easier to test and maintain. It divides an application into three parts: the Model, which holds the domain data; the View, which is responsible solely for rendering and capturing user input; and the Presenter, which acts as the intermediary between the two – retrieving data, applying presentation logic, and telling the View what to display.

In this example, the FoodPresenter acts as an intermediary between the IFoodView and the FoodListViewModel, handling user input and updating the view accordingly:

public interface IFoodView
{
    void Show(FoodListViewModel viewModel);
}

For this project, a FoodListViewModelMapper is used to map the FoodListViewModel from the Food data. This mapper is responsible for creating the initial state of the FoodListViewModel and mapping the Food data to FoodViewModel objects:

public static class FoodListViewModelMapper
{
    public static FoodListViewModel MapFoodListViewModel()
    {
        return new FoodListViewModel
        {
            IsSorted = false,
            Food = MapFoodList()
        };
    }

    private static IList<FoodViewModel> MapFoodList()
    {
        var foodList = GetFood();

        return foodList.Select(MapFood).ToList();
    }

    private static FoodViewModel MapFood(Food food)
    {
        return new FoodViewModel { Id = food.Id, Name = food.Name };
    }

    private static IList<Food> GetFood()
    {
        return new List<Food>
        {
            new Food { Name = "Sushi" },
            new Food { Name = "Cake" },
            new Food { Name = "Sandwich" },
            new Food { Name = "Cookie" },
            new Food { Name = "Banana" }
        };
    }
}

The FoodListViewModel simply contains a list of FoodViewModel instances and a boolean that indicates whether the list is sorted:

public class FoodListViewModel
{
    public IEnumerable<FoodViewModel> Food { get; set; }

    public bool IsSorted { get; set; }
}

The FoodPresenter is then responsible for initialising the view with the initial state of the FoodListViewModel. It does this by calling the GetViewModel method to retrieve the view model and then passing it to the Show method of the IFoodView interface:

public class FoodPresenter
{
    private readonly IFoodView _view;

    public FoodPresenter(IFoodView view)
    {
        _view = view;
    }

    public void Init()
    {
        var foodListViewModel = GetViewModel();

        _view.Show(foodListViewModel);
    }

    public void Sort(bool isSorted)
    {
        var vm = GetViewModel();

        if (!isSorted)
        {
            vm.Food = vm.Food.OrderBy(f => f.Name);
        }

        vm.IsSorted = !isSorted;

        _view.Show(vm);
    }

    private static FoodListViewModel GetViewModel()
    {
        return FoodListViewModelMapper.MapFoodListViewModel();
    }
}

Using a standard WinForms app to demonstrate the Model-View-Presenter pattern, MainForm implements the IFoodView interface and the corresponding Show() method. When the SortButton is clicked, the SortButton_Click() handler is invoked which calls the FoodPresenter.Sort() method to process the command:

public partial class MainForm : Form, IFoodView
{
    private readonly FoodPresenter _presenter;

    public MainForm()
    {
        InitializeComponent();

        _presenter = new FoodPresenter(this);
        _presenter.Init();
    }

    public void Show(FoodListViewModel viewModel)
    {
        SortButton.Tag = viewModel.IsSorted;

        FoodListBox.DataSource = new List<FoodViewModel>(viewModel.Food);
        FoodListBox.DisplayMember = "Name";
        FoodListBox.ValueMember = "Id";
    }

    private void SortButton_Click(object sender, System.EventArgs e)
    {
        _presenter.Sort((bool)SortButton.Tag);
    }
}

The Model-View-Presenter pattern offers several benefits:

  • Separation of concerns – Presentation logic lives in the presenter, keeping the view dumb and the business logic in the model, which makes each part easier to reason about.
  • Improved testability – Because the presenter only depends on an interface for the view, presentation logic can be unit tested without instantiating any UI controls.
  • View interchangeability – Since the view is defined purely by an interface, the same presenter can drive a WinForms view, a web view, or a mock implementation.
  • Reduced duplication – Common presentation logic such as sorting or filtering can be centralised in one presenter rather than scattered across event handlers.

By moving all decision-making out of the view and into a presenter that talks to an abstraction, MVP keeps UI code tidy, testable, and reusable across front ends.

Model-View-ViewModel pattern

The Model-View-ViewModel (MVVM) pattern is slightly different to the Model-View-Presenter pattern in that the ViewModel holds the model that’s being displayed and processes commands that are invoked on the model. Rather than the presenter explicitly pushing state at the view, the ViewModel publishes changes to its own properties, and the view keeps itself up to date through data binding. This makes MVVM the pattern of choice for binding-driven frameworks such as WPF.

In this example, a Car class implements the INotifyPropertyChanged interface so that interested parties - in this case the data bindings on the form - are notified whenever a property changes:

public class Car : INotifyPropertyChanged
{
    private string _make;
    private string _model;
    private DateTime _lastUpdated;

    public event PropertyChangedEventHandler PropertyChanged;

    public string Make
    {
        get
        {
            return _make;
        }

        set
        {
            _make = value;
            OnPropertyChanged("Make");
        }
    }

    public string Model
    {
        get
        {
            return _model;
        }

        set
        {
            _model = value;
            OnPropertyChanged("Model");
        }
    }

    public DateTime LastUpdated
    {
        get
        {
            return _lastUpdated;
        }

        set
        {
            _lastUpdated = value;
            OnPropertyChanged("LastUpdated");
        }
    }

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

A SaveCarCommand implements the ICommand interface from the System.Windows.Input namespace by providing functionality for the CanExecute() and Execute() methods:

public class SaveCarCommand : ICommand
{
    private readonly Action _executeMethod;

    public SaveCarCommand(Action executeAction)
    {
        _executeMethod = executeAction;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _executeMethod.Invoke();
    }
}

This is then all wired together in the MainFormViewModel by implementing the INotifyPropertyChanged interface as well as holding an instance of the Car class:

public class MainFormViewModel : INotifyPropertyChanged
{
    private Car _modelCar;
    private SaveCarCommand _saveCarCommand;

    public MainFormViewModel()
    {
        InitializeCommands();
        LoadCar();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public Car ModelCar
    {
        get
        {
            return _modelCar;
        }

        set
        {
            _modelCar = value;
            OnPropertyChanged("ModelCar");
        }
    }

    public SaveCarCommand SaveCarCommand
    {
        get
        {
            return _saveCarCommand;
        }

        set
        {
            _saveCarCommand = value;
            OnPropertyChanged("SaveCarCommand");
        }
    }

    private void LoadCar()
    {
        ModelCar = new Car { Make = "BMW", Model = "M2", };
    }

    private void InitializeCommands()
    {
        SaveCarCommand = new SaveCarCommand(UpdateCar);
    }

    private void UpdateCar()
    {
        ModelCar.LastUpdated = DateTime.Now;
    }

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Finally, this is all demonstrated in another WinForms app by adding to the DataBindings property by creating a Binding for each property to change on the Control based on the property that’s changed in the Car model. When the SaveButton is clicked, the SaveButton_Click method is invoked which calls the respective SaveCarCommand on the MainFormViewModel instance, as set up on the SaveButton.Tag property when the form is loaded:

public partial class MainForm : Form
{
    private MainFormViewModel _mainFormViewModel;
    
    public MainForm()
    {
        InitializeComponent();
    }
    
    private void MainForm_Load(object sender, EventArgs e)
    {
        _mainFormViewModel = new MainFormViewModel();
    
        BindingSource bindingSource = new BindingSource(_mainFormViewModel, null);
    
        MakeTextBox.DataBindings.Add("Text", bindingSource, "ModelCar.Make");
        ModelTextBox.DataBindings.Add("Text", bindingSource, "ModelCar.Model");
        LastUpdatedLabel.DataBindings.Add("Text", bindingSource, "ModelCar.LastUpdated");
    
        SaveButton.Tag = _mainFormViewModel.SaveCarCommand;
    }
    
    private void SaveButton_Click(object sender, EventArgs e)
    {
        ICommand command = ((Control)sender).Tag as ICommand;
    
        if (command != null)
        {
            command.Execute(null);
        }
    }
}

The Model-View-ViewModel pattern offers several benefits:

  • Loose coupling between view and logic – The view knows only about bindable properties and commands, never about the mechanics behind them, so both halves can evolve independently.
  • Improved testability – Commands and property-change behaviour live in plain classes that can be exercised by unit tests without any UI harness.
  • Less plumbing code in the view – Data binding eliminates the manual “read the control, validate, push to the model” wiring that event-handler-heavy approaches require.
  • Composable commands – Encapsulating actions as ICommand implementations makes them reusable across multiple views and easy to enable or disable via CanExecute().

By letting the view observe the ViewModel rather than be driven by it, MVVM turns UI state into data that can be bound, tested, and reused with minimal ceremony.

Service Locator pattern

The Service Locator pattern is a design pattern that provides a centralised location for getting services or dependencies in an application. It acts as a registry or lookup table that allows components to locate and retrieve services without having to know the specific implementation details of those services.

While probably considered an antipattern which has been superseded by the Dependency Injection pattern, Service Locator is still worth a mention as it is very similar:

In this example an ILogger interface defines a single LogMessage method with a string parameter:

public interface ILogger
{
    void LogMessage(string message);
}

This is then implemented by a Logger class:

public class Logger : ILogger
{
    public void LogMessage(string message)
    {
        Console.WriteLine(message);
    }
}

A ServiceLocator class is then used to register and retrieve services based on the interface and the corresponding implementation:

public class ServiceLocator
{
    private static readonly Hashtable _services = new Hashtable();

    public static void AddService<T>(T service)
    {
        _services.Add(typeof(T).Name, service);
    }

    public static void AddService<T>(string name, T service)
    {
        _services.Add(name, service);
    }

    public static T GetService<T>()
    {
        return (T)GetService<T>(typeof(T).Name);
    }

    public static object GetService<T>(string serviceName)
    {
        return (T)_services[serviceName];
    }
}

A Consumer class then has a parameterless constructor with a reference to the ServiceLocator class for retrieving the ILogger concrete implementation:

public class Consumer
{
    private readonly ILogger _logger;

    public Consumer()
    {
        _logger = ServiceLocator.GetService<ILogger>();
    }

    public void DoSomething(int numberOfTimes)
    {
        for (int i = 0; i < numberOfTimes; i++)
        {
            _logger.LogMessage("Doing something!");
        }
    } 
}

To demonstrate this pattern at runtime using a console application, a RegisterServices method is used to register the ILogger concrete implementation with the ServiceLocator class:

public class Program
{
    public static void Main(string[] args)
    {
        RegisterServices();

        Consumer consumer = new Consumer();
        consumer.DoSomething(5);

        Console.ReadLine();
    }

    private static void RegisterServices()
    {
        ILogger logger = new Logger();
        ServiceLocator.AddService(logger);
    }
}

When the Consumer class is created, the ServiceLocator is invoked via the constructor and the respective concrete implementation is retrieved.

The reason why Service Locator is considered an antipattern is due to the reliance on the ServiceLocator class in the constructor - whereas the Dependency Injection pattern works by injecting the dependencies into the class through its constructor or properties, making them visible in the class signature rather than hidden inside its body.

The Service Locator pattern does nonetheless offer some benefits, particularly in constrained scenarios:

  • Centralised dependency resolution – Every service is registered and retrieved from one well-known place, so there is a single composition point for the whole application.
  • Decoupling from concrete types – Consumers depend only on interfaces and remain ignorant of which implementation sits behind them.
  • Low ceremony – For small applications or legacy codebases, a locator avoids the upfront cost of introducing a full DI container or reworking constructors.
  • Deferred resolution – Dependencies can be looked up lazily at the point of use rather than resolved eagerly at construction time.

Although Service Locator trades a little transparency for convenience - which is precisely why dependency injection is now preferred - it remains a useful pattern to recognise, both in older codebases and in the parts of modern frameworks where a locator still lurks behind the scenes.

Unit of Work pattern

The Unit of Work pattern is a design pattern that provides a way to manage a collection of objects that have been modified and need to be persisted to a database. It is often used in object-relational mapping (ORM) frameworks to manage the state of objects and ensure that changes are persisted to the database in a consistent and efficient manner.

To demonstrate this we have a Database class that has two methods - Insert() and Update() - which both take an IEntity as a parameter:

public static class Database
{
    public static void Insert(IEntity entity)
    {
        Console.WriteLine("Inserting entity {0} into database...", entity);
    }

    public static void Update(IEntity entity)
    {
        Console.WriteLine("Updating entity {0} in database...", entity);
    }
}

The IEntity interface itself is defined as follows:

public interface IEntity
{
    Guid Id { get; set; }

    void Insert();
    void Update();
    IList<IEntity> Load();
}

Implementing this interface is a Customer class that provides concrete implementations for these interface methods:

public class Customer : IEntity
{
    public Customer()
    {
        Id = Guid.NewGuid();
    }

    public Guid Id { get; set; }

    public string FirstName { get; set; }
    public string LastName { get; set; }

    public void Insert()
    {
        Database.Insert(this);
    }

    public void Update()
    {
        Database.Update(this);
    }

    public IList<IEntity> Load()
    {
        return new List<IEntity>
        {
            new Customer { FirstName = "Bill", LastName = "Gates" },
            new Customer { FirstName = "Steve", LastName = "Jobs" },
            new Customer { FirstName = "Elon", LastName = "Musk" }
        };
    }

    public override string ToString()
    {
        return string.Format("{0} {1}", FirstName, LastName);
    }
}

Finally, the UnitOfWork class exposes Add(), Load(), Commit(), ListAdded() and ListChanged() methods. Note that the Commit() method uses a TransactionScope instance to ensure the code is run transactionally. In other words, work inside the block is committed only if no exception happens, otherwise it is rolled back:

public class UnitOfWork
{
    private readonly IList<IEntity> _added = new List<IEntity>();
    private IList<IEntity> _changed = new List<IEntity>();

    public void Add(IEntity entity)
    {
        _added.Add(entity);
    }

    public void Load(IEntity entity)
    {
        _changed = entity.Load() as List<IEntity>;
    }

    public void Commit()
    {
        using (TransactionScope transactionScope = new TransactionScope())
        {
            foreach (IEntity entity in _changed)
            {
                entity.Update();
            }

            foreach (IEntity entity in _added)
            {
                entity.Insert();
            }

            transactionScope.Complete();
        }
    }

    public void ListAdded()
    {
        foreach (IEntity entity in _added)
        {
            Console.WriteLine("Entity '{0}' added", entity);
        }
    }

    public void ListChanged()
    {
        foreach (IEntity entity in _changed)
        {
            Console.WriteLine("Entity '{0}' changed", entity);
        }
    }
}

A console application demonstrates this by instantiating a UnitOfWork instance and performing the operations on it:

public static void Main(string[] args)
{
    UnitOfWork unitOfWork = new UnitOfWork();

    Customer customer = new Customer();
    unitOfWork.Load(customer);

    Customer firstCustomer = new Customer { FirstName = "Steve", LastName = "Ballmer" };
    Customer secondCustomer = new Customer { FirstName = "Steve", LastName = "Wozniak" };

    unitOfWork.Add(firstCustomer);
    unitOfWork.Add(secondCustomer);

    Console.WriteLine("Changed entities...");
    unitOfWork.ListChanged();

    Console.WriteLine("Added entities...");
    unitOfWork.ListAdded();

    Console.WriteLine("Committing changes...");
    unitOfWork.Commit();

    Console.ReadLine();
}

The Unit of Work pattern offers several benefits:

  • Transactional consistency – Changes are accumulated and committed as one atomic unit, so either everything persists or nothing does.
  • Efficient batched persistence – Multiple inserts and updates are flushed together in a single round of operations rather than one database trip per change.
  • Change tracking in one place – The unit of work maintains the state of added and changed entities centrally, keeping persistence bookkeeping out of the domain objects themselves.
  • Clear commit boundary – The explicit Commit() call makes the moment of persistence visible and controllable in application code.

By deferring persistence until an explicit commit, the Unit of Work pattern keeps database interactions consistent, efficient, and easy to reason about – which is exactly why ORM frameworks like Entity Framework build it into their DbContext.

Wrapping Up

That concludes this look at four more patterns that sit outside the official Gang of Four catalogue. Although they emerged from different corners of software - UI architecture in the case of MVP and MVVM, dependency management for Service Locator, and data persistence for Unit of Work – they all serve the same underlying goal: separating what an application does from the mechanics of how it does it.

Recognising them (alongside when they fall short, as with Service Locator) makes it easier to read unfamiliar codebases and to reach for the right abstraction when architecting your own.