Facade Design Pattern
The Facade design pattern provides a unified interface to a set of interfaces in a subsystem. This pattern defines a higher-level interface that makes the subsystem easier to use.
I’ve dealt with major codebases where there’s large number of subsystems, and when I understood this pattern - it was easy to identify when this pattern was being used.
Participants
- Facade (
MortgageApplication
): knows which subsystem classes are responsible for a request. delegates client requests to appropriate subsystem objects. - Subsystem classes (
Bank
,Credit
,Loan
): implement subsystem functionality. handle work assigned by the Facade object. They have no knowledge of the facade and keep no reference to it.
Intuitive Implementation:
1/// <summary>2/// Facade Design Pattern3/// </summary>4public class Program5{6static void Main()7{8Facade facade = new Facade();9facade.MethodA();10facade.MethodB();1112// Wait for user13Console.ReadKey();14}15}1617/// <summary>18/// The 'Subsystem ClassA' class19/// </summary>20public class SubSystemOne21{22public void MethodOne()23{24Console.WriteLine(" SubSystemOne Method");25}26}2728/// <summary>29/// The 'Subsystem ClassB' class30/// </summary>31public class SubSystemTwo32{33public void MethodTwo()34{35Console.WriteLine(" SubSystemTwo Method");36}37}3839/// <summary>40/// The 'Subsystem ClassC' class41/// </summary>42public class SubSystemThree43{44public void MethodThree()45{46Console.WriteLine(" SubSystemThree Method");47}48}4950/// <summary>51/// The 'Subsystem ClassD' class52/// </summary>53public class SubSystemFour54{55public void MethodFour()56{57Console.WriteLine(" SubSystemFour Method");58}59}6061/// <summary>62/// The 'Facade' class63/// </summary>64public class Facade65{66private readonly SubSystemOne one;67private readonly SubSystemTwo two;68private readonly SubSystemThree three;69private readonly SubSystemFour four;7071public Facade()72{73one = new SubSystemOne();74two = new SubSystemTwo();75three = new SubSystemThree();76four = new SubSystemFour();77}7879public void MethodA()80{81Console.WriteLine("\nMethodA() ---- ");82one.MethodOne();83two.MethodTwo();84four.MethodFour();85}8687public void MethodB()88{89Console.WriteLine("\nMethodB() ---- ");90two.MethodTwo();91three.MethodThree();92}93}
1MethodA() ----2SubSystemOne Method3SubSystemTwo Method4SubSystemFour Method56MethodB() ----7SubSystemTwo Method8SubSystemThree Method
Practical Example
Implement a MortgageApplication object which provides a simplified interface to a large subsystem of classes measuring the creditworthiness of an applicant.
Create a customer:
1public class Customer2{3public string Name { get; set; }4}
Create your subsystems:
1/// <summary>2/// The 'Subsystem ClassA' class3/// </summary>4public class Bank5{6public bool HasSufficientSavings(Customer c, int amount)7{8Console.WriteLine("Check bank for " + c.Name);9return true;10}11}1213/// <summary>14/// The 'Subsystem ClassB' class15/// </summary>16public class Credit17{18public bool HasGoodCredit(Customer c)19{20Console.WriteLine("Check credit for " + c.Name);21return true;22}23}2425/// <summary>26/// The 'Subsystem ClassC' class27/// </summary>28public class Loan29{30public bool HasNoBadLoans(Customer c)31{32Console.WriteLine("Check loans for " + c.Name);33return true;34}35}
Create the Facade class:
1/// <summary>2/// The 'Facade' class3/// </summary>4public class Mortgage5{6private readonly Bank bank = new Bank();7private readonly Loan loan = new Loan();8private readonly Credit credit = new Credit();910public bool IsEligible(Customer cust, int amount)11{12Console.WriteLine("{0} applies for {1:C} loan\n",13cust.Name, amount);1415bool eligible = true;1617// Check creditworthyness of applicant18if (!bank.HasSufficientSavings(cust, amount))19{20eligible = false;21}22else if (!loan.HasNoBadLoans(cust))23{24eligible = false;25}26else if (!credit.HasGoodCredit(cust))27{28eligible = false;29}30return eligible;31}32}
Run it:
1static void Main()2{3// Facade4Mortgage mortgage = new Mortgage();56// Evaluate mortgage eligibility for customer7Customer customer = new() { Name = "Giovany" };8bool eligible = mortgage.IsEligible(customer, 125000);910Console.WriteLine("\n" + customer.Name +11" has been " + (eligible ? "Approved" : "Rejected"));1213// Wait for user14Console.ReadKey();15}
1Giovany applies for $125,000.00 loan23Check bank for Giovany4Check loans for Giovany5Check credit for Giovany67Giovany has been Approved
Practical Example
Say we have a Web API where we might have multiple services/controllers.
1public interface IInventoryService2{3string[] Get();4}56internal class InventoryService : IInventoryService7{8public string[] Get()9{10return new[] { "Book", "Pen" };11}12}
1public interface INotificationService2{3void Send(string message);4}56internal class NotificaitonService : INotificationService7{8public void Send(string message)9{10Console.WriteLine($"Sending notification message: {message}");11}12}
1public interface IPaymentService2{3void Pay(double amount, string item);4}56internal class PaymentService : IPaymentService7{8public void Pay(double amount, string item)9{10Console.WriteLine($"Paying amount: {amount} for item: {item}");11}12}
1using Practice.Service;23var builder = WebApplication.CreateBuilder(args);45// Add services to the container.6builder.Services.AddSingleton<INotificationService, NotificaitonService>();7builder.Services.AddSingleton<IPaymentService, PaymentService>();8builder.Services.AddSingleton<IInventoryService, InventoryService>();910builder.Services.AddControllers();11// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle12builder.Services.AddEndpointsApiExplorer();13builder.Services.AddSwaggerGen();1415var app = builder.Build();1617// Configure the HTTP request pipeline.18if (app.Environment.IsDevelopment())19{20app.UseSwagger();21app.UseSwaggerUI();22}2324app.UseHttpsRedirection();2526app.UseAuthorization();2728app.MapControllers();2930app.Run();
1public record SubmitPaymentDto(string Item, double Amount);23public record SubmitNotificationDto(string Value);
Now, let’s define our subsystems:
1[ApiController]2[Route("api/[controller]")]3public class InventoryController : ControllerBase4{5private readonly IInventoryService m_inventoryService;67public InventoryController(IInventoryService m_inventoryService)8{9this.m_inventoryService = m_inventoryService;10}1112[HttpGet]13public IEnumerable<string> Get()14{15return m_inventoryService.Get();16}17}
1[ApiController]2[Route("api/[controller]")]3public class NotificationController : ControllerBase4{5private readonly INotificationService m_notificationService;67public NotificationController(INotificationService m_notificationService)8{9this.m_notificationService = m_notificationService;10}1112[HttpPost]13public void Post([FromBody] SubmitNotificationDto notification)14{15m_notificationService.Send(notification.Value);16}17}
1[ApiController]2[Route("api/[controller]")]3public class PaymentController : ControllerBase4{5private readonly IPaymentService m_paymentService;67public PaymentController(IPaymentService m_paymentService)8{9this.m_paymentService = m_paymentService;10}1112[HttpPost]13public void Post([FromBody] SubmitPaymentDto payment)14{15m_paymentService.Pay(payment.Amount, payment.Item);16}17}
Where the design pattern takes place is when the client needs to make request to each service via each single controller
which is not ideal. Therefore, we would use the Facade Design Pattern to solve this issue by creating PurchaseFacadeController
. Note that you may decouple this monolothic
architecture into seperate microservice (i.e Notification Microservice, etc).
1[ApiController]2[Route("api/purchase")]3public class PurchaseFacadeController : ControllerBase4{5// Subsystems6private readonly INotificationService m_notificationService;7private readonly IPaymentService m_paymentService;8private readonly IInventoryService m_inventoryService;910private readonly ILogger<PurchaseFacadeController> m_logger;1112public PurchaseFacadeController(13INotificationService m_notificationService,14IPaymentService paymentService,15IInventoryService inventoryService,16ILogger<PurchaseFacadeController> logger)17{18this.m_notificationService = m_notificationService;19m_paymentService = paymentService;20m_inventoryService = inventoryService;21m_logger = logger;22}2324[HttpPost]25public IActionResult Post([FromBody] SubmitPaymentDto payment)26{27var inventory = m_inventoryService.Get();2829if (!inventory.Any(item => item == payment.Item)) return BadRequest();3031m_paymentService.Pay(payment.Amount, payment.Item);3233m_notificationService.Send($"Item: {payment.Item} purchase with amount {payment.Amount}");3435m_logger.LogInformation($"Submitted a payment of {0} with amount {1}", payment.Item, payment.Amount);3637return Ok();38}39}
I hope this helps!
x, gio