User Tools

Site Tools


notes:misc:software_patterns

Software Patterns

A design pattern is a general solution to a common software design problem.

Common Patterns

Pattern's purpose Pattern
Creational Patterns
- Ensures a class has only one instance.
- Provides a global point of access to the single instance.
Singleton
- Provides a generalized way to create instances of an object. Factory Method
- Encapsulates a group of related factories. Abstract Factory
- Separates a complex object's construction from its representation. Builder
- Specifies an instance of a class that can be cloned to produce new objects. Prototype
Structural Patterns
- Translates the interface of a class into a different interface.
- Resolves incompatible interfaces.
- A single-component wrapper: a single adapter class maps to a single original class.
- The interface of the adapter and the original class may be different.
Adapter (aka Wrapper)
- Provides a one-to-one interface to another class.
- The proxy class and the original class have the same interface.
- A single-component wrapper: a single proxy class maps to a single original class.
- The interface of the proxy and the original class are the same.
Proxy
- Provides a simplified interface to a large collection of classes.
- Provides a unified higher-level interface to a set of interfaces in a subsystem.
Façade
- Decouples an abstraction from its implementation so that both can be changed independently.
- Simplifies class hierarchy.
Bridge
- Represents leaves and branches in a tree structure the same way. Composite
- Adds functionality to an existing object by wrapping it and exposing the same interface as the existing object. Decorator (aka Wrapper)
- Reduces storage costs for large number of objects.
- Uses sharing to support large numbers of fine-grained objects efficiently.
Flyweight
- Allows multiple instances of a class to be created where all the instances share the same static data. Monostate
Behavioral Patterns
- Allows a one-to-many notification of state changes between objects.
- Reduces direct dependencies between classes.
Observer
Gives more than one receiver object a chance to handle a request from a sender object. Chain of Responsibility
- Encapsulates a request as an object.
- An object-oriented replacement for callbacks.
Command
Specifies how to represent and evaluate sentences in a language. Interpreter
Loops through elements of a collection or a sequence. Iterator
Defines an object that encapsulates how a set of objects interact. Mediator
Captures an object's internal state so that it can be restored to the same state later. Memento
- Makes an object behave in distinct ways based on a current mode or state.
- Allows an object to appear to change its type when its internal state changes.
State
- Defines a family of algorithms, encapsulates each one, and makes them interchangeable at run-time.
- Decouples algorithm implementation from a class.
Strategy
Defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method
Represents an operation to be performed on the elements of an object structure. Visitor
Other Patterns and Techniques
Provides CRUD and/or CQRS (Command Query Responsibility Separation). Repository
Helps to create loosely coupled code. Dependency Injection

Dependency Injection

Dependency injection is a technique where an object is passed into a class (injected) instead of having the class create and store the object itself [Reddy 2011]

  • The primary purpose of dependency injection is to create loosely coupled code.
  • Dependency injection delegates the creation of services that an object consumes outside of the object.
  • The consumer does not have to decide which implementation to use. It only specifies interfaces to types it needs.
  • A container maps concrete types to interfaces, for example a FileDataService type may be mapped to IDataService.
  • The container creates instances of classes.
  • If an instance requires further dependecies (instances of other classes), the container will create them. This way the container resolves all dependencies in the chain of dependencies.

Constructor injection

  • Pass a dependency into a dependent class via constructor.
public class MainViewModel
{
    private IDataService dataService;
 
    public MainViewModel(IDataService dataService)
    {
        this.dataService= dataService;
    }
}

Property injection

  • Create a setter on the dependent class.
  • Use the setter to set the dependency.
  • Also called setter injection.
  • (+) You can change the dependency during run-time.
  • (-) You may try to use the object before a dependency is injected.

Example: You may want to assign a different DataService before calling the DataService's getter:

public class PersonRepository : IPersonRepository
{
    private IDataService dataService;
    public IDataService DataService
    {
        get
        {
            if (dataService == null)
                dataService = new FileDataService(); // a default repository
            return dataService;
        }
        set { dataService = value; }
    }
}

Interface injection

  • It is similar to the property injection. It uses a method rather than a property to do the injection.

Dependency injection frameworks

  • Unity
  • Autofac
  • Ninject
  • StructureMap
  • Castle.Windsor
  • Spring.NET

Repository

  • It is a low level layer that retrieves data from a WS or a DB. It abstracts details of data access.
  • Provides CRUD operations on models.
  • Provides caching if necessary.
  • Works with a single model or a family of related models (for example Invoices and InvoiceLines).
  • Returns model instances.
  • It may have a base class. The base class may, for example, create a connection to DB.
  • Implements an IRepository interface.

Related to the Repository is DataService:

  • Provides unit of functionality by combining calls to different repositories, for example a UserRepository and an InvoiceRepository.
  • Applies business rules (rather than implementing them in VM).
  • Implements an IDataService interface.

Adapter

  • Converts the interface of a class into another interface clients expect.
  • Allows classes to work together that couldn't otherwise due to incompatible interfaces.
  • Enforces consistency across your API – you can provide a unified interface for classes that have different interfaces.
  • Future-proof client implementations by having them depend on Adapter interfaces, rather than concrete classes directly.
  • May be used to wrap a library. This provides consistency in your API and shields the client from any future changes to the library.
  • May be used to transform data types such as input parameters of the methods.

Two ways of implementing adapters:

  • Composition (object adapters).
  • Inheritance (class adapters) – usually uses private inheritance to only expose the new interface.

Proxy

A Proxy pattern is useful to modify the behaviour of a class while preserving its interface especially when we are not able to modify the original class.

Two ways to implement a proxy:

  • Store a pointer to the original class in the proxy class. Then, redirect the proxy methods to the original class methods.
  • Share an abstract interface between the proxy and the original class.

Use cases for the proxy pattern:

  • Lazy instantiation of the original class's object.
  • Access control to the original class's methods (a permission layer).
  • Debug mode by inserting logging into the proxy methods.
  • Thread safety by adding mutex locking to the methods that are not thread safe.
  • As a transition step towards an adapter.

Decorator (aka Wrapper)

  • Extends functionality of an existing object dynamically at runtime. You can use dependency injection to pass the exiting object to the decorator.
  • Supports the Open/Close Principle: the decorated classes are open for extensions but closed for modifications.

How it works:

  • Component is a base class or an interface.
  • ConcreteComponent inherits from Component. It is a class we want to add functionality to.
  • Decorator inherits from Component. It contains a private member of type Component which represents an object being decorated.
  • Pass the Component you want to wrap in the Decorator's ctor.
  • Decorator is a base class for all ConcreteDecorators.
  • ConcreteDecorators inherit from Decorator.
  • ctor accepts Decorator as an arg and passes it to the base class's ctor i.e., Decorator.

Examples of usage:

  • Adding functionality to legacy systems.
  • Adding functionality to UI controls
    • XAML's ScrollViewer is a decorator - it adds the scrolling functionality to any child control.
    • XAML's ContentControl is a decorator - it adds control-like behaviour (e.g., double-click) to non-control elements such as a Grid or Image.
  • Adding functionality to sealed classes.

Singleton

  • Ensures that a class has one instance.
  • Provides a global point of access to that single instance.
  • Allows support for thread-safe access to the object's global state.
  • Useful for modelling singular resources such as the clipboard or the keyboard.
  • Useful for creating manager classes that provide a single point of access to multiple resources, such as a thread manager or an event manager.
  • Introduces global state and dependencies making your code difficult to unit test.
  • Consider introducing a “session” or “execution context” object that would hold all of the state of your application rather than representing the state with multiple singletons. It's sometimes called a Toolbox Singleton, where the application is the singleton, not the individual classes.

Use singletons to model objects that are truly singular such as system clipboard.

The implementation of the singleton pattern involves creating a class with a static method that returns the same instance of the class every time it is called. The static method (or a property) is often called GetInstance or Instance.

An implementation of the singleton pattern in C#:

public class MySingleton
{
    // Let the CLR manage locking, mutex, and volatile stuff.
    private static readonly MySingleton instance = new MySingleton();
 
    // An empty static ctor determines that the class will be initialized before its first use.
    static MySingleton() {}
 
    private MySingleton()
    {
        //...
    }
 
    public static MySingleton Instance { get { return instance; } }
 
    //...
}

In C++, a singleton class can use the following features:

  • Declare the default constructor to be private. This way, users won't be able to create new instances.
  • Declare a private copy constructor and a private assignment operator. This makes the singleton non-copyable.
  • Declare the destructor private. This prevents users from deleting the singleton instance.
  • Return a reference from the GetInstance method rather than a pointer. This way, users won't be able to delete the object.
class Singleton
{
public:
    // A static method that returns the reference to a singleton object.
    static Singleton& GetInstance();
 
private:
    // A private default constructor, a copy constructor, and an assignment operator.
    Singleton();
    ~Singleton();
    Singleton(const Singleton &);
    const Singleton &operator =(const Singleton &);
}
...
// usage
Singleton &obj = Singleton::GetInstance();

In C++, it's important to allocate the singleton instance properly. It is dangerous to initialize a singleton using a non-local static variable: “The relative order of initialization of non-local static objects in different translation units is undefined [Meyers, 2005]”. A non-local object is declared outside of a function. One way to initialize a singleton is to create a static variable inside a method of a class:

// - The instance will be allocated when ''%%GetInstance%%'' is first called.
// - This approach is not thread safe. There is a race condition in the initialization of the Singleton static.
//   The instance could be created twice or it could be used by one thread before it is initialized by another
//   thread.
Singleton& Singleton::GetInstance()
{
    static Singleton instance;
    return instance;
}

A thread safe method uses mutex lock around the code that exhibits the race condition:

static Mutex s_mutex;
 
// This solution may be expensive because the lock is acquired every time the method is called. If there is
// a performance problem, you can call this method once per thread and cache the result.
Singleton& Singleton::GetInstance()
{
    ScopedLock lock(&sMutex); // ScopedLock unlocks the mutex on function exit
 
    static Singleton instance;
    return instance;
}

A high-performance solution uses the Double Checked Locking Pattern (DCLP):

static Mutex s_Mutex;
 
Singleton& Singleton::GetInstance()
{
    static Singleton* instance = NULL;
 
    if (!instance)      // check #1
    {
        ScopedLock lock(&s_Mutex);
 
        if (!instance)  // check #2
        {
            instance = new Singleton();
        }
    }
 
    return *instance;
}

Another approach is to avoid the lazy instantiation all together and instead initialize the singleton on startup. There are two mechanisms that allow this approach:

  • Static initialization. Static initializers are called before main(), where the program is still single threaded. You can create a singleton instance as part of a static initializer and avoid the need for mutex locking.
// Add the following static initialization call to your singleton implementation to ensure that the singleton
// instance is created before main() is called.
static Singleton& obj = Singleton::GetInstance();
  • Explicit API initialization. Add an initialization routine for your library. This allows you to remove mutex locks from GetInstance and instead instantiate the singleton as part of the library initialization routine, placing the mutex locks at this point. A downside of this approach is that it requires users to explicitly initialize your library.
static Mutex s_Mutex;
 
void InitializeApi()
{
    ScopedLock lock(&s_Mutex);
 
    Singleton::GetInstance();
}

Question: What are the alternatives to the Singleton pattern?

Answer: The alternatives to the Singleton pattern include:

  • dependency injection
  • the Monostate pattern
  • a session context

Factory Method

An example of a factory method CreateMesh. It creates different types of meshes: CubeMesh, SphereMesh, CylinderMesh, etc. All these meshes implement the IMesh interface.

IMesh CreateMesh(string meshName);
IMesh cube = CreateMesh("cube");
IMesh sphere = CreateMesh("sphere");

An example of an extendable factory (a plug-in):

  • MeshFactory.Register(name, callback)
  • MeshFactory.Create(name)
  • MeshFactory.Unregister(name)
// Register a user-defined mesh. 
// The callback parameter is a "create" (factory) method that knows how to create the new mesh. MeshFactory
// keeps a map of mesh names and corresponding callbacks. It may also have some built-in meshes and their 
// callbacks already registered in the map.
MeshFactory.Register("new_mesh", callback)
 
// Find the callback method for "new_mesh" and use it to create an instance of this mesh.
IMesh newMesh = Factory.Create("new_mesh");
 
// Remove a callback for "new_mesh" from the map.
Factory.Unregister("new_mesh");

Chain of Responsibility

  • Sender is aware of only one receiver.
  • Each receiver is only aware of the next receiver.
  • Receivers process the message or send it down the chain.
  • The sender does not know who received the message.
  • The first receiver to handle the message terminates the chain.
  • The order of the receiver list matters.

Composite

  • Represent leaves and branches in a tree structure the same way. This way there is no distiction between the two.
  • Leaves and branches implement the same interface (Component interface).
  • We can traverse the entire tree automatically by invoking the interface method(s) on its root. It does not matter how many levels the tree has.

Flyweight

  • Reduces storage costs for large number of objects.
  • Stores state as intrinsic state and extrinsic state.
    • intrinsic state: stored in an object; does not depend on the context; it's shareable.
    • extrinsic state: supplied from outside; depends on the context.
  • Uses a factory to create flyweight instances. The factory maintans a pool of flyweights of various types.
  • Flyweight instances do not maintain identity i.e., you are not able to refer to specific flyweight instances.

.NET uses the flyweight pattern for string interning: keeping only one copy of the same string despite of being assigned to different string variables. You can force interning by using the String.Intern method.

Leaf objects in a hierarchy maintained using the Composite pattern are often flyweights.

Tester / Doer

Tester tests if the operation is legal; returns true or false. Doer performs the operation. Tester / doer pattern is not thread-safe.

Observer

Observer allows conceptually unrelated classes to communicate by allowing one class (the observer) to register for notification from another class (the subject). It is used for designing loosely coupled APIs.

notes/misc/software_patterns.txt · Last modified: 2021/09/22 by leszek