Press "Enter" to skip to content

How to implement the Adapter design pattern in C#

What is the Adapter Design Pattern?

The Adapter design pattern is a structural pattern, used to allow objects of one type to be used in an application or function that expects objects of a different type. You may use this pattern when your program needs to submit data to a web service or API, which is expecting the data as a different datatype.

In this example, we’ll simulate a program that creates an Order object (with Customer and OrderItem objects in its properties) and needs to submit a shipping request to a different program, that expects objects of a different datatype as its input.

Pattern version

We’ll start with a typical Order object.

On line 6, it has a Customer property, which holds a Customer object. And on line 7, it has an OrderItems List property that holds OrderItem objects.

namespace DesignPatternsCSharpNet6.Adapter.Pattern;
public class Order
{
    public int Id { get; set; }
    public Customer Customer { get; set; }
    public List<OrderItem> OrderItems { get; } = new();
}

The Customer class contains properties for the customer’s name and address information.

namespace DesignPatternsCSharpNet6.Adapter.Pattern;
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string StreetAddress { get; set; }
    public string City { get; set; }
    public string StateProvince { get; set; }
    public string PostalCode { get; set; }
}

The InventoryItem class contains properties about the item being sold.

namespace DesignPatternsCSharpNet6.Adapter.Pattern;
public class InventoryItem
{
    public int Id { get; set; }
    public string Description { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public decimal Weight { get; set; }
    public bool IsFragile { get; set; }
}

The OrderItem class holds the InventoryItem object and the Quantity being purchased in this Order.

namespace DesignPatternsCSharpNet6.Adapter.Pattern;
public class OrderItem
{
    public InventoryItem Item { get; set; }
    public int Quantity { get; set; }
}

When we want to schedule shipping for this order, we need to send it to a different program/service. That program is expecting a ShippingRequest object, which has significantly different properties from the Order object and the objects it holds.

namespace DesignPatternsCSharpNet6.Adapter.Pattern;
public class ShippingRequest
{
    public Guid Id { get; set; }
    public Address ShipFromAddress { get; set; }
    public Address ShipToAddress { get; set; }
    public int NumberOfItems { get; set; }
    public decimal TotalWeight { get; set; }
    public bool ContainsFragileItem { get; set; }
    public class Address
    {
        public string StreetAddress { get; set; }
        public string City { get; set; }
        public string StateProvince { get; set; }
        public string PostalCode { get; set; }
    }
}

To create a ShippingRequest object that can be submitted to the shipping service, we’ll use this ShippingRequestAdapter class, and its CreateShippingRequestFromOrder function.

On lines 7-14, this function creates a “fromAddress” variable, using our company’s address. In a real program, this probably would be pulled from a database or config file. For this sample, I hard-coded it.

On lines 16-23, we create a “toAddress” variable from the properties of the Order’s Customer object.

On lines 25-33, we create the ShippingRequest object we need, using the address variables and gathering information from the Order.OrderItems, such as the number of items in the shipment, their total weight, and whether or not any of the items are fragile.

namespace DesignPatternsCSharpNet6.Adapter.Pattern;
public static class ShippingRequestAdapter
{
    public static ShippingRequest CreateShippingRequestFromOrder(Order order)
    {
        ShippingRequest.Address fromAddress =
            new ShippingRequest.Address
            {
                StreetAddress = "1234 Main Street",
                City = "Houston",
                StateProvince = "TX",
                PostalCode = "77777"
            };
        ShippingRequest.Address toAddress =
            new ShippingRequest.Address
            {
                StreetAddress = order.Customer.StreetAddress,
                City = order.Customer.City,
                StateProvince = order.Customer.StateProvince,
                PostalCode = order.Customer.PostalCode
            };
        return new ShippingRequest
        {
            Id = Guid.NewGuid(),
            ShipFromAddress = fromAddress,
            ShipToAddress = toAddress,
            NumberOfItems = order.OrderItems.Sum(i => i.Quantity),
            TotalWeight = order.OrderItems.Sum(i => i.Quantity * i.Item.Weight),
            ContainsFragileItem = order.OrderItems.Any(i => i.Item.IsFragile)
        };
    }
}

Summary

With more and more programs being decoupled, often by moving them into separate web/micro-services, this pattern is very common. Your program needs to send information to another program, and you cannot change the other program. So, you need to convert your object into an object (or a serialized object) the other program understands. That’s where the Adapter pattern can be used.

You may hear about this type of thing with working with DTOs (Data Transfer Objects) – objects used to transfer data from one application and another.

While you can create your own Adapter classes, you may decide to use libraries like AutoMapper or Mapster, which let you write the mapping between two objects in a more declarative way.

The Adapter design pattern is used when you need to convert an object to a different object, so you can send it to a separate application. If you just want your object to look differently in different parts of the same program, you might want to use the Decorator design pattern.

    Leave a Reply

    Your email address will not be published.

    This site uses Akismet to reduce spam. Learn how your comment data is processed.