Summary: in this tutorial, you’ll learn how to use the C# Adapter pattern to enable classes of incompatible interfaces to work together.
Introduction to the C# Adapter pattern
The Adapter pattern converts an interface into another interface that clients expect. The Adapter pattern allows classes of incompatible interfaces to work together.
Suppose you’re traveling to a foreign country and you want to charge your phone. The problem is that the electric socket in that country has a different shape than your phone charger’s plug.
To solve this problem, you can use an adapter that converts the foreign socket into your phone charger’s plug shape.
This adapter serves as a wrapper that translates the interface of the foreign socket into the interface that your phone charger expects. By doing this, you can charge your phone without changing the foreign socket.
Similarly, the Adapter pattern creates an adapter that converts the interface of an object into an interface that clients expect, allowing them to work together without modifying their code.
The Adapter pattern has two variants: object adapter and class adapter.
Object Adapter
The following UML diagram illustrates the object adapter:
In this Object Adapter UML diagram:
ITarget
interface: This is the interface that the client expects to work with. It’s also the interface that theAdapter
class will implement.Adaptee
: is the class that has an interface that is not compatible with theITarget
interface. It is the class that theAdapter
class will wrap and adapt.Adapter
: is the class that implements the target interface and wraps theAdaptee
object. TheAdapter
class is responsible for translating the calls from theITarget
interface to theAdaptee
‘s interface and delegating the work to it.
In this pattern, the Adapter
has a member which is the Adaptee
object. The Operation()
method of the Adapter
class calls the SpecificOperation()
method of the Adaptee
object.
Object Adapter example
Suppose you need to implement a payment feature in your system application. And you have to use a third-party class called PaymentProcessor
.
The PaymentProcessor
class has a method called ProcessPayment
, which accepts a decimal
argument that represents the payment amount:
public class PaymentProcessor
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"You have paid {amount:C}.");
}
}
Code language: C# (cs)
But your application uses an interface called IPaymentProvider
with a method
. The problem is that the MakePayment
method accepts two arguments: a string that represents the payment details and a decimal value that represents the payment amount:MakePayment
public interface IPaymentProvider
{
void MakePayment(string details, decimal amount);
}
Code language: C# (cs)
As you can see, the interface that your application expects is IPaymentProvider
which is incompatible with the class PaymentProcessor
.
To solve this problem, you can apply the object Adapter pattern.
To use the PaymentProcessor
with your IPaymentProvider
interface, you can create an Adapter
class called PaymentProviderAdapter
.
The PaymentProviderAdapter
needs to implement the IPaymentProvider
interface and wrap the
class. Also, the PaymentProcessor
MakePayment
method of the PaymnetProviderAdapter
needs to translate the call to the ProcessPayment
method of the
class:PaymentProcessor
// The adapter class that adapts the PaymentProcessor
// to the IPaymentProvider interface
public class PaymentProviderAdapter : IPaymentProvider
{
private readonly PaymentProcessor _paymentProcessor;
public PaymentProviderAdapter(PaymentProcessor paymentProcessor)
{
_paymentProcessor = paymentProcessor;
}
public void MakePayment(string details, decimal amount)
{
Console.WriteLine($"Making a payment for: {details}");
_paymentProcessor.ProcessPayment(amount);
}
}
Code language: C# (cs)
Now, you can use the PaymentProcessor
class in your application as follows:
public class Program
{
public static void Main(string[] args)
{
var paymentProcessor = new PaymentProcessor();
var paymentProvider = new PaymentProviderAdapter(paymentProcessor);
paymentProvider.MakePayment("C# design pattern course", 100.0m);
}
}
Code language: C# (cs)
In the Program
class:
- First, create an instance of the
class and create an instance of thePaymentProcess
PaymentProviderAdapter
class that wraps the
‘s object.PaymentProcess
- Second, call the
MakePayment
of the instance of thePaymentProviderAdapter
class to make a payment, which will in turn call theProcessPayment
method on thePaymentProcessor
class.
Put it all together.
namespace ObjectAdapter;
// The third-party PaymentProcessor class
public class PaymentProcessor
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"You have paid {amount:C}.");
}
}
// your application's IPaymentProvider interface
public interface IPaymentProvider
{
void MakePayment(string details, decimal amount);
}
// The adapter class that adapts the PaymentProcessor
// to the IPaymentProvider interface
public class PaymentProviderAdapter : IPaymentProvider
{
private readonly PaymentProcessor _paymentProcessor;
public PaymentProviderAdapter(PaymentProcessor paymentProcessor)
{
_paymentProcessor = paymentProcessor;
}
public void MakePayment(string details, decimal amount)
{
Console.WriteLine($"Making a payment for: {details}");
_paymentProcessor.ProcessPayment(amount);
}
}
public class Program
{
public static void Main(string[] args)
{
var paymentProcessor = new PaymentProcessor();
var paymentProvider = new PaymentProviderAdapter(paymentProcessor);
paymentProvider.MakePayment("C# design pattern course", 100.0m);
}
}
Code language: C# (cs)
Output:
Making a payment for: C# design pattern course
You have paid $100.00.
Code language: C# (cs)
Class Adapter pattern
The Object Adapter pattern uses composition to wrap the incompatible class. Meanwhile, the class Adapter pattern uses inheritance.
In the Class Adapter pattern, the Adapter
class extends both the Adaptee
and target classes. The Adapter
class then overrides the method of the target class and calls the corresponding method of the Adaptee
interface.
C# doesn’t support multiple inheritances that allow a class to extend two or more classes. But, a class can extend a class and implement multiple interfaces.
The following UML diagram illustrates the Class Adapter pattern:
In this diagram, the Adapter
class inherits from the Adaptee
class instead of composing it. And the Operation()
method of the Adapter
class calls the SpecificOperation()
method of the Adaptee
class.
Class Adapter example
The following is the same as the example that uses the object adapter pattern, but uses the Class Adapter pattern instead:
namespace ClassAdapter;
// The third-party PaymentProcessor class
public class PaymentProcessor
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"You have paid {amount:C}.");
}
}
// Our application's IPaymentProvider interface
public interface IPaymentProvider
{
void MakePayment(string details, decimal amount);
}
// The adapter class that adapts the PaymentProcessor to the IPaymentProvider interface
public class PaymentProviderAdapter : PaymentProcessor, IPaymentProvider
{
public void MakePayment(string details, decimal amount)
{
Console.WriteLine($"Making a payment for: {details}");
ProcessPayment(amount);
}
}
class Program
{
public static void Main(string[] args)
{
var paymentProvider = new PaymentProviderAdapter();
paymentProvider.MakePayment("C# design pattern course", 100.0m);
}
}
Code language: C# (cs)
Summary
- The Adapter pattern allows the objects with incompatible interfaces to work together.
- The Object Adapter pattern uses the composition while the Class Adapter class uses inheritance.