Lesson 26: Advanced OOP Patterns (Factory, Strategy, Observer)
Apply the most essential Gang of Four design patterns to write flexible, extensible C# code.
What You'll Learn
- • Factory Pattern — create objects without hard-coding types
- • Strategy Pattern — swap algorithms at runtime
- • Observer Pattern — event-driven pub/sub architecture
- • When to use each pattern in real applications
| Pattern | Purpose | Real-World Use |
|---|---|---|
| Factory | Create objects without specifying exact class | Plugin systems, notification services |
| Strategy | Swap algorithms at runtime | Sorting, pricing, validation |
| Observer | Notify many objects of state changes | Event systems, UI updates |
Factory Pattern
The Factory pattern encapsulates object creation. Instead of new EmailNotification() scattered everywhere, you call factory.Create("email"). This makes your code open for extension (register new types) and closed for modification.
Factory Pattern with Registration
Create a notification factory that produces different notification types.
using System;
// Factory Pattern — create objects without exposing creation logic
interface INotification
{
void Send(string message);
}
class EmailNotification : INotification
{
public void Send(string message) =>
Console.WriteLine($"📧 Email: {message}");
}
class SmsNotification : INotification
{
public void Send(string message) =>
Console.WriteLine($"📱 SMS: {message}");
}
class PushNotification : INotification
{
public void Send(string message) =>
...Strategy Pattern
Strategy lets you define a family of algorithms, encapsulate each one, and swap them interchangeably. The client code works through an interface and doesn't know which concrete algorithm it's using.
Strategy Pattern — Swappable Algorithms
Switch sorting algorithms at runtime without changing client code.
using System;
using System.Collections.Generic;
using System.Linq;
// Strategy Pattern — swap algorithms at runtime
interface ISortStrategy<T>
{
void Sort(List<T> data);
string Name { get; }
}
class BubbleSort<T> : ISortStrategy<T> where T : IComparable<T>
{
public string Name => "Bubble Sort";
public void Sort(List<T> data)
{
for (int i = 0; i < data.Count - 1; i++)
for (int j = 0; j < data.Count - i - 1; j++)
if (data[j].CompareTo(data[
...Observer Pattern
Observer implements a one-to-many dependency: when one object changes state, all dependents are notified automatically. In C#, this naturally maps to events and delegates, but building your own gives you more control (like an event bus).
Observer Pattern — Event Bus
Build a pub/sub event bus that notifies email, analytics, and logging services.
using System;
using System.Collections.Generic;
// Observer Pattern — notify subscribers of state changes
interface IObserver<T>
{
void OnNext(T value);
}
class EventBus<T>
{
private readonly List<IObserver<T>> _observers = new();
private readonly List<Action<T>> _handlers = new();
public void Subscribe(IObserver<T> observer) => _observers.Add(observer);
public void Subscribe(Action<T> handler) => _handlers.Add(handler);
public void Publish(T data)
{
...Pro Tip
In production C#, you'll often combine these patterns. A Factory creates Strategy instances, and an Observer notifies when strategies are swapped. Design patterns compose — they're building blocks, not standalone solutions.
Common Mistakes
- • Over-engineering with patterns — use them when complexity justifies it
- • Choosing inheritance over composition — prefer interfaces and injection
- • Forgetting to unsubscribe observers — causes memory leaks
Lesson Complete!
You've mastered Factory, Strategy, and Observer patterns in C#. Next, dive deep into Dependency Injection — the backbone of modern .NET applications.
Sign up for free to track which lessons you've completed and get learning reminders.