Back to Blog
design-patternscsharptypescriptstructural-patterns

Facade Pattern — FixIt Pro Series #10

Scheduling, assigning, notifying, and billing, FixIt Pro's dispatch process involves five subsystems. Learn how the Facade pattern hides that complexity behind a single clean interface, in C# and TypeScript.


Series: Design Patterns with FixIt Pro  ·  Episode 10 / 22  ·  Structural Pattern
Previous: #09 — Decorator Pattern


The Scenario

When a homeowner submits a job request in FixIt Pro, a lot happens under the hood:

  1. Scheduler — finds an available time slot and books it
  2. HandymanAssigner — finds a qualified handyman and assigns them
  3. NotificationService — alerts the homeowner, handyman, and supervisor
  4. JobCardRegistry — registers the new job card in the system
  5. BillingService — generates a quote and sends it to the client

Every one of these is a separate subsystem with its own interface. A developer building a "Submit Job" feature shouldn't need to know the internals of all five. They shouldn't need to call them in the right order, handle each one's error surface, or know which depends on which.

They should call one method: DispatchJob(...).

That's the Facade.


What Is the Facade Pattern?

Provide a unified interface to a set of interfaces in a subsystem. The Facade defines a higher-level interface that makes the subsystem easier to use.

The Facade doesn't replace the subsystems — they still exist and can be used directly if needed. It just provides a simplified entry point for the most common use cases. Complexity doesn't disappear; it moves to where it belongs.

The two participants

Role FixIt Pro equivalent
Facade JobDispatchFacade — the unified entry point
Subsystems Scheduler, HandymanAssigner, NotificationService, JobCardRegistry, BillingService

C# Implementation

// ── Subsystem 1: Scheduler ─────────────────────────────────

public class Scheduler
{
    public string FindAvailableSlot(string category)
    {
        Console.WriteLine($"  📅 Scheduler: Finding slot for {category} job...");
        return "Monday 2 June 2025 @ 09:00";
    }

    public void BookSlot(string slot, string jobId)
    {
        Console.WriteLine($"  📅 Scheduler: Slot '{slot}' booked for Job #{jobId}.");
    }
}

// ── Subsystem 2: HandymanAssigner ──────────────────────────

public class HandymanAssigner
{
    public string FindQualifiedHandyman(string category)
    {
        Console.WriteLine($"  👷 HandymanAssigner: Finding {category} handyman...");
        return "James Nghipandua";
    }

    public void Assign(string handyman, string jobId)
    {
        Console.WriteLine($"  👷 HandymanAssigner: {handyman} assigned to Job #{jobId}.");
    }
}

// ── Subsystem 3: NotificationService ──────────────────────

public class NotificationService
{
    public void NotifyHomeowner(string jobId, string slot)
    {
        Console.WriteLine($"  📧 NotificationService: Homeowner notified — Job #{jobId} scheduled for {slot}.");
    }

    public void NotifyHandyman(string jobId, string handyman)
    {
        Console.WriteLine($"  📱 NotificationService: {handyman} notified of Job #{jobId}.");
    }

    public void NotifySupervisor(string jobId)
    {
        Console.WriteLine($"  🔔 NotificationService: Supervisor notified of new Job #{jobId}.");
    }
}

// ── Subsystem 4: JobCardRegistry ───────────────────────────

public class JobCardRegistry
{
    private readonly List<(string JobId, string Title, string Status)> _jobs = new();

    public void Register(string jobId, string title)
    {
        _jobs.Add((jobId, title, "Active"));
        Console.WriteLine($"  🗂️  JobCardRegistry: Job #{jobId} '{title}' registered.");
    }

    public int Count => _jobs.Count;
}

// ── Subsystem 5: BillingService ────────────────────────────

public class BillingService
{
    public decimal GenerateQuote(string category, bool isUrgent)
    {
        var baseRate = category switch
        {
            "Plumbing"   => 850m,
            "Electrical" => 1200m,
            "Carpentry"  => 600m,
            _            => 750m
        };

        var total = isUrgent ? baseRate * 1.25m : baseRate;
        Console.WriteLine($"  💰 BillingService: Quote generated — N${total:N2}");
        return total;
    }

    public void SendQuote(string jobId, string clientEmail, decimal amount)
    {
        Console.WriteLine($"  💰 BillingService: Quote of N${amount:N2} sent to {clientEmail} for Job #{jobId}.");
    }
}

// ── Facade ─────────────────────────────────────────────────

public class JobDispatchFacade
{
    // Facade owns and coordinates the subsystems
    private readonly Scheduler           _scheduler    = new();
    private readonly HandymanAssigner    _assigner     = new();
    private readonly NotificationService _notifier     = new();
    private readonly JobCardRegistry     _registry     = new();
    private readonly BillingService      _billing      = new();

    // Single entry point — the client calls this and nothing else
    public void DispatchJob(
        string jobId,
        string title,
        string category,
        string clientEmail,
        bool   isUrgent = false)
    {
        Console.WriteLine($"\n{'='.ToString().PadRight(50, '=')}");
        Console.WriteLine($"🚀 Dispatching Job #{jobId}: {title}");
        Console.WriteLine('='.ToString().PadRight(50, '='));

        // Step 1 — Schedule
        var slot = _scheduler.FindAvailableSlot(category);
        _scheduler.BookSlot(slot, jobId);

        // Step 2 — Assign handyman
        var handyman = _assigner.FindQualifiedHandyman(category);
        _assigner.Assign(handyman, jobId);

        // Step 3 — Notify all parties
        _notifier.NotifyHomeowner(jobId, slot);
        _notifier.NotifyHandyman(jobId, handyman);
        _notifier.NotifySupervisor(jobId);

        // Step 4 — Register
        _registry.Register(jobId, title);

        // Step 5 — Billing
        var quote = _billing.GenerateQuote(category, isUrgent);
        _billing.SendQuote(jobId, clientEmail, quote);

        Console.WriteLine($"✅ Job #{jobId} dispatched successfully.\n");
    }
}

// ── Client Code ────────────────────────────────────────────
// The client knows nothing about Scheduler, HandymanAssigner,
// NotificationService, JobCardRegistry, or BillingService.
// It just calls DispatchJob.

class Program
{
    static void Main()
    {
        var facade = new JobDispatchFacade();

        facade.DispatchJob(
            jobId:       "JC-301",
            title:       "Replace burst pipe under sink",
            category:    "Plumbing",
            clientEmail: "[email protected]",
            isUrgent:    false
        );

        facade.DispatchJob(
            jobId:       "JC-302",
            title:       "Faulty circuit breaker — urgent",
            category:    "Electrical",
            clientEmail: "[email protected]",
            isUrgent:    true
        );
    }
}

Output:

==================================================
🚀 Dispatching Job #JC-301: Replace burst pipe under sink
==================================================
  📅 Scheduler: Finding slot for Plumbing job...
  📅 Scheduler: Slot 'Monday 2 June 2025 @ 09:00' booked for Job #JC-301.
  👷 HandymanAssigner: Finding Plumbing handyman...
  👷 HandymanAssigner: James Nghipandua assigned to Job #JC-301.
  📧 NotificationService: Homeowner notified — Job #JC-301 scheduled for Monday 2 June 2025 @ 09:00.
  📱 NotificationService: James Nghipandua notified of Job #JC-301.
  🔔 NotificationService: Supervisor notified of new Job #JC-301.
  🗂️  JobCardRegistry: Job #JC-301 'Replace burst pipe under sink' registered.
  💰 BillingService: Quote generated — N$850.00
  💰 BillingService: Quote of N$850.00 sent to [email protected] for Job #JC-301.
✅ Job #JC-301 dispatched successfully.

TypeScript Implementation

// ── Subsystem/Scheduler.ts ─────────────────────────────────

export class Scheduler {
  findAvailableSlot(category: string): string {
    console.log(`  📅 Scheduler: Finding slot for ${category} job...`);
    return "Monday 2 June 2025 @ 09:00";
  }

  bookSlot(slot: string, jobId: string): void {
    console.log(`  📅 Scheduler: Slot '${slot}' booked for Job #${jobId}.`);
  }
}

// ── Subsystem/HandymanAssigner.ts ──────────────────────────

export class HandymanAssigner {
  findQualifiedHandyman(category: string): string {
    console.log(`  👷 HandymanAssigner: Finding ${category} handyman...`);
    return "James Nghipandua";
  }

  assign(handyman: string, jobId: string): void {
    console.log(`  👷 HandymanAssigner: ${handyman} assigned to Job #${jobId}.`);
  }
}

// ── Subsystem/NotificationService.ts ──────────────────────

export class NotificationService {
  notifyHomeowner(jobId: string, slot: string): void {
    console.log(`  📧 NotificationService: Homeowner notified — Job #${jobId} scheduled for ${slot}.`);
  }

  notifyHandyman(jobId: string, handyman: string): void {
    console.log(`  📱 NotificationService: ${handyman} notified of Job #${jobId}.`);
  }

  notifySupervisor(jobId: string): void {
    console.log(`  🔔 NotificationService: Supervisor notified of new Job #${jobId}.`);
  }
}

// ── Subsystem/JobCardRegistry.ts ──────────────────────────

export class JobCardRegistry {
  private jobs: { jobId: string; title: string; status: string }[] = [];

  register(jobId: string, title: string): void {
    this.jobs.push({ jobId, title, status: "Active" });
    console.log(`  🗂️  JobCardRegistry: Job #${jobId} '${title}' registered.`);
  }

  get count(): number { return this.jobs.length; }
}

// ── Subsystem/BillingService.ts ────────────────────────────

export class BillingService {
  generateQuote(category: string, isUrgent: boolean): number {
    const rates: Record<string, number> = {
      Plumbing:   850,
      Electrical: 1200,
      Carpentry:  600,
    };

    const base  = rates[category] ?? 750;
    const total = isUrgent ? base * 1.25 : base;

    console.log(`  💰 BillingService: Quote generated — N$${total.toFixed(2)}`);
    return total;
  }

  sendQuote(jobId: string, clientEmail: string, amount: number): void {
    console.log(`  💰 BillingService: Quote of N$${amount.toFixed(2)} sent to ${clientEmail} for Job #${jobId}.`);
  }
}

// ── Facade/JobDispatchFacade.ts ────────────────────────────

import { Scheduler           } from "../Subsystem/Scheduler";
import { HandymanAssigner    } from "../Subsystem/HandymanAssigner";
import { NotificationService } from "../Subsystem/NotificationService";
import { JobCardRegistry     } from "../Subsystem/JobCardRegistry";
import { BillingService      } from "../Subsystem/BillingService";

export class JobDispatchFacade {
  private scheduler = new Scheduler();
  private assigner  = new HandymanAssigner();
  private notifier  = new NotificationService();
  private registry  = new JobCardRegistry();
  private billing   = new BillingService();

  dispatchJob(params: {
    jobId:       string;
    title:       string;
    category:    string;
    clientEmail: string;
    isUrgent?:   boolean;
  }): void {
    const { jobId, title, category, clientEmail, isUrgent = false } = params;

    console.log(`\n${"=".repeat(50)}`);
    console.log(`🚀 Dispatching Job #${jobId}: ${title}`);
    console.log("=".repeat(50));

    // Step 1 — Schedule
    const slot = this.scheduler.findAvailableSlot(category);
    this.scheduler.bookSlot(slot, jobId);

    // Step 2 — Assign handyman
    const handyman = this.assigner.findQualifiedHandyman(category);
    this.assigner.assign(handyman, jobId);

    // Step 3 — Notify all parties
    this.notifier.notifyHomeowner(jobId, slot);
    this.notifier.notifyHandyman(jobId, handyman);
    this.notifier.notifySupervisor(jobId);

    // Step 4 — Register
    this.registry.register(jobId, title);

    // Step 5 — Billing
    const quote = this.billing.generateQuote(category, isUrgent);
    this.billing.sendQuote(jobId, clientEmail, quote);

    console.log(`✅ Job #${jobId} dispatched successfully.\n`);
  }
}

// ── App.ts ─────────────────────────────────────────────────

import { JobDispatchFacade } from "./Facade/JobDispatchFacade";

const facade = new JobDispatchFacade();

facade.dispatchJob({
  jobId:       "JC-301",
  title:       "Replace burst pipe under sink",
  category:    "Plumbing",
  clientEmail: "[email protected]",
  isUrgent:    false,
});

facade.dispatchJob({
  jobId:       "JC-302",
  title:       "Faulty circuit breaker — urgent",
  category:    "Electrical",
  clientEmail: "[email protected]",
  isUrgent:    true,
});

C# vs TypeScript — Key Differences

Aspect C# TypeScript
Named parameters DispatchJob(jobId: "JC-301", title: ...) Object destructuring { jobId, title, ... }
Switch expression category switch { "Plumbing" => 850m, ... } Record<string, number> lookup object
Default parameter bool isUrgent = false isUrgent?: boolean with ?? false
String repeat '='.ToString().PadRight(50, '=') "=".repeat(50)
Subsystem ownership Private fields, instantiated in constructor body Private fields with inline initialisation

The TypeScript version uses an options object for dispatchJob — idiomatic for functions with many parameters. The C# version uses named arguments for the same clarity at the call site.


Facade vs Other Patterns It Resembles

Facade Adapter Mediator
Purpose Simplify a complex subsystem Make incompatible interfaces compatible Centralise complex communication between objects
Direction One-way — client to subsystem One-way — client to adaptee Multi-directional — objects communicate through it
Knows about Many subsystems One adaptee Many colleagues
FixIt Pro JobDispatchFacade → 5 subsystems SmsNotifierAdapter → LegacySmsGateway Coming in Episode 16

The Mediator (Episode 16) looks similar but is different in intent — the Facade simplifies access to subsystems from the outside; the Mediator controls how objects communicate with each other on the inside.


When to Use the Facade

Use it when:

  • You want to provide a simple interface to a complex subsystem
  • You want to layer your system — the Facade defines the entry point for each layer
  • There are too many dependencies between clients and implementation classes

Avoid it when:

  • The subsystem is already simple — adding a Facade is unnecessary overhead
  • You need full access to subsystem internals from all callers — the Facade would be leaky anyway
  • The "simplification" just moves the complexity one level up without actually reducing it

Real-World Takeaway

The Facade is the most quietly ubiquitous pattern in software. Every SDK you've ever used is a Facade. The AWS SDK wraps hundreds of HTTP calls behind methods like s3.putObject(). The Stripe API hides payment processing complexity behind stripe.charges.create(). In .NET, File.ReadAllText() is a Facade over FileStream, StreamReader, encoding, and buffer management. In ASP.NET Core, WebApplication.CreateBuilder() is a Facade over DI container setup, configuration, logging, and middleware pipeline construction.

In FixIt Pro, facade.DispatchJob(...) is one line for the caller. Underneath, five subsystems coordinate in a specific order. The caller doesn't know. The caller doesn't need to know. That's the point.


Repo Structure for This Episode

github.com/antonlungameni/fixit-pro-design-patterns

fixit-pro-design-patterns/
├── csharp/Structural/10-Facade/
│   ├── Subsystem/Scheduler.cs
│   ├── Subsystem/HandymanAssigner.cs
│   ├── Subsystem/NotificationService.cs
│   ├── Subsystem/JobCardRegistry.cs
│   ├── Subsystem/BillingService.cs
│   ├── Facade/JobDispatchFacade.cs
│   └── Program.cs
└── typescript/Structural/10-Facade/
    ├── Subsystem/Scheduler.ts
    ├── Subsystem/HandymanAssigner.ts
    ├── Subsystem/NotificationService.ts
    ├── Subsystem/JobCardRegistry.ts
    ├── Subsystem/BillingService.ts
    ├── Facade/JobDispatchFacade.ts
    └── App.ts

Previous: #09 — Decorator Pattern
Next up: #11 — Flyweight Pattern
Sharing common job metadata — tool types, material specs, category descriptions — across thousands of job cards in memory without duplicating data.