Interface-based design
If you're already coupled to a particular class, replacing use of that class with an interface and having the other class implement that interface is the easiest way to change the coupling between two classes. This reduces coupling from content coupling to a more loosely coupled message coupling.
If the requirements of the other class are very complex or a series of members must come from a single source, using interfaces is often the best solution. Having to hook up several delegates or several events becomes tedious and error prone when a single reference to an object that implements a particular interface is so simple. Imagine if implementing a Windows Form wasn't as simple as deriving from Form and having to register a number of delegates or events.
If you find that implementers of the interface would find default or base implementation for them to be useful, implementing that interface may best be done with an abstract class.
Our Invoice class is a good example of something that can be more loosely coupled through interface-based design. It currently implements the calculation of grand totals through interface-based design and the strategy pattern (see Chapter 9). This could have easily been implemented through direct use of a particular class. For example:
/// <summary>/// Service to enapsulate calculation of/// grand totals./// </summary>public class InvoiceGrandTotalService{ public float CalculateGrandTotal(float invoiceSubTotal, float invoiceTotalTax) { return invoiceSubTotal + invoiceTotalTax; }}/// <summary>/// Invoice class that uses/// <seealso cref="InvoiceGrandTotalService"/>/// </summary>public class Invoice{ InvoiceGrandTotalService invoiceGrandTotalService = new InvoiceGrandTotalService(); public List<InvoiceLineItem> InvoiceLineItems { get; set; } public Invoice(IEnumerable<InvoiceLineItem> invoiceLineItems) { InvoiceLineItems = new List<InvoiceLineItem>(invoiceLineItems); } public float CalculateGrandTotal(float invoiceSubTotal, float invoiceTotalTax) { return invoiceGrandTotalService.CalculateGrandTotal(invoiceSubTotal, invoiceTotalTax); } //...}
In this example, we've created the InvoiceGrandTotalService class that contains the CalculateGrandTotal method. We then instantiate this class in the Invoice class and make reference to it in the CalculateGrandTotal method.
We've given away the surprise with this refactoring. We're obviously going to replace direct use of the class with an interface. Since we essentially need a reference to an object right from the start, and to effectively loosen the coupling, we begin refactoring by accepting a reference to an IInvoiceGrandTotalStrategy object in the constructor. We then change our InvoiceGrandTotalService field to an IInvoiceGrandTotalStrategy field and initialize it in the constructor. We finish our refactoring by replacing references from invoiceGrandTotalServcie to invoiceGrandTotalStrategy. The resulting refactoring will look similar to the following:
/// <summary>/// Invoice class that uses/// <seealso cref="IInvoiceGrandTotalStrategy"/>/// </summary>public class Invoice{ private IInvoiceGrandTotalStrategy invoiceGrandTotalStrategy; public List<InvoiceLineItem> InvoiceLineItems { get; set; } public Invoice(IEnumerable<InvoiceLineItem> invoiceLineItems, IInvoiceGrandTotalStrategy invoiceGrandTotalStrategy) { InvoiceLineItems = new List<InvoiceLineItem>(invoiceLineItems); this.invoiceGrandTotalStrategy = invoiceGrandTotalStrategy; } public float CalculateGrandTotal(float invoiceSubTotal, float invoiceTotalTax) { return invoiceGrandTotalStrategy.CalculateGrandTotal(invoiceSubTotal, invoiceTotalTax); } //...}
If you find that the relationship between the two classes is the invocation of one or two methods that return or update data, you may find that delegates are the best way of refactoring.