Skip to content
wannacode

Adapter and Facade

In short: Adapter translates a foreign interface into the one your code expects, while Facade hides several subsystems (stock, payment, invoice, email) behind a single method.

Problem

The new provider doesn't fit

The shop pays through its own interface: Charge takes the amount in euros and says whether the payment went through. The shop doesn't know who is behind it:

interface IPaymentGateway
{
bool Charge(decimal amount);
}
class Shop
{
private readonly IPaymentGateway gateway;
public Shop(IPaymentGateway gateway)
{
this.gateway = gateway;
}
public void Pay(string order, decimal amount)
{
if (gateway.Charge(amount))
{
Console.WriteLine(order + ": paid");
}
else
{
Console.WriteLine(order + ": declined");
}
}
}

The company switches its payment provider to FastPay, which charges lower fees. FastPay ships a ready-made library:

// The FastPay library: foreign code you can't change.
class FastPayClient
{
public string MakePayment(string currency, long amountInCents, Dictionary<string, string> options)
{
Console.WriteLine($"FastPay: {amountInCents} {currency}");
if (amountInCents > 100000)
{
return "DECLINED";
}
return "APPROVED";
}
}

It does the same job, in a different shape. The method is called MakePayment, wants a currency, an amount in cents and options, and returns the text "APPROVED" or "DECLINED" instead of yes or no. Handed straight to the shop, it doesn't fit:

var shop = new Shop(new FastPayClient());
Error
error CS1503: Argument 1: cannot convert from 'FastPayClient' to 'IPaymentGateway'

The library is foreign code: you can't add a method to it. Rewriting the shop for FastPay means touching every place that pays, and touching it again with the next provider.

The idea

Two wrappers, two different goals

Both patterns in this lesson wrap foreign code, but they solve different problems.

An Adapter is a class with the shape your code expects, which calls the foreign object inside. It translates the names, the units and the result. Like a travel plug adapter: your charger's plug doesn't fit the socket abroad, the adapter fits both. It adds nothing, it only changes the shape.

A Facade is one simple entry point in front of many classes that have to be used in a certain order. Like a hotel reception: you don't call the cleaners, the kitchen and accounting one by one, you tell the reception.

The difference in short:

  • an adapter changes the shape of one interface so it fits another;
  • a facade simplifies working with many classes by putting the usual sequence behind one method.

See it

The adapter translates

FastPayAdapter implements IPaymentGateway. Inside, it keeps the FastPay client and translates: euros to cents, "EUR" and empty options, the text to yes or no:

class FastPayAdapter : IPaymentGateway
{
private readonly FastPayClient client;
public FastPayAdapter(FastPayClient client)
{
this.client = client;
}
public bool Charge(decimal amount)
{
long cents = (long)Math.Round(amount * 100, MidpointRounding.AwayFromZero);
string status = client.MakePayment("EUR", cents, new Dictionary<string, string>());
return status == "APPROVED";
}
}

The shop gets the adapter instead of the client. Nothing in it changes:

var shop = new Shop(new FastPayAdapter(new FastPayClient()));
shop.Pay("#1042", 19.99m);
shop.Pay("#1043", 1299m);
Output
FastPay: 1999 EUR
#1042: paid
FastPay: 129900 EUR
#1043: declined

The €1299 TV is above FastPay's limit (€1000), so it is declined. The shop learns about it in its own language: false.

The scene shows the adapter, then the facade from the next card:

var payments = new FastPayAdapter(new FastPayClient());
Console.WriteLine(payments.Charge(19.99m));
var checkout = new CheckoutFacade(new Stock(), payments, new Invoices(), new Mailer());
checkout.PlaceOrder("Kettle", 24.90m);
Step by step, in words
  1. The shop calls Charge(amount in euros). FastPayClient only has MakePayment(currency, cents, options), so a direct call bounces off.
  2. FastPayAdapter steps in between: outside it is IPaymentGateway with Charge, inside it keeps the FastPay client.
  3. Charge(19.99): the adapter rounds euros to cents, 1999, and calls MakePayment with "EUR".
  4. FastPay answers "APPROVED", and the adapter turns it into True: the shop's own language.
  5. CheckoutFacade gets the warehouse, the payments (the same adapter), the invoices and the email.
  6. PlaceOrder: first the warehouse reserves the kettle.
  7. Then the payment, through the adapter again: €24.90 becomes 2490 cents.
  8. It went through, so the facade issues the invoice and sends the email. The caller wrote one line.
More precisely, if you are curious
  • .NET has adapters too. StreamReader turns a byte stream (Stream) into a text reader, and ReadOnlyCollection<T> shows a list as a read-only collection.
  • The adapter keeps the foreign object in a field (composition). It could inherit from it instead, but then the shop also sees every FastPay method, and one adapter can't wrap a client that already exists.

See it

The facade in front of four classes

The payment is only one step of an order. Before it, the warehouse reserves the item, and after it come the invoice and the email. If the payment is declined, the warehouse releases the item. Each step is a small class:

class Stock
{
public bool Reserve(string item)
{
Console.WriteLine("Warehouse: reserving " + item);
return true;
}
public void Release(string item)
{
Console.WriteLine("Warehouse: releasing " + item);
}
}
class Invoices
{
public void Issue(string item, decimal amount)
{
Console.WriteLine($"Invoice: {item}, €{amount:F2}");
}
}
class Mailer
{
public void Send(string text)
{
Console.WriteLine("Email: " + text);
}
}

Without a facade, every place that takes an order (the website, the mobile app, phone orders) writes the same thing, in the same order:

if (stock.Reserve(item))
{
if (payments.Charge(amount))
{
invoices.Issue(item, amount);
mailer.Send("confirmation for " + item);
}
else
{
stock.Release(item);
}
}

If one place forgets stock.Release, the item stays reserved for an order that was never paid. CheckoutFacade writes the order once:

class CheckoutFacade
{
private readonly Stock stock;
private readonly IPaymentGateway payments;
private readonly Invoices invoices;
private readonly Mailer mailer;
public CheckoutFacade(Stock stock, IPaymentGateway payments, Invoices invoices, Mailer mailer)
{
this.stock = stock;
this.payments = payments;
this.invoices = invoices;
this.mailer = mailer;
}
public bool PlaceOrder(string item, decimal amount)
{
if (!stock.Reserve(item))
{
return false;
}
if (!payments.Charge(amount))
{
stock.Release(item);
return false;
}
invoices.Issue(item, amount);
mailer.Send("confirmation for " + item);
return true;
}
}

The payments in the facade are an IPaymentGateway, handed in from outside (dependency injection from the lesson "Singleton and Dependency Injection"). Here it is the adapter from the previous card. The two patterns work together:

var payments = new FastPayAdapter(new FastPayClient());
var checkout = new CheckoutFacade(new Stock(), payments, new Invoices(), new Mailer());
checkout.PlaceOrder("Kettle", 24.90m);
Output
Warehouse: reserving Kettle
FastPay: 2490 EUR
Invoice: Kettle, €24.90
Email: confirmation for Kettle

A facade isn't a wall. The classes behind it can still be used directly when you need something off the usual path. Accounting issues the invoice again:

var invoices = new Invoices();
invoices.Issue("Kettle", 24.90m);
Output
Invoice: Kettle, €24.90

Try it

Predict the output

The classes are from the cards "The adapter translates" and "The facade in front of four classes". What does the program print?

var payments = new FastPayAdapter(new FastPayClient());
var checkout = new CheckoutFacade(new Stock(), payments, new Invoices(), new Mailer());
checkout.PlaceOrder("TV", 1299m);
Answer

Try it

Find the bug

A colleague "simplified" the adapter: cents are a whole number anyway. The shop also sells coffee by weight. A customer buys 375 g at €13.99 per kilogram (order #1044):
var shop = new Shop(new FastPayAdapter(new FastPayClient()));
shop.Pay("#1044", 0.375m * 13.99m);
Output
FastPay: 524 EUR
#1044: paid
375 g at €13.99 is €5.24625, that is €5.25. FastPay took 524 cents, one short. On which line of the adapter is the bug?

Pick the line with the bug.

More precisely, if you are curious

Try it yourself:

Console.WriteLine(0.375m * 13.99m * 100);
Output
524.62500
That is why money in C# is decimal, not double: with double, 19.99 * 100 gives 1998.9999999999998, just like in Java. decimal keeps the decimal digits exactly, but fractions of a cent still have to be rounded, and the rule (MidpointRounding) is written explicitly.

The idea

When you don't need them

You don't need an adapter when both sides are your own code. Then change one side to fit the other instead of putting a translator between them. An adapter is for code you can't touch: a foreign library, an old system, another team's service.

You don't need a facade when the sequence is used in one place. A single function there is enough. A facade also has a trap: it easily becomes the class everything goes through, with a method for every need of every screen. Keep only the usual paths in it, and call the classes directly for the rest.

Both patterns are thin. An adapter that starts deciding business rules (discounts, limits) isn't an adapter anymore. Neither is a facade that computes amounts itself instead of calling the classes that do.

Exercise

A second provider

Solve it in your own editor.

PayNow. The company adds a second provider, PayNow. Its library wants the cents first and the currency second, and returns a number: 0 means success, anything else is a decline.

class PayNowApi
{
public int Send(int cents, string currency)
{
Console.WriteLine($"PayNow: {currency} {cents}");
return 0;
}
}

Write PayNowAdapter so the shop pays through PayNow without changes:

var shop = new Shop(new PayNowAdapter(new PayNowApi()));
shop.Pay("#1042", 19.99m);
Output
PayNow: EUR 1999
#1042: paid

More tasks

  • Cancelling an order. Add CancelOrder(item) to the facade: the warehouse releases the item, and the customer gets an email "order cancelled for …".
  • A test provider. Write FakeGateway : IPaymentGateway that declines any amount over €50. Hand it to the facade and check that the warehouse releases the item on a decline.
  • The invoice fails. Let issuing the invoice throw an exception. The money has already been taken. What should the facade do? Decide and write it: for example, carry on with the email and record the error so the invoice is issued later.

Test

Test yourself

1. The provider's library has a method with a different name, units and result from what your code expects. Which pattern fits?
2. The website, the mobile app and phone orders repeat the same four steps in the same order. Which pattern fits?
3. With a facade in place, can the warehouse and the invoices still be used directly?
4. The adapter turns €19.99 into cents by cutting off the fraction. What does the provider get?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·