Mastering C# Patterns: Facade, Fluent Builder & Options
C# Design Patterns: Facade, Fluent Builder & Options
Design patterns in C# exist to solve common software problems. Today, we are going to look at how to manage complex workflows using the Facade, Builder (Fluent Pipeline), and Options patterns.
Let's break it down step by step in a clear, practical way, focusing on a real-world e-commerce order process.
1. What is the Facade Pattern?
The Facade pattern provides a simplified, higher-level interface to a complex subsystem of classes.
Imagine placing an order. Behind the scenes, five different things need to happen:
CheckInventory()ProcessPayment()CreateOrder()ArrangeShipping()SendNotification()
Instead of forcing your OrderController to talk to five different services, you create an OrderFacade.
Classic Facade Example
public class OrderFacade
{
private readonly IInventoryService _inventory;
private readonly IPaymentService _payment;
private readonly IOrderService _order;
private readonly IShippingService _shipping;
private readonly INotificationService _notification;
public OrderFacade(
IInventoryService inventory,
IPaymentService payment,
IOrderService order,
IShippingService shipping,
INotificationService notification)
{
_inventory = inventory;
_payment = payment;
_order = order;
_shipping = shipping;
_notification = notification;
}
public async Task PlaceOrderAsync(OrderRequest request)
{
await _inventory.CheckInventoryAsync(request);
await _payment.ProcessPaymentAsync(request);
await _order.CreateOrderAsync(request);
await _shipping.ArrangeShippingAsync(request);
await _notification.SendNotificationAsync(request);
}
}
Now, your Controller simply calls await _orderFacade.PlaceOrderAsync(request);. Clean, readable, and simple!
2. Is Facade Just Encapsulation?
This is a great question. Yes and no.
While both concepts involve "hiding" things, their intents are different:
| Concept | Purpose | Analogy |
|---|---|---|
| Encapsulation | Hides the internal state and logic of a single object to protect its integrity. | A car engine's internal pistons and spark plugs are hidden under the hood. |
| Facade | Hides the structural complexity of multiple subsystems behind a single interface. | The car's steering wheel and pedals (Facade) control the engine, transmission, and brakes (Subsystems). |
Facade uses encapsulation principles, but applied at an architectural/subsystem level rather than a class level.
3. The Problem: "I don't want to send a notification this time!"
Let's say in AdminController, you want to call PlaceOrderAsync, but you do not want to send a notification to the user.
With our strict OrderFacade, you are stuck. You'd either have to:
- Pass a boolean flag
PlaceOrderAsync(request, sendNotification: false)(which gets messy if you have 10 steps). - Create a completely new method
PlaceOrderWithoutNotificationAsync().
This is where Builder (Fluent Pipeline) and Options patterns come to the rescue!
4. The Fluent Builder Pattern (Pipeline)
You asked: "Can I do _service.CheckInventory().CreateOrder()...?"
Absolutely. This is known as a Fluent Builder or Fluent Pipeline. Instead of executing everything immediately, you "build" a pipeline of steps and then execute them at the end.
Step 1: Create the Fluent Interface
public interface IOrderPipeline
{
IOrderPipeline CheckInventory();
IOrderPipeline ProcessPayment();
IOrderPipeline CreateOrder();
IOrderPipeline ArrangeShipping();
IOrderPipeline SendNotification();
Task ExecuteAsync(OrderRequest request);
}
Step 2: Implement the Builder
public class OrderPipelineBuilder : IOrderPipeline
{
private readonly IInventoryService _inventory;
private readonly IPaymentService _payment;
private readonly IOrderService _order;
private readonly IShippingService _shipping;
private readonly INotificationService _notification;
private bool _checkInventory;
private bool _processPayment;
private bool _createOrder;
private bool _arrangeShipping;
private bool _sendNotification;
public OrderPipelineBuilder(
IInventoryService inventory,
IPaymentService payment,
IOrderService order,
IShippingService shipping,
INotificationService notification)
{
_inventory = inventory;
_payment = payment;
_order = order;
_shipping = shipping;
_notification = notification;
}
public IOrderPipeline CheckInventory() { _checkInventory = true; return this; }
public IOrderPipeline ProcessPayment() { _processPayment = true; return this; }
public IOrderPipeline CreateOrder() { _createOrder = true; return this; }
public IOrderPipeline ArrangeShipping() { _arrangeShipping = true; return this; }
public IOrderPipeline SendNotification() { _sendNotification = true; return this; }
public async Task ExecuteAsync(OrderRequest request)
{
if (_checkInventory) await _inventory.CheckInventoryAsync(request);
if (_processPayment) await _payment.ProcessPaymentAsync(request);
if (_createOrder) await _order.CreateOrderAsync(request);
if (_arrangeShipping) await _shipping.ArrangeShippingAsync(request);
// Conditional execution based on what was chained!
if (_sendNotification) await _notification.SendNotificationAsync(request);
}
}
Step 3: Controller Usage
Now, you can completely customize the flow per controller!
// In CustomerController
await _pipeline
.CheckInventory()
.ProcessPayment()
.CreateOrder()
.ArrangeShipping()
.SendNotification() // Normal flow
.ExecuteAsync(request);
// In AdminController (Skipping Notification!)
await _pipeline
.CheckInventory()
.ProcessPayment()
.CreateOrder()
.ArrangeShipping() // Notification skipped!
.ExecuteAsync(request);
5. The Options Pattern Alternative
If building a fluent pipeline feels like overkill, the standard .NET way to handle optional behaviors is the Options Pattern.
Instead of chaining methods, you pass a configuration object to your standard Facade.
// 1. Define Options
public class OrderOptions
{
public bool SkipNotification { get; set; } = false;
public bool SkipInventoryCheck { get; set; } = false;
}
// 2. Modify Facade
public class ConfigurableOrderFacade
{
private readonly IInventoryService _inventory;
private readonly IPaymentService _payment;
private readonly IOrderService _order;
private readonly IShippingService _shipping;
private readonly INotificationService _notification;
public ConfigurableOrderFacade(
IInventoryService inventory,
IPaymentService payment,
IOrderService order,
IShippingService shipping,
INotificationService notification)
{
_inventory = inventory;
_payment = payment;
_order = order;
_shipping = shipping;
_notification = notification;
}
public async Task PlaceOrderAsync(OrderRequest request, Action<OrderOptions> configureOptions = null)
{
var options = new OrderOptions();
configureOptions?.Invoke(options); // Apply overrides
if (!options.SkipInventoryCheck)
await _inventory.CheckInventoryAsync(request);
await _payment.ProcessPaymentAsync(request);
await _order.CreateOrderAsync(request);
await _shipping.ArrangeShippingAsync(request);
if (!options.SkipNotification)
await _notification.SendNotificationAsync(request);
}
}
Usage in Controller:
// Standard call
await _facade.PlaceOrderAsync(request);
// Admin call skipping notification
await _facade.PlaceOrderAsync(request, options =>
{
options.SkipNotification = true;
});
This is the same pattern Microsoft uses extensively in ASP.NET Core (e.g., AddSwaggerGen(options => ...)).
Quick Summary
| Pattern | Best Used For |
|---|---|
| Facade | Hiding complex, multi-step operations behind one simple method call. |
| Encapsulation | Hiding internal class data/state to prevent outside interference. |
| Fluent Builder | When you want an intuitive, chainable method().method() syntax to conditionally build a workflow. |
| Options Pattern | When you have a standard workflow but want to pass simple configuration flags to toggle behaviors. |
Key Takeaway: If you want that beautiful _service.CheckInventory().CreateOrder().Execute() syntax, use the Fluent Builder pattern. If you want to keep the single PlaceOrderAsync() call but need flexibility, use the Options pattern!
Enjoyed this article? Share it with your network!