Skip to content

Instantly share code, notes, and snippets.

@wi7a1ian
Last active February 1, 2024 18:06
Show Gist options
  • Save wi7a1ian/e4867e2cfd2cf7678b178866fe86e13e to your computer and use it in GitHub Desktop.
Save wi7a1ian/e4867e2cfd2cf7678b178866fe86e13e to your computer and use it in GitHub Desktop.
Design patterns in object-oriented programming

Any fool can write code that a computer can understand. Good programmers write code that humans can understand. - Martin Fowler

Legend:

  • πŸ”Œ promotes decoupled architecture
  • πŸŽ“ synergy with SOLID principles (ocp, srp, dip...)
  • ▢️ runtime focused (i.e: promotes reconfiguration)
  • πŸ›  compiletime focused
  • πŸ“ configurable once (during initialization, usually from a config file)
  • πŸ†• creational pattern
  • β›“ structural pattern
  • πŸ…±οΈ behavioral pattern
  • πŸ’€ controls death of an object
  • 🌟 top pattern according to my experience
  • ♻️ often using when refactoring legacy code

SOLID

understand it and then follow it at all costs... patters will come later...

Creational Patterns

creation of entities

Factory method

πŸ†•πŸ”ŒπŸ› β™»οΈ code | code | wiki
When: We need a common operation that creates an object (single) without specyfying exact class object that will be created. Subclasses should decide which class to instantiate. When we need OOP replacement for switch duplication.
Usage:

BaseUiFactory uiFactory = new MultithreadedUiFactory();
...
IWindow mainWindow = uiFactory.GetWindow(Environment.OsType);
mainWindow.Show(800, 600, "Factory method sample v1.0");

Keywords: switch, harmful new, factory class, SOLID open/closed, refactoring
Synergy with: template method (called within 'em), object pool
Competing with: abstract factory, builder

Abstract factory

πŸ†•πŸ”Œ code | code | code | wiki
When: A class needs to be independent of how families of objects it requires are created.
Usage:

class GameEngine {
  ...
  GameEngine(IGuiFactory uiFactory, ...)
  {
    IGuiSplashscreen splash = uiFactory.CreateSplashscreen();
    splash.Show("Loading...", 5s);
    
    IGuiMainWindow window = uiFactory.CreateMainWindow(Mode.NoFrame);
    m_mainThread = window.Show();
  }
  ...
}
var engine = new GameEngine(new WpfGuiFactory(...), ...);

Keywords: IFactory IProduct, avoid new
Synergy with: dependency injection
Competing with: factory method

Builder

πŸ†•πŸ”Œβ™»οΈ code | code | code | wiki
When: We need to separate an construction of complex object from its representation allowing same construction process to create various representations. Provide control over steps of construction process.
Usage:

IWindowBuilder builder = ... some concrete builder;
...
builder.SetTitle("Yo").ForOs(OsType.Windows); // note: method chaining
if(isWpfEnv) builder.AttachTo(m_mainProcess);
SetSizeByDeviceDimensions(builder); // i.e dimensions 800x600
builder.ConfigureLogging(logging => { logging.AddFile("Logs/{Date}.log"); });
...
IWindow mainWindow = builder.Create();
mainWindow.Show();

Keywords: builder, complex creation, complex constructor, refactoring
Synergy with: method chaining, composite (built by builder)
Competing with: dependency injection

Dependency injection

πŸ†•πŸ”ŒπŸŽ“πŸ’€πŸ“πŸŒŸ code | wiki
When: We want classes to remove all knowledge of a concrete implementations that they needs to use. When no client code has to be changed simply because an object it depends on needs to be changed to a different one. When we want to specify the way objects are created in separate configuration files. When we want to do concurrent or independent development, with two developers writing classes that use each other, while only needing to know the interface the classes will communicate through. When we want a special (injector) object to resolve the dependency tree and return fully resolved objects. When we need a plugin system. When we need independent entities that are easier to unit test in isolation using mock objects.
Usage:

class SdlEngine : IGameEngine {
  private readonly IMyDependency m_myDependency;
  Engine(IMyDependency dep, ILogger<Engine> logger, IConfiguration config) {
    m_myDependency = dep;
    ...
  }
  public ISomeOptionalService SomeService { get; set; }
}
...
services.AddSingleton<typeof(ILogger<T>), typeof(Logger<T>)>();
services.AddScoped<IConfiguration, JsonFileConfig>();
services.AddTransient<IMyDependency>( sp => new SomeDynamicDependency() );
IGameEngine engine = services.Resolve<SdlEngine>();

Keywords: di container, ioc inversion of control, SOLID soc, ctor vs setter, plugins, plug-ins, unit testing
Synergy with: dependency injection container, registry
Competing with: service locator (injects implementations instead of a factory that resolves dependencies at runtime), abstract factory (doesn't have to delegate instantiation to a factory object), builder, template method (also an ioc but with composition over inheritance), strategy (injects dependencies during init instead of allowing interchangeable dependencies at runtime)

Service locator

πŸ†•πŸ”ŒπŸŽ“β–ΆοΈπŸ“ code | code | code | wiki
When: Same as in DI but we want to inject a factory that resolves dependencies at runtime. When we want to allow dependencies to be changed at runtime without re-compiling the application. When application should optimize itself at runtime by selectively adding and removing items from the service locator.
Usage:

class SdlEngine : IGameEngine {
  private readonly IServiceLocator m_services;
  Engine(IServiceLocator services) {
    m_services = services;
  }
  void Run() {
    IScene scene = m_services.Get<IScene>("layered");
    while(true) {
      IFrame engine = m_services.Get<IFrame>(scene, time);
      ...
    }
  }
}
...
services.AddSingleton<IScene, FastScene>();
services.AddSingleton<IScene, LayeredScene>("layered");
services.AddTransient<IFrame, FastFrame>();
IGameEngine engine = nw SdlEngine(services);

Keywords: ioc inversion of control, SOLID soc, inside a loop
Synergy with: ?
Competing with: dependency injection

Lazy initialization

πŸ†• code | code | code | wiki
When: To delay the creation of a "heavy" object, the calculation of a value or some other expensive process until the first time it is used.
Usage:

Lazy<IGameEngine> lazyEngine = new Lazy<Engine>();
...
IGameEngine engine = lazyEngine.Value;

Keywords: delay, virtual proxy, ghost, lazy loading, lazy factory, value holder, performance
Synergy with: factory method, multiton, proxy (often uses lazy), singleton (uses lazy)
Competing with: ?

Object pool

πŸ†•πŸ’€πŸ“ code | code | wiki
When: It is necessary to work with a large number of objects that are particularly expensive to instantiate and each object is only needed for a short period of time. Rather than allocating and destroying objects on demand, we care for performance and we want a set of recycling objects kept ready to use - aka "pool".
Usage:

var task = Task.Run(() => { ... }); // acquire expensive thread from the thread pool
task.Wait(); // after that, lets the thread get back to the thread pool

Keywords: object pooling, memory pool, thread pool, connection pool, recycling objects, performance, minimal memory footprints, acquire release
Synergy with: singleton (is often a singleton), factory method, dispose
Competing with: ?

Singleton

πŸ†•πŸ’€ code | wiki
When: When exactly one globally available object instance is needed to coordinate actions across the system. When we want to restrict the instantiation of a class to one object. When implementing facade objects since usually only one facade object is required.
Usage:

GameEngine.Instance.NextFrame();
...
GameEngine.Instance.SetAbsolutePosition(car, 0, 0);
...
GameEngine.Instance.Draw();

Keywords: anti-pattern, one instance, global state, static, thread safety problem
Synergy with: facade, factory method, abstract-factory, builder, prototype, lazy initialization
Competing with: multiton

Multiton

πŸ†•πŸ’€ code | wiki
When: We need N x singleton to manage a map of named instances (as key-value pairs) with global point of access.
Usage:

var foreground = GameScene.GetInstance(LayerType.Foreground);
var background = GameScene.GetInstance(LayerType.Background);

Keywords: anti-pattern, global state, static
Synergy with: lazy initialization
Competing with: singleton

Dispose

πŸ†•πŸ’€ code | code | cpde | wiki
When: We want to signal for explicit call to a method which releases any resources the object is holding onto.
Usage:

using (GameResource resource = GetGameResource()) {
    ...
}

Keywords: resource management, close, dispose, free, cleanup, release, RAII
Synergy with: object pool
Competing with: ?

Prototype

πŸ†•β–ΆοΈ code | code | wiki
When: When the type of objects to create is determined at runtime by prototypical instance, which is cloned to produce new objects.
Usage:

bullet.SetPosition(1337, 6);
bullet.Rotate(90deg);
...
var followingBullet = bullet.Clone();

Keywords: harmful new, proto, clone, true copy, deep copy, shallow copy, performance, minimal memory footprints
Synergy with: factory method, registry (aka prototype manager)
Competing with: ?

Resource acquisition is initialization (RAII)

πŸ†•πŸ’€πŸŒŸ code | wiki
When: We want to ensure that resources are properly released by tying them to the lifespan of an object.
Usage:

std::mutex m;

void RedrawStage(...) {
  std::lock_guard<std::mutex> lk(m); // RAII: mutex acquisition is initialization
  auto buffer = std::make_unique<uint8_t[]>(stage.Width*stage.Height); // RAII: memory acquisition is initialization
  ...
} // deallocation happens here even if the function throws

Keywords: c++, rust, RAII, exception safety, Constructor Acquires Destructor Releases (CADRe), Scope-based Resource Management (SBRM)
Synergy with: dispose
Competing with: ?


Structural Patterns

relationship between entities, structure related logic is being internal to the structure - the structure gets affected by various changes and executes some actions as a consequence

Adapter

β›“πŸ”ŒπŸŽ“β™»οΈ code | code | wiki
When: We want to allow classes with incompatible interfaces to work together. When we have to convert the interface of a class into another interface clients expect.
Usage:

ISdlLayer sdlLayer = new SdlLayer();
IGameLayer adapted = new My2DGameLayer(sdlLayer);
adapted.Draw(scene);

Keywords: wrapper, translator, refactoring
Synergy with: dependency injection, delegation (may be implemented as)
Competing with: decorator (adapt vs add behavior), facade (adapts to one interface vs simplifies many), delegation (different intent)

Decorator

β›“πŸ”ŒπŸŽ“β–ΆοΈπŸŒŸβ™»οΈ code | wiki
When: We want to attach additional responsibilities to an object, dynamically at run-time, keeping the same interface. When we want to stack multiple responsibilities on top of each other (via wrapping).
Usage:

IGameLayer basic = new Layer2D();
basic.Add(bullet);
IGameLayer rotated = new RotatedLayer(basic, 90deg);
IGameLayer transmuted = new TransmutedLayer(rotated, [1,2,2]);
transmuted.Draw(scene);

Keywords: subclassing alternative, SOLID srp opc, mixins, refactoring
Synergy with: dependency injection, forwarding (may be implemented as)
Competing with: chain of responsibility (exactly one of the classes handles the request, not all), adapter (change responsibility vs change interface), mixin

Bridge

β›“πŸ”Œβ–ΆοΈβ™»οΈ code | wiki
When: An abstraction and its characteristics should be defined as separate class hierarchies, thus extended independently, achieving composition over inheritance. We need two layers of abstraction since both the abstraction hierarchy and hierarchy of its behaviors vary often. When components can be selected at run-time rather than compile-time.
Usage:

IEngineImpl box2d = new Box2DEngineImplementor();
IEngineImpl farseer = new FarseerEngineImplementor();
IPhysicEngine pysics = new TwoDimensionalPhysicsEngine( useBox2d ? box2d : farseer );
IBody car = pysics.CreateBody(); // i.e: calls IEngineImpl.GetConcreteBody();

Keywords: SOLID ocp srp, composition over inheritance, pimpl, impl handle body, GUI framework, refactoring
Synergy with: dependency injection, abstract factory (used to create and configure a particular bridge)
Competing with: adapter (different intent, used after design, not during), strategy (different intent), state (change imp along with its state)

Composite

β›“πŸ”ŒπŸŽ“β–ΆοΈβ™»οΈ code | wiki
When: We need tree structures where clients treat individual objects and compositions of objects uniformly. When objects should forward requests to their child components.
Usage:

IStageObject objA = new Bitmap(...);
IStageObject objB = new Sprite(...);
IStageObject layerA = new CompositeSprite();
layerA.AddChild(objA);
layerA.AddChild(objB);
...
IStageObject mainLayer = new CompositeSprite();
mainLayer.AddChild(layerA);
...
mainLayer.Show(); // = spriteA.Show() & spriteB.Show();

Keywords: tree structure, part-whole hierarchy, node element, node leaf, mixin, XQuery, XDocument, refactoring
Synergy with: builder, decorator (used to decorate components)
Competing with: ?

Facade

β›“πŸ”ŒπŸ›  code | wiki
When: We want to provide a simplified/unified interface to a set of interfaces/libraries in a subsystem (i.e: OpenGL). When we want to wrap poorly designed collection of APIs.
Usage:

var facade = new OpenGlEsFacade();
facade.CreateComplete2DScene();

Keywords: forwarding, multiple interfaces hidden, monolit refactor, simplify api
Synergy with: singleton
Competing with: decorator, adapter, abstract factory

Filter

β›“πŸ”Œ code | wiki
When: We want to filter a set of objects using different criteria and chaining them in a decoupled way through logical operations.
Usage:

IMatchCriteria sameRank = new RankCriteria(Rank.Diamond);
IMatchCriteria sameKdRatio = new KillDeathRatioCriteria(player.KDRatio);
var opponents = Criteria.And(sameRank, sameKdRatio).Filter(players);

Keywords: filter, criteria, matching
Synergy with: ?
Competing with: ?

Flyweight

β›“ code | code | code | wiki
When: We want to minimize memory usage by sharing as much (invariant) data as possible with other similar objects. When we can afford to remove non-shareable state from the class, and having the client supply it when methods are called.
Usage:

class DirtTile { // the client of texture (flyweight), he is the he heavyweight one...
  public int X {get; set}
  public int Y {get; set}
  public Matrix<2> Scale {get; set}
  private ITexture texture = TextureFactory.Get("dirt"); // the flyweight with intrinsic (shared) bitmap data
  public void DrawOnto(IScene scene) {
  	scene.ClearRectArea(X, Y, texture.Width, texture.Height, Scale);
	texture.DrawOnto(scene.GetCanvas(), X, Y, Scale); // extrinsic data passed in
...
var dirtTiles = Enumerable.Range(0, 640).Random(200).Select( x => new DirtTile { X = x, Y = (x % 13) } );
dirtTiles.ForEach( tile => tile.DrawOnto(scene));

Super simplified case without factory but with shared invariant state:

class MyGlyph {
    public Guid Id { get; set; }
    public string FontName => MyFont.Name; // flyweight pointer to a string
    public string License => MyFont.License; // flyweight pointer to a large string
    public IList<int> Points = ...
}

Keywords: performance, memory usage, smart_ptr, immutable shared data, intrinsic extrinsic, string interning
Synergy with: singleton, multiton, factory method, state (states are often flyweights), strategy (stategies are often flyweights)
Competing with: object pool (flyweights are shared among clients, objts from obj pool are not)

Delegation

β›“πŸ”ŒπŸ›  code | wiki
When: We want an object to delegate task to an associated helper object (with delegate using sender's context) instead of performing one of its stated tasks. When we want to achieve composition over inheritance.
Usage:

class Rectangle {
  ...
  GetDimensions(IWindow receiver) => [Width + 2*receiver.BorderSize, Height + 2*receiver.BorderSize];
}
class Window : IWindow {
  public int BorderSize { get; set; } = 1;
  ...
  GetDimensions() => m_myDimesionsDelegate.GetDimensions(this);
}
...
var window = new Window("Some game", new Rectangle(100px, 100px));
...
var dimensions = window.GetDimensions(); // prints [102, 102]
class Window : IWindow {
  public int BorderSize { get; set; } = 1;
  ...
  Func<Rectangle> Dimensions = () =>  [Width + 2 * this.BorderSize, Height + 2 * this.BorderSize];
}
...
var window = new Window("Some game", 100px, 100px);
var dimensions = window.Dimensions(); // prints [102, 102]

Keywords: composition over inheritance, wrapper wrappee, sending receiving, functional programming
Synergy with: adapter, facade
Competing with: forwarding

Forwarding

β›“πŸ”ŒπŸ›  code | wiki
When: We want an object to delegate task to an associated helper object (with delegate using its own context) instead of performing one of its stated tasks. Same use case as in delegation pattern with difference in binding of the this (self, context) in the delegate when called by the original (sending) object.
Usage:

class Rectangle {
  ...
  GetDimensions(IWindow receiver) => [Width, Height];
}
class Window : IWindow {
  public int BorderSize { get; set; } = 1;
  ...
  GetDimensions() => m_myDimesionsDelegate.GetDimensions() + 2*[BorderSize, BorderSize];
}
...
var window = new Window("Some game", new Rectangle(100px, 100px));
...
var dimensions = window.GetDimensions(); // prints [102, 102]

Keywords: composition over inheritance, wrapper object, wrapper function, wrapper wrappee, functional programming
Synergy with: adapter, facade
Competing with: delegation

Proxy

β›“πŸŽ“πŸ›  code | code | code | wiki
When: We want to controll access to another object. When we want to add extra functionality to an object like caching, audits, locking/synchronization or checking preconditions while hiding info about the real object.
Usage:

ISearchFeature search = new MetadataSearch();
search.OnDoc(somePdf, keywordList);
... refactor ...
ISearchFeature search = new ProxyMetadataSearch(Thread.CurrentPrincipal, checkLicenseOnline = true, retentionPolicy = 1min);
search.OnDoc(somePdf, keywordList);

Keywords: surrogate, forwarding, virtual proxy, access control, cache, smart pointer, SOLID srp
Synergy with: forwarding, lazy initialization
Competing with: decorator(adds functionalities vs controll access)

Marker interface

β›“β–ΆοΈπŸ›  wiki
When: We need to associate metadata with a class, i.e: via empty interface. When wee need to mark a class.
Usage:

public interface IQuery { }
public interface ICommand { }
...
var request = requests.Dequeue();
if(request is IQuery) {
  ...
} else if (request is ICommand) {
  ...

Keywords: marker, tagging, custom attribute, annotation, reflection, run-time, Serializable
Synergy with: ?
Competing with: attributes

Module

β›“πŸ› πŸ“ code | wiki | wiki
When: We need one entity that groups several related elements, such as classes, singletons, services, methods etc. and simplify the process of their configuration and deployment. When we need to decreased configuration complexity (i.e: of di containers, automappers, DAL, logging). When we want group of services to configure themselves to their execution environment. Usage:

var Screen = MainScreenModule.Get( cfg => cfg.ForceFullScreen().BringFront().DebugMode(true).ShowFPS() );
Screen.Load();
...
Screen.Display(someBitmap);
...
var position = Screen.LastTouch.GetLocation();
Screen.Unload();

Keywords: modular programming, bundle, bootstrap, subsystem, package, plug-in, not namespace
Synergy with: singleton, facade, flyweight, adapter
Competing with: namespace (no initializer/finalizer function, like ctor/dtor), dependency injection (doesn't work well with it, but may be used for di container configuration)

Registry

β›“πŸ”Œβ–ΆοΈ code | info | wiki
When: We need to register & access objects at runtime from anyewhere in the code via key-to-object registry.
Usage:

ICutscene carCrash = new CarCrashCutscene(...);
...
cutseneRegistry.Register("car-crash-cutscene", carCrash);
cutseneRegistry.Register("death-cutscene", new DeathCutscene(...));
cutseneRegistry.Register<EndCutscene>("ending-cutscene");
...
var ending = cutseneRegistry.Get("ending-cutscene");
ending.Play(game);

Keywords: IViewRegistry, global, DI Container
Synergy with: dependency injection (as a di container), singleton
Competing with: multiton

Twin TODO

πŸ†•β›“πŸ…±οΈπŸ”ŒπŸŽ“πŸ’€β–ΆοΈπŸ› πŸ“ code | wiki
When: We need multiple inheritance in a programming language that do not support this feature.
Usage:

Keywords: TODO
Synergy with: TODO
Competing with: TODO


Behavioral Patterns

behaviour between entities, scenarios external to the structures - a certain data structure can "be used" in multiple scenarios

Chain of responsibility

πŸ…±οΈπŸ”ŒπŸŽ“β–ΆοΈ code | code | wiki
When: We need an object oriented version of if ... else if ... else if ... else ... endif that can be rearranged at run-time, with more than one object handling the request depending on run-time conditions.
Usage:

IEnemyGenerator basic = new RandomBasicEnemyGenerator(rng=...);
IEnemyGenerator elite = new RandomSpecialEnemyGenerator(rng=..., difficulty, next=basic);
IEnemyGenerator enemyGenerator = new DirectedEnemy(map, difficulty, next=elite);

enemyGenerator.AddEnemyToThe(scene);

Keywords: sender handler receiver, SOLID srp, asp.net middleware, linked list, pipeline
Synergy with: dependency injection
Competing with: decorator (all handle the request vs only one)

Command

πŸ…±οΈπŸ”ŒπŸŽ“β–ΆοΈβ™»οΈ code | code | wiki
When: We need an object that encapsulates all information needed to perform an action or trigger an event at a later time. When we need to decouple invoker (of the command) and the handlers (of the command). When we need to save requests in a queue.
Usage:

IGamePlayer player = ...
IGameEngine game = ...

ICommand killPlayerCommand = new DelegateCommand<CauseOfDeathEnum>(
  causeOfDeath => game.IsRunning() && player.IsAlive() && player.CanBeKilledVia(causeOfDeath),
  causeOfDeath => player.KillVia(causeOfDeath))
//or ICommand killPlayerCommand = new KillCommand(player);

IScenario scenario = RussianRoulette(game, killPlayerCommand);
// i.e:
// if(killPlayerCommand.CanExecute(CauseOfDeathEnum.ShotInTheHead))
//    killPlayerCommand.Execute(CauseOfDeathEnum.ShotInTheHead);

scenario.Run();

Keywords: command receiver invoker client, mvc, mvvm, delayed execution, encapsulation, SOLID srp ocp, DelegateCommand, RelayCommand, CompositeCommand, GUI, refactoring
Synergy with: memento (can pass state for ops like undo), delegate (coz Execute() delegates the call to the handler)
Competing with: servant

CQS - Command Query Separation

πŸ…±οΈπŸ”ŒπŸŽ“ code | wiki | wiki
When: We need decent separation of concerns via defining what is an command that performs an action (mutate or modify the data), or a query that returns data to the caller (safely retrieve data), but never both. When we want to constrain that methods should return a value only if they create no side effects.
Usage:

IQuery query = new GetPlayerRankQuery { PlayerID = playerId, ShowForCurrentSeason = true };
IRankQueryResult result = queryDispatcher.Dispatch<IRankQueryResult>(query); // finds & fires appropriate query handler
if(result.Rank == LadderRank.Diamond) {
  ...

ICommand updateCommand = new UpdateRankCommand{ PlayerID = playerId, KdRatio = 0.7, OpponentRank = ... };
IRankCommandResult result = commandDispatcher.Dispatch<IRankCommandResult>(updateCommand);  // finds & fires appropriate command handler
if(result.Status == CommandResult.OK) {
  ...

Keywords: principle, design by contract, not CQRS, complex domains, DDD, separation of concerns, modular, Query QueryResult QueryDispatcher QueryHandler, Command CommandResult CommandDispatcher CommandHandler
Synergy with: CQRS for event-based architectures
Competing with: CRUD

Interpreter

πŸ…±οΈπŸ›  code | wiki
When: We need to evaluate sentences in a language. When we need to interpret expressions.
Usage:

var context = new GameCmdContext();
var expressions = expressionParser.Parse(console.GetText(), context);
expressions.Interpret(context); 
// or: for( var expr in expressions) expr.Interpret(context);
scenario.Perform(context.Action, context.Params);

Keywords: context expression terminal
Synergy with: composite, state
Competing with: ?

Iterator

πŸ…±οΈπŸ”ŒπŸŽ“ code | wiki
When: We need a way to access the elements of an aggregate object sequentially e.g. when data has to be be lazy-loaded or to avoid memory allocation peaks.
Usage:

IEnumerator<Sprite> walking = spriteManager.GetEnumeratorFor(PlayerSpriteEnum.Walking);
...
walking.Reset();
while(walking.MoveNext())
{
  scene.Draw(walking.Current);
  ...
}

Keywords: container, enumerator, IAsyncEnumerable IEnumerable<T> IEnumerator<T>, lazy loading, internal vs external, functional programming (internal iterator)
Synergy with: composite
Competing with: ?

Observer

πŸ…±οΈπŸ”Œβ–ΆοΈβ™»οΈ code | code | code | wiki
When: We want to notify/update other (multiple) objects about state change within an object. When multiple objects are dependent on the state of one object. When we want to register and unregister from notifications at runtime.
Usage:

IObservable<Connection> gamePortHub = ...
IObserver<Connection> gameLobby = ...
IDisposable unsub = gamePortHub.Subscribe(gameLobby);
...
gamePortHub.Connected(new Connection{...});
...
unsub.Dispose();
PlayerJoinedLobby(object source, ConnectedEventArgs e) { ... }
...
player.AddEventListener("connected", PlayerJoinedLobby)
player.AddEventListener("connected", (s, e) => { chat.Send("Player {e.Name} joined the lobby."); }) // leak, use wak ref
...
player.RemoveEventListener("connected", PlayerJoinedLobby)
// EventHandler<ConnectedEventArgs> is a delegate type, ConnectedEventArgs is event data
public event EventHandler<ConnectedEventArgs> Connected; 
...
// Connected is an event
Connected += (s) => {...}; 
...
OnConnected(...)
{
  Connected?.Invoke(this, new ConnectedEventArgs{...})
}

Keywords: one-to-many, subject observers, publish subscribe, event listener, event emitter target dispatcher, event driven, memo leak, weak reference, message handler, IObservable<T> IObserver<T>, view in mvc, viewmodel, push vs pull architecture, refactoring
Synergy with: dispose
Competing with: mediator

Event Aggregator

πŸ…±οΈπŸ”Œ code | code | code | code | code | info
When: We have lots of objects that are potential event sources. When we have multiple publishers for multiple subscribers.
Usage:

IEventAggregator eventAggr = game.EventAggregator;
var subToken = eventAggr.GetEvent<GameStateSaveEvent>()
  .Where( e => e.IsClosing ).Subscribe( e => stateManager.Save(e.State) ).ToToken();
using(var subSession = eventAggr.Subscribe<GameStateSaveEvent>( e => stateCacheManager.Cache(e.State) )
{
  ...
  eventAggr.Publish(new GameStateSaveEvent { Sate = ecs.Dump(), IsClosing = true });
}
...
eventAggr.Ubsubscribe(subToken);

Keywords: many-to-many, dispatcher, publishers subscribers, event bus, message hub, change manager, IObserver<T> IObservable<T>
Synergy with: observer (more advanced version of an observer), dispose (for unsub), singleton (often is a singleton)
Competing with: observer (for less complex scenarios)

Mediator

πŸ…±οΈπŸ”Œ code | code | code | wiki
When: We want classes to exchange messages between each other but not directly but via specialized mediator class. We do not want 'em to communicate/interact directly with each other. When we want to reduce coupling between (set of) classes. When we want to define exactly how they can interact.
Usage:

// TODO: needs simpler example
IModalMediator modalMediator = new AsyncModalMediator(InvokeOn.MainUiThread);
modalMediator.RegisterView<FilterOptionsViewModel, FilterOptionsView>();
...
class ShellViewModel {
  ShellViewModel(IModalMediator modalMediator, ... ) {
    modalMediator.RegisterContainerForModals(this);
    ...
  }
  ...
  void ShowModal(IView view, IViewModel vm) {
    ActiveModal = view;
    view.Closed += (s, e) => modalMediator.Closed(vm);
...
class ItemListViewModel {
  ...
  ItemListViewModel(IModalMediator modalMediator, ... ) {
    this.modalMediator = modalMediator;
  }
  ...
  async Task ShowFilterOptions() { 
    var filtersVm = new FilterOptionsViewModel();
    await modalMediator.ShowAsync(filtersVm);
    ...

Keywords: sender receiver
Synergy with: observer (mediator can be also an observer), command (passed between objs via mediator)
Competing with: observer (same intent, receivers reconfigured at runtime, doesn't encapsulate the communication between other objs), event aggregator (same intent, many-to-many, not only messages but events, does not have business/workflow logic), chain of responsibility (passes a sender request along a chain of potential receivers VS has senders and receivers reference each other indirectly)

Memento

πŸ…±οΈ code | wiki
When: We want to have an ability to restore an object to its previous state.
Usage:

GameState save = ecs.Dump();
autoSave.Add(save);
...
ecs.Load(autoSave.GetLast());

Keywords: save state, undo via rollback, originator caretaker memento, check point, immutable memento
Synergy with: command (for undo)
Competing with: command (passes request rather than state)

Method chaining

πŸ…±οΈβ–ΆοΈ code | code | wiki | wiki | wiki
When: We want to invoke multiple method calls in single statement, without requiring variables to store the intermediate results.
Usage:

IEnumerable<string> monsterIDs = stageObjectDict
	.Where(x => x.Value.IsMonster && x.Value.IsVisible)
	.OrderBy(x => x.Value.AppearTime)
	.Select(x => x.Key.ToUpper());

Keywords: named parameter idiom, cascading-by-chaining by returning this, method cascading, LINQ, functional programming
Synergy with: builder, method cascading, fluent interfaces
Competing with: ?

Null object

πŸ…±οΈβ™»οΈ code | wiki
When: We want to avoid null references and runtime NullReferenceExceptions by providing a default object. When we want to pass/return a an object which implements the expected interface, but whose method body is empty. When we need to remove old functionality by replacing it with null objects (refactoring).
Usage:

if(fake)
  enemyId = String.Empty; // ex #1
...
enemy = enemiesOnStage.Where( x => x.Id.Equals(enemyId)).GetFirstOrDefault(); // ex #2
enemy.AttachTo(stage);

Keywords: SOLID lsp, nullable, default, empty list, null coalescing, LINQ, insert null object refactoring, refactoring, functional programming, Nullable<T>, T?, string.Empty, std::optional
Synergy with: state (can be returned as default), singleton, factory method (return real or null obj), template method (for concrete Template that does nothing)
Competing with: ?

Plugin

πŸ…±οΈβ–ΆοΈπŸ“ code1 2 | code | info | wiki
When: We need centralized, runtime configuration.
Usage:

IList<IFighter> fighters = AssetManager.LoadFrom<IFighter>("./data/fighters");

Keywords: runtime, add-on, extension, plug-in, separated interface
Synergy with: ?
Competing with: ?

Repository

πŸ…±οΈπŸ”ŒπŸŽ“ code | wiki
When: We need to encapsulate the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer (more like a collection). When we need testable/mockable mediator between the domain/business logic and data access/mapping layers. Not a database abstraction!
Usage:

PlayerStats stat = statsRepo.Find(playerName, new StatFilter{ IsBeta = true });
stat.GamesPlayed++;
playerStatsRepo.CreateOrUpdate(stat);
...
statRepo.Delete(player);
orderRepo.ClearHistory(playerId); // note: shouldn't be in DDD
playerRepo.SetInactive(player);  // note: shouldn't be in DDD

Keywords: CRUD, IQueryable<T> vs IEnumerable<T>, paging, sorting, lazy loading, IAsyncEnumerable, unit-of-work SaveChanges
Synergy with: unit-of-work, fluent interfaces
Competing with: ORM (DbSet is a repo)

Unit of work

πŸ…±οΈ code | wiki
When: We want many small database updates that represent one business transaction to merge into single batch to optimize the number of round-trips.
Usage:

// repositories and the uow as separate objects with uow injected into repositories
statRepo.Delete(playerId);
orderRepo.ClearHistory(playerId);
playerRepo.SetInactive(player);
trx.Commit();
// repositories being exposed via uow with uow composed of repositories
uow.StatRepo.Delete(playerId);
uow.OrderRepo.ClearHistory(playerId);
uow.PlayerRepo.SetInactive(player);
uow.Commit();

Keywords: database transaction, business transaction, unit-of-work, SaveChanges, commit, rollback
Synergy with: repository
Competing with: ORM (DbContext is a uow), Saga (distributed systems)

Scheduled-task TODO

πŸ…±οΈ code | wiki
When: We want a task to be scheduled to be performed at a particular interval or clock time.
Usage:

Keywords: real-time computing
Synergy with: TODO
Competing with: TODO

Servant

πŸ…±οΈπŸ”ŒπŸŽ“ code | wiki
When: We want to define common functionality for a group of classes. When we need an object to which we want to offer some functionality, so that it can perform it (serve) on objects (passed as parameters) at a later time.
Usage:

IGameEngine game = ...
IKillServant hitman = new HitmanServant(game);
...
IKillable victim = player as IKillable;
hitman.TryToKill(victim);
hitman.KillWith(ToolboxEnum.Crowbar, victim);
HitmanHelper.KillWith(ToolboxEnum.Crowbar, this as IKillable);

Keywords: helper class, utility class, all static methods, no encapsulation, no SOLID srp ocp
Synergy with: ?
Competing with: command

Specification

πŸ…±οΈβ–ΆοΈ code | wiki
When: We need easily maintainable, yet highly customizable business logic. When business rules should be recombined an run-time by chaining the rules together using boolean logic.
Usage:

ISpec<Invoice> OverDue = new OverDueSpecification();
ISpec<Invoice> NoticeSent = new NoticeSentSpecification();
ISpec<Invoice> InCollection = new InCollectionSpecification();
ISpec<Invoice> ShouldBeSendToCollection = OverDue.And(NoticeSent).And(InCollection.Not());

foreach (var invoice in service.GetInvoices()) {
  if (ShouldBeSendToCollection.IsSatisfiedBy(invoice))  {
    ...

Keywords: anti-pattern, DDD domain-driven design, business rules, runtime, BDD behavior-driven development, SpecFlow
Synergy with: interpreter, composition
Competing with: ?, boolean algebra

State

πŸ…±οΈπŸ”ŒπŸŽ“β–ΆοΈβ™»οΈ code | wiki | article
When: An object has to have many behaviours which change depending on its internal state. Behaviour often change at run-time.
Usage:

public class Player : GameObject {
  private IPlayerState _state;
  ...
  public override void Initialize() {
    _state = new PlayerIdleState();
  }
  public override void Update(GameTime gameTime) {
    HandleInput(Keyboard.GetState());
    UpdateState(gameTime);
    Animation.Update(gameTime);
  }
  private void UpdateState(GameTime gameTime) {
    if (_state != null) {
      IPlayerState newState = _state.Update(this, gameTime);
      if (newState != null && _state != newState) {
        _state.Exit(this);
        _state = newState;
        _state.Enter(this);
  ...
class PlayerAttackState : IPlayerState {
  ...
  public IPlayerState Update(IPlayer player, GameTime gameTime) {
    if (player.Animation.AnimationCompleted)
      return new PlayerIdleState();
    return null;
  ...

Keywords: SOLID srp ocp, finite state machine, refactoring (status/state field and many ifs)
Synergy with: flyweight (states often are flyweights)
Competing with: strategy (states often change internaly)

Strategy

πŸ…±οΈπŸ”ŒπŸŽ“β–ΆοΈπŸ› πŸŒŸβ™»οΈ code | wiki
When: Behavior (algorithm, strategy) of a class needs to be selectable (or interchangeable) without breaking the class that use it, both at run-time as well as at design-time.
Usage:

public class EliteZombie : IMonster, IGameObject, IElitare {
  IMonsterBehavior Behavior = MonsterBehavior.Default;
...
  Update(GameTime gameTime) {
    Behavior.UpdateVelocity(this.Body);
    if(Behavior.ShouldAttack(this.Body.Position, this.AttackRange)) {
      ...
  }
...
}
monster[i].SetBehavior(new NormalUnawareMonsterBehavior(game));
monster[i].AttachTo(stage);
...
if(monster.CanSee(player)) {
  monster.SetBehavior(new AwareButStealthMonsterBehavior(game, player, fadeOut: true));
}
else if(monster.Health/monster.MaxHealth < 0.4) {
  monster.SetBehavior(new EnragedMonsterBehavior(game, player));
}

Keywords: SOLID srp ocp, composition over inheritance, policy pattern, refactoring (many ifs with different behaviours), inversion of control IoC, high cohesion, loose coupling
Synergy with: factory method (to hide concrete imps and decide which strategy to use), dependency injection, flyweight (strategies often are flyweights)
Competing with: state (strategies often are changed by external source), template (also IOC but achieved via inheritance, not via delegation), bridge (same diagram, different intent: behavior vs structure)

Template method

πŸ…±οΈπŸ”Œβ–ΆοΈπŸ› β™»οΈ code | wiki
When: We have overall behaviour (algorithm) of a class defined, but we want some of the steps to be redefinable by the subclasses.
Usage:

abstract class TrainingAlgorithmBase : ITrainingAlgorithm {
  abstract void PreTrain();
  abstract ErrorGradient CalculateEpoch(ITraininDataSet set);
  abstract void PostTrain();
  ...
  void Train(IFeedForwardNetwork network, ITraininDataSet set) {
    PreTrain();
    for (int i = 0; i < maxIterations; ++i) {
      var errorGrad = CalculateEpoch(set);
      if(IsBelowThreshold(errorGrad))
        break;
      errorGrad = EludeLocalMinima(network, errorGrad)
    }
    PostTrain();
    ...
class SupervisedTraining : TrainingAlgorithmBase {
  override void PreTrain() {
    ...
var trainer = new SupervisedTraining( optimizationAlgorithm, 1000, 0.00001 );
trainer.Train(network, training_set);

Keywords: inversion of control IoC, refactoring, inheritance, override, customization hooks, prefix do- pre- post-, frameworks
Synergy with: factory method (often used within)
Competing with: dependency injection, strategy (changes whole algo, not parts of it, delegation vs inheritance)

Visitor

πŸ…±οΈπŸ”ŒπŸŽ“πŸ› β™»οΈ code | wiki
When: We want to enable adding new behaviours (operations, algorithms) that operate on some class hierarchy/fammily of classes (object structures) without the need to change 'em. When we want behaviours to be separated from the object structures on which they operate. When new ops are added frequently. When the classes that make up the object structure are known and not expected to change. When recovering lost type information without resorting to dynamic (down)cast (dynamic_cast<PersianCat*>(animal), (PersianCat)animal, animal as PersianCat).
Usage:

struct NoTarget{};
using AggroResult = std::variant<PlayerID, MobID, NoTarget, Error>;
...
const std::vector<AggroResult> targets = boss.ChooseTargets<3>();
...
for (const auto& target: targets) {
  std::visit(overloaded {
    [](auto _) { /* nop */ },
    [](const Error& e) { log.Error(e.Code, e.Reason.ToString()) },
    [](PlayerID player) { if(boss.currentTarget != player) boss.AttackNext(player); },
    [](MobID mob) { boss.Heal(mob); },
  }, target);
}

Keywords: SOLID ocp, method overload, double dispatch, obj.accept(visitor) -> visitor.visit(obj), dynamic_cast<>, refactoring
Synergy with: composite (object structures can be a composite structure)
Competing with: ?


Concurrency Patterns


Links:


Pattern template

TODO

πŸ†•β›“πŸ…±οΈπŸ”ŒπŸŽ“πŸ’€β–ΆοΈπŸ› πŸ“ code | wiki
When: TODO
Usage:

Keywords: TODO
Synergy with: TODO
Competing with: TODO


Git Gud or Die Tryin'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment