Refactoring external coupling
External coupling involves two pieces of code being coupled through the common use of a data format or protocol. Issues arise when coupling to a specific data format if the data format is less stable than the classes that use it. If the data format is less stable and tends to change quite often, you may want to refactor the code to not be directly coupled to the data format.
This coupling can be mitigating by performing the Introduce Adapter pattern. The Adapter Pattern converts the less stable data format to a more stable format. It's common that going from a persistent storage source to an in-memory representation of data that a specific data format is used for persistent storage then used to create objects (in-memory representations). Coupling the object-oriented class to this flat data format means the class takes on the responsibility of conversion. This can become out of control if there need to be many different data formats for this object to support many different persistence mechanisms. Data formats often need to change independently of the class(es) that use it due to size and performance requirements, which leads to the domain class having a dependence on instability.
For example, we may need to instantiate an Invoice object based on a flat data format such as the following:
/// <summary>/// LineItemData shape for/// serialization of line item data/// </summary>[StructLayout(LayoutKind.Sequential)]struct LineItemData{ public float Price; public float Discount; public float Quantity; public String Description;}/// <summary>/// InvoiceData shape for/// serialization of invoice data/// </summary>[StructLayout(LayoutKind.Sequential)]struct InvoiceData{ public LineItemData[] LineItemData;}public class Invoice{ public Invoice(InvoiceData invoiceData) { InvoiceLineItems = new List<InvoiceLineItem>(invoiceData.LineItemData.Length); foreach (LineItemData lineItemData in invoiceData.LineItemData) { InvoiceLineItems.Add(new InvoiceLineItem() { Price = lineItemData.Price, Discount = lineItemData.Discount, Description = lineItemData.Description, Quantity = lineItemData.Quantity }); } } //...}
We have two structures intended to be used for getting data in and out of some sort of flat storage (file system, over the wire, and so on): LineItemData and InvoiceData. The Invoice class populates itself when constructed with an InvoiceData instance.
This obviously couples the Invoice class directly with InvoiceData and indirectly with LineItemData and their instability. We can get around this problem by performing the Introduce Adapter refactoring.
This refactoring starts with abstracting the InvoiceData class by creating an adapter, like InvoiceDataAdapter. The Invoice class would then be changed to make use of InvoiceDataAdapter instead of directly using InvoiceData. This refactoring would result in something like the following:
/// <summary>/// Provides a translation of InvoiceData/// into more appropriate interface/// </summary>public class InvoiceDataAdapter{ List<InvoiceLineItem> invoiceLineItems; public InvoiceDataAdapter(InvoiceData invoiceData) { invoiceLineItems = new List<InvoiceLineItem>(invoiceData.LineItemData.Length); foreach (LineItemData lineItemData in invoiceData.LineItemData) { invoiceLineItems.Add(new InvoiceLineItem() { Price = lineItemData.Price, Discount = lineItemData.Discount, Description = lineItemData.Description, Quantity = lineItemData.Quantity }); } } public IEnumerable<InvoiceLineItem> InvoiceLineItems { get { return invoiceLineItems; } }}/// <summary>/// Invoice class that ues InvoiceDataAdapter/// </summary>public class Invoice{ public List<InvoiceLineItem> InvoiceLineItems { get; set; } public Invoice(InvoiceDataAdapter adapter) { InvoiceLineItems = new List<InvoiceLineItem>(adapter.InvoiceLineItems)); } //...}