Microsoft Visual Studio 2010: Improving Class Quality with Coupling

Exclusive offer: get 50% off this eBook here
Refactoring with Microsoft Visual Studio 2010

Refactoring with Microsoft Visual Studio 2010 — Save 50%

Refactor with Microsoft Visual Studio 2010 and evolve your software system to support new and ever-changing requirements by updating your C# code base with patterns and principles with this book and eBook

$35.99    $18.00
by Peter Ritchie | July 2010 | Enterprise Articles Microsoft

In the first part of this two-part article series by Peter Ritchie, author of Refactoring with Microsoft Visual Studio 2010, we covered principles related to cohesion and refactorings that increase cohesion. In this part, principles related to coupling are introduced and refactorings that decrease coupling are covered. We will see how coupling can be applied to increase code quality.

(For more resources on Microsoft products, see here.)

Coupling is the degree to which two things are related. Coupling and cohesion go hand in hand. Something that is highly-cohesive generally has low coupling. Coupling is a form of dependency.

There are many different types of coupling when we're talking about software design and development. The effort here isn't to make a decoupled design, it's to change the coupling. At some level, one piece of code is coupled to every other piece of code in the system—there is rarely any change you can make to change that. Some code is more highly-coupled to other code and uses the other code directly. Some code is more loosely-coupled and uses other code indirectly. Efforts at refactoring towards a more loosely-coupled design are about the degree to which coupling has been made indirect.

Code can be coupled to other code by a shared data format (external coupling). Code can be coupled to other code by the fact that it results in the execution of other code (control coupling). Code can be coupled to other code by the fact that it results in executing other code by way of an abstract interface ( message coupling). Code can be coupled to other code by the fact that they share data, usually in the form of parameters (data coupling ). Code can be coupled to other code by the fact that it has a subclass relationship ( subclass coupling). Code can also be coupled to other code by that fact that it directly references a type's public interface (content coupling ). The direction and degree to which a type is coupled can also help focus our refactoring efforts. Afferent coupling is the degree to which a type is depended upon (inward coupling). Efferent coupling is the degree to which a type depends on other types (outward coupling ). High afferent coupling can indicate that a type has too many responsibilities. It's trying to be everything to everyone and thus being used by everyone. High efferent coupling could indicate a type is very dependant. This becomes an issue when the types the class depends upon are in many different assemblies, suggesting a cohesion issue at the assembly layer.

Highly-coupled software is generally accepted to exhibit certain traits, and it can be hard to change. It's like pulling on the thread of a sweater; there are so many dependencies it's impossible to predict how much code will need to be modified in order to make a seemingly simple change. Highly-coupled code is also very rigid. It's hard to move or hard not to duplicate it outside its current context. It carries a lot of baggage (dependencies) with it that need to move with it as a unit. It's one thing to move the code you want to move, it's exponentially harder to move all its dependencies.

While good object-oriented design often promotes high cohesion, loosely coupled design and structure can easily fall to the wayside.

Refactoring subclass coupling

Refactoring subclass coupling involves removing the inheritance relationship between two classes.

Refactoring content coupling

Content coupling is one class directly referencing another. There are several tactics for refactoring away this coupling. Depending on the situation, one tactic may be more applicable than another. One tactic is to use interface-based design and remove the coupling to the content of the class and replace it with coupling to an interface that the other class now implements. Another tactic is to replace method calls into the other class with delegate invocations. A final tactic is to use events instead of direct method calls.

For any particular refactoring, a combination of these tactics may be the best solution. You may find that despite a one-to-one coupling between two classes, it's more appropriate to use a combination of tactics to refactor away the content coupling.

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. 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.

Refactoring with Microsoft Visual Studio 2010 Refactor with Microsoft Visual Studio 2010 and evolve your software system to support new and ever-changing requirements by updating your C# code base with patterns and principles with this book and eBook
Published: July 2010
eBook Price: $35.99
Book Price: $59.99
See more
Select your format and quantity:

(For more resources on Microsoft products, see here.)

Delegates

Loosely coupling to delegates requires that another class (usually the other class) inform the class of what delegates to call. This is generally only useful if there are only one or two delegates. Use of another class that contains many methods that are used by the class becomes problematic to loosen coupling by using delegates simply due to the number of delegate initializations that need to occur.

Use of delegates is generally called callbacks. A callback is something that another class calls in order to obtain values or functionality from an external source. If you find that use of the other class requires values or functionality from the other class, use of callbacks may be an appropriate solution. This is particularly true if this is a comprehensive value or a single functionality.

In our Invoice class, we really only have one method that we need to inject into an Invoice object. This may be a perfect scenario for a callback.

Refactoring to a callback is much the same as refactoring to interface-based design. In our particular case, we begin by accepting a Func<float, float, float> parameter. Then, add a Func<float, float, float> field named calculateGrandTotalCallback to the Invoice class. Next we need to initialize the calculateGrandTotalCallback field in the constructor. Finally, we need to replace the call to CalculateGrandTotal to an invocation of the calculateGrandTotalCallbackfield. The refactoring should result in something similar to the following:

/// <summary>
/// Example of using callback
/// instead of interface
/// </summary>
public class Invoice
{
private Func<float, float, float>
calculateGrandTotalCallback;

public List<InvoiceLineItem> InvoiceLineItems { get; set; }

public Invoice(IEnumerable<InvoiceLineItem>
invoiceLineItems,
Func<float, float, float> calculateGrandTotalCallback)
{
InvoiceLineItems = new
List<InvoiceLineItem>(invoiceLineItems);
this.calculateGrandTotalCallback =
calculateGrandTotalCallback;
}
public float CalculateGrandTotal(float invoiceSubTotal,
float invoiceTotalTax)
{
return calculateGrandTotalCallback(
invoiceSubTotal, invoiceTotalTax);
}
//...
}

If you find that you are only passing data to the other class and not receiving any data from it, then events are the best way of refactoring.

Events

Although events are effectively callbacks, they have first-class status in C# and .NET with their own syntax and follow a specific protocol. Events are an optional one-way communication between one class and many subscribers. If you're refactoring from a one-to-one coupling with another class and the use of that other class is to effectively notify it of particular values and not receive any information in return, events are a very apt refactoring.

Events are different from the average use of delegates in one important way: multicasting. This means the standard event interface automatically supports combining event listeners into a multicast delegate under the covers. You can certainly support multicast delegates in our delegate example, but we'd have to expand the interface to support "adding" and "removing" a callback. For example:

/// <summary>
/// Invoice class that supports
/// multicast delegates
/// </summary>
public class Invoice
{
private Func<float, float, float>
calculateGrandTotalCallback;
//...

public void AddCalculateGrandTotalCallback(
Func<float, float, float> callback)
{
calculateGrandTotalCallback += callback;
}
public void RemoveCalculateGrandTotalCallback(
Func<float, float, float> callback)
{
calculateGrandTotalCallback -= callback;
}
}

But, of course, this isn't what callbacks are intended for and thus not applicable for our exemplified purpose. We only get one result from executing multiple delegates with a multicast delegate. For the same reason, multicast delegates aren't suitable for delegates that return values; events that return values are not appropriate. Let's return to a disposable Invoice for a moment. Let's say we wanted to inform a subscriber when we are disposed. One way of doing that is with delegates. For example:

/// <summary>
/// Invoice that implements IDisposable
/// </summary>
public class Invoice : IDisposable
{
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
//...
}
Action callback = disposedCallback;
if (callback != null)
{
callback();
}
disposed = true;
}
}
private List<InvoiceLineItem> invoiceLineItems;
private Action disposedCallback;
public Invoice(IEnumerable<InvoiceLineItem>
invoiceLineItems,
Action disposedCallback)
{
this.invoiceLineItems = new
List<InvoiceLineItem>(invoiceLineItems);
this.disposedCallback =
disposedCallback;
}
//...
}

This does what we want, but it limits us to just one subscriber—the subscriber that created the Invoice object. Clearly this isn't the best way to do this, plus it's uncommon and not intuitive. Using events is much more intuitive and supports multiple subscribers. Refactoring to events involves changing the delegate to a public event, removing the initialization of the delegate in the constructor, changing the invocation of the Invoice constructor, changing the method used for the delegate to include an object and an EventArgs parameter, using this new method to assign an event to the Invoice object, and changing the Dispose method to use an EventHandler object instead. This results in something like the following:

/// <summary>
/// Disposable Invoice and Disposed event
/// </summary>
public class Invoice : IDisposable
{
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
//...
}
EventHandler handler = Disposed;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
disposed = true;
}
}
private List<InvoiceLineItem> invoiceLineItems;
public EventHandler Disposed;
public Invoice(IEnumerable<InvoiceLineItem>
invoiceLineItems)
{
this.invoiceLineItems = new
List<InvoiceLineItem>(invoiceLineItems);
}
//...
}

Refactoring with Microsoft Visual Studio 2010 Refactor with Microsoft Visual Studio 2010 and evolve your software system to support new and ever-changing requirements by updating your C# code base with patterns and principles with this book and eBook
Published: July 2010
eBook Price: $35.99
Book Price: $59.99
See more
Select your format and quantity:

(For more resources on Microsoft products, see here.)

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 fl at 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 Invoiceclass 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));
}
//...
}

Dependency cycles

We can't discuss coupling without discussing Dependency cycles. A dependency cycle (otherwise known as a Circular dependency) is when an item depends on something else and that something else, or its dependants, depend on that item. Where this presents a problem is at the deployment level. If something in assembly A depends on something in assembly B and something in assembly B depends on something in assembly A, the compiler won't be able to figure out which project to build first and generate an error. Assembly cycles are easy to detect, you've got an error to deal with. The Acyclic Dependency Principle details that the dependency structure of packages must have no cycles.

Package is a grouping of elements. How the elements are grouped is somewhat subjective, but generally means a physical grouping (for example, assembly in .NET). However, it can be interpreted as any grouping (for example, namespace).

I tend to view packages as all groupings: from assembly to class. The real problem with dependency cycles rears its head at the assembly level, but the assembly structure is dependent on the deployment requirements of the system. The deployment requirements of the system are independent of the logical design of the system. Sometimes, the physical deployment requirements of the system are not known when initial design is begun, and often changes throughout the evolution of a system.

Dependency cycles that don't span assembly boundaries aren't as easy to detect. If class A uses class B and class B uses class A and they're all in the same assembly, there will be no errors. That may or may not be a bad thing. Obviously, it hinders your ability to maintain the code such that you cannot break those two classes out into their own assembly.

Proper dependency design

Dependencies between packages should always occur in one direction. There are various guidelines about what direction makes for the most maintainable code. One guideline deals with stability. The Stable Dependencies Principle (DSP) details that a package should only depend on other packages that are more stable than it.

One means of providing stability is abstractiveness. The more abstract something is, the more likely it can be stable. Interfaces, for example, since they only contain a contract and no code, have no possibility that a code change will cause instability with an interface. This assumes that thorough analysis went into the design of the interface. This means the best kind of dependency is a dependency upon an abstraction. The corollary to that is that abstractions should never depend on concrete implementation.

Another proper dependency design attribute has to deal with layering. Layers can be physical and explicit or logical and implicit. A layer is some sort of abstraction grouping of elements with a common goal. Some common contemporary layers are the Data Layer and the User Interface Layer. Layers have levels. Some are lower than other layers. The Data Layer, for example, is a lower-level layer compared to the User Interface Layer, which is a higher-level layer. Proper dependency structure between layers is always such that lower-level layers never depend on higher-level layers. The abstraction between layers means a lower-level layer can be used by any number of higher-level layers. Take the User Interface Layer and Data Layer layers for example. I may have multiple User Interface Layers: a WinForm layer, a WPF layer, a Web layer, and so on. If my Data Layer depended on one of the User Interface layers, that User Interface layer would have to be deployed with all other user interface layers. It would be catastrophic if we had to deploy WinForm code with our Web User Interface Layer on a web server, for example.

Summary

We've reviewed what it is to be highly cohesive and loosely coupled. We've seen what it means to be non-cohesive and how to detect certain non-cohesiveness through metrics. With some simple refactoring we can make something cohesive where it was previously not.

By refactoring our code to be more cohesive and less coupled, we've improved the maintainability of our code base. Classes are easier to move or reuse and we're less likely to repeat ourselves. Changes are now easier to make because we've decreased the dependencies.


Further resources on this subject:


About the Author :


Peter Ritchie

Peter Ritchie is a software development consultant. Peter is president of Peter Ritchie Inc. Software Consulting Co., a software consulting company in Canada's National Capital Region specializing in Windows-based software development management, process, and implementation consulting. Peter has worked with such clients as Mitel, Nortel, Passport Canada, and Innvapost from mentoring to architecture to implementation. Peter has considerable experience building software development teams and working with startups towards agile software development.

Peter's range of experience ranges from designing and implementing simple standalone applications to architecting distributed n-tier applications spanning dozens of computers; from C++ to C#. Peter is active in the software development community attending and speaking at various events as well as authoring various works including Refactoring with Microsoft Visual Studio 2010.

Books From Packt


ASP.NET 3.5 Application Architecture and Design
ASP.NET 3.5 Application Architecture and Design

Microsoft Silverlight 4 Data and Services Cookbook
Microsoft Silverlight 4 Data and Services Cookbook

Microsoft Silverlight 4 and SharePoint 2010 Integration
Microsoft Silverlight 4 and SharePoint 2010 Integration

WCF 4.0 Multi-tier Services Development with LINQ to Entities
WCF 4.0 Multi-tier Services Development with LINQ to Entities

Microsoft Windows Workflow Foundation 4.0 Cookbook
Microsoft Windows Workflow Foundation 4.0 Cookbook

.NET Compact Framework 3.5 Data Driven Applications
.NET Compact Framework 3.5 Data Driven Applications

Applied Architecture Patterns on the Microsoft Platform
Applied Architecture Patterns on the Microsoft Platform

Microsoft Visio 2010 Business Process Diagramming and Validation
Microsoft Visio 2010 Business Process Diagramming and Validation


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
r
v
i
A
E
Q
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software