I think the simplest design pattern we’ve all used without really calling it a pattern, other than observer, is the adapter design pattern. The adapter design pattern is known more colloquially as a wrapper where you wrap a bunch of functionality from one or more classes into a single class because of incompatibilities between the interfaces.
In my post, Observer and Command Design Patterns – A Real World Example I discussed the need for the interfaces:
public interface IOpenDialog
{
string Filter { get; set; }
string FileName { get; set; }
string[] FileNames { get; set; }
bool Multiselect { get; set; }
bool? ShowDialog();
}
public interface ISaveDialog
{
string Filter { get; set; }
string FileName { get; set; }
string[] FileNames { get; set; }
bool? ShowDialog();
}
These are modified a bit from the original interfaces I showed but you get the idea.
See, the problem comes when running your unit tests. I had a need in my view models where I wanted to open files and save files. But if I had used the standard dialogs from the Microsoft.Win32 namespace when I ran my unit tests things would just not have worked. I mean, how do I show a SaveFileDialog in a unit test? There is some stuff I could have done with binding and listening for a property changed event in my view, that would have an ugly, unnecessary hack.
This is all made easier just by wrapping the SaveFileDialog and OpenFileDialog classes.
public class OpenFile : IOpenFileDialog
{
#region IOpenDialog Members
public string Filter { get; set; }
public string FileName { get; set; }
public string[] FileNames { get; set; }
public bool Multiselect { get; set; }
public bool? ShowDialog()
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = Filter;
ofd.FileName = FileName;
ofd.Multiselect = Multiselect;
bool? result = ofd.ShowDialog();
if (result == true)
{
FileName = ofd.FileName;
FileNames = ofd.FileNames;
}
return result;
}
#endregion
}
public class SaveFile : ISaveFileDialog
{
#region ISaveDialog Members
public string Filter { get; set; }
public string FileName { get; set; }
public string[] FileNames { get; set; }
public bool? ShowDialog()
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = Filter;
sfd.FileName = FileName;
bool? result = sfd.ShowDialog();
if (result == true)
{
FileName = sfd.FileName;
FileNames = sfd.FileNames;
}
return result;
}
#endregion
}
Since opening the dialogs in the view model is not possible, by utilizing the above classes (which wrap the standard dialogs) we abstract the dependencies away from the view model into the classes that implement the interface. Now all I have to do is register the types with my unity container: container.RegisterType<ISaveFileDialog, SaveFile>(); container.RegisterType<IOpenFileDialog, OpenFile>();
And in my view models when I use it:
IOpenFileDialog ofd = container.Resolve<IOpenFileDialog>();
ofd.Filter = "Xml Files (*.xml)|*.xml";
if (ofd.ShowDialog() != true)
return;
But in my mind there is an even cooler thing. In my NUnit project I have the two following classes in my Mocks directory:
public class OpenFileForUnitTest : IOpenFileDialog
{
public bool? ShowDialogShouldReturn { get; set; }
#region IOpenFileDialog Members
public string Filter { get; set; }
public string FileName { get; set; }
public string[] FileNames { get; set; }
public bool Multiselect { get; set; }
public bool? ShowDialog()
{
return ShowDialogShouldReturn;
}
#endregion
public OpenFileForUnitTest()
{
Flush();
}
public void SetFileNames(params string[] FileNamesToAdd)
{
FileNames = FileNamesToAdd;
if (FileNamesToAdd == null || FileNamesToAdd.Length == 0)
{
FileName = null;
}
else
{
FileName = FileNamesToAdd[0];
}
}
public void Flush()
{
Filter = null;
FileName = null;
FileNames = new string[0];
Multiselect = false;
ShowDialogShouldReturn = true;
}
}
public class SaveFileForUnitTest : ISaveFileDialog
{
public bool? ShowDialogShouldReturn { get; set; }
public bool IgnoreFileNameSet { get; set; }
#region ISaveFileDialog Members
public string Filter { get; set; }
string fileName;
public string FileName
{
get { return fileName; }
set
{
if (IgnoreFileNameSet)
return;
fileName = value;
}
}
public string[] FileNames { get; set; }
public bool? ShowDialog()
{
return ShowDialogShouldReturn;
}
#endregion
public SaveFileForUnitTest()
{
Flush();
}
/// <summary>
/// will override IgnoreFileNameSet to set FileName and then restore it
/// </summary>
/// <param name="FileNamesToAdd"></param>
public void SetFileNames(params string[] FileNamesToAdd)
{
FileNames = FileNamesToAdd;
bool ignore = IgnoreFileNameSet;
IgnoreFileNameSet = false;
if (FileNamesToAdd == null || FileNamesToAdd.Length == 0)
{
FileName = null;
}
else
{
FileName = FileNamesToAdd[0];
}
IgnoreFileNameSet = ignore;
}
public void Flush()
{
Filter = null;
FileName = null;
FileNames = new string[0];
ShowDialogShouldReturn = true;
IgnoreFileNameSet = false;
}
}
And in my unit tests similar to how it’s used:
var ofd = new OpenFileForUnitTest();
container.RegisterInstance<IOpenFileDialog>(ofd);
ofd.SetFileNames(pathToTestFile);
var myVM = new myVM();
myVM.MethodToTest();
And auto-magically the view model gets the path to the test file for the unit test. Following this is a bunch of asserts to ensure that the state and properties of the view model loaded as I expected from the test file.
That’s it for this week.
Thanks,
Brian