A frequently-used design pattern is the Decorator. This is also known as a mixin (or they might not be the very same thing but certainly they are related).
Typically, you might need to create a class that implements a certain interface and uses another class that implements that exact interface but you need to provide some additional feature(s). An example would be a class that adds transactional behavior to an existing data-access class (a naive example):
public interface IDataAccess
{
void AddCustomerInvoice(Invoice invoice, User user);
}
public class DataAccess : IDataAccess
{
public void AddCustomerInvoice(Invoice invoice, User user)
{
InsertInvoice(invoice, user);
UpdateCustomerDebt(user, invoice.Total);
}
}
public class TransactionalDataAccess : IDataAccess
{
private readonly IDataAccess _dataAccess;
public TransactionalDataAccess(IDataAccess dataAccess)
{
if (dataAccess == null)
{
throw new ArgumentNullException();
}
_dataAccess = dataAccess;
}
public void AddCustomerInvoice(Invoice invoice, User user)
{
using(var tx = new TransactionScope())
{
_dataAccess.AddCustomerInvoice(invoice, user);
tx.Complete();
}
}
}
Another type of example would be the Adapter design pattern. An example would be providing access to a (static
) class (that may be out of your control) in a mock-able manner. That is, implement another class, non-static, which implements a defined interface and eases unit-testing:
public class StorageManager
{
public void StoreData(string key, byte[] data)
{
if (string.IsNullOrWhiteSpace(key))
{
throw new ArgumentNullException("key");
}
if (data == null)
{
throw new ArgumentNullException("data");
}
System.IO.File.WriteAllBytes(key + ".bin", data);
}
}
This class, as you can see, uses the static File
class in the BCL of .NET Framework. If I need to unit-test the StoreData
method in a way to assert that the data is actually written to the file (and maybe test the file is named as the key and the binary data and only the binary data is placed in that file, etc.), then I can’t do it. Well, you might get away with TypeMock Isolator (expensive piece of… software) or Moles/Pex, but it’s just not right™.
So we define an interface...
public interface IFileAccess
{
void WriteAllBytes(string filePath, byte[] binaryContent);
}
...and then we write an implementation for this class:
public class FileAccessAdapter : IFileAccess
{
public void WriteAllBytes(string filePath, byte[] binaryContent)
{
System.IO.File.WriteAllBytes(filePath, binaryContent);
}
}
Finally, we update the StorageManager
class:
public class StorageManager
{
private readonly IFileAccess _fileAccess;
public StorageManager(IFileAccess fileAccess)
{
if (fileAccess == null)
{
throw new ArgumentNullException("fileAccess");
}
_fileAccess = fileAccess;
}
public void StoreData(string key, byte[] data)
{
if (string.IsNullOrWhiteSpace(key))
{
throw new ArgumentNullException("key");
}
if (data == null)
{
throw new ArgumentNullException("data");
}
_fileAccess.WriteAllBytes(key + ".bin", data);
}
}
Now we can unit test the StorageManager
class by providing a mock of the IFileAccess
interface.
Having finished the introduction, let’s get to business. The nice thing is that using these patterns, you have a more loosely-coupled code and you can unit test it. The not-so-nice thing is that you need to write a lot of repetitive code that’s really boring. I mean usually the interfaces won’t have 1-2 methods like my examples above but sometimes tens or even a hundred (yes, unfortunately, some people haven’t heard of ISP – Interface Segregation Principle).
Today, as I was creating such an adapter for a static
class to which I don’t have much control, I thought: maybe ReSharper can help me… I looked in the implement interface dialog box, but no help. Then I turned to Google and as usual in the past few years, one of the best pieces of information came from Stackoverflow: How to (visual studio 2008 / Resharper) refactor / automate mixin pattern.
Trying to implement the adapter pattern, I failed since ReSharper does not support this scenario directly but being intrigued, I tried to create a decorator (the first scenario presented above). However, let’s resume the first example. At first, I wrote the class definition and the constructor for the TransactionalDataAccess
class:
public class TransactionalDataAccess : IDataAccess
{
private readonly IDataAccess _dataAccess;
public TransactionalDataAccess(IDataAccess dataAccess)
{
if (dataAccess == null)
{
throw new ArgumentNullException();
}
_dataAccess = dataAccess;
}
}
Then, I pressed ALT-INSERT inside the block of code of the class:
I selected Delegating members and then in the following dialog box:
I selected all the interface’s members (in this simple example, there was only one but in production, I selected around 50 members). Finally, the code generated was like this:
public class TransactionalDataAccess : IDataAccess
{
private readonly IDataAccess _dataAccess;
public TransactionalDataAccess(IDataAccess dataAccess)
{
if (dataAccess == null)
{
throw new ArgumentNullException();
}
_dataAccess = dataAccess;
}
public void AddCustomerInvoice(Invoice invoice, User user)
{
_dataAccess.AddCustomerInvoice(invoice, user);
}
}
I just added the transaction wrapper:
public class TransactionalDataAccess : IDataAccess
{
private readonly IDataAccess _dataAccess;
public TransactionalDataAccess(IDataAccess dataAccess)
{
if (dataAccess == null)
{
throw new ArgumentNullException();
}
_dataAccess = dataAccess;
}
public void AddCustomerInvoice(Invoice invoice, User user)
{
using (var tx = new TransactionScope())
{
_dataAccess.AddCustomerInvoice(invoice, user);
tx.Complete();
}
}
}
… and I was done with it. For the adapter class, all you need is to extract the interface from the static
class (you can view the metadata using Visual Studio and copy/paste it). Then create the adapter class, create a constructor with (at least) one parameter of the interface type, generate the delegating members just like above, modify or delete the constructor and the private
field and replace (CTRL-H) all the occurrences of the private
field with a call to the static
class being adapted.
… That’s all folks!