This is a pattern I run into all the time: I want to write a class which will perform some routine. The routine will consist of many steps which have to follow a certain work flow. By putting all this behind a method call I can abstract the details of the work flow and how the steps are done. However, after all is said and done, I need to know what happened. When the method is simple this can often just be a single return value, GetString() returns a string. If it returns null, there was no string to get. But when your method has many steps to perform this can get more complicated. It may need to return different information depending on what happened. I need the method to report back more than just a single value, I need a status report.
Let's look at an example. Suppose you're given a file path and you want to "scrub" it. Pretend you want to make sure the file type is not on some list of "excluded" file types. Then, if it's a shortcut you want to resolve to the target path. If it's a folder you want to expand the folder into all its files. There is some non-trivial work to be done here and it could be used in multiple locations, so it would be nice to abstract the whole process into a single call.
If we do this it could report back with:
- File okay (file path)
- File excluded (file path, file type)
- Shortcut resolved (shortcut path, target path)
- Folder expanded (list of file paths, list of excluded files)
public enum ScrubResultType
{
FileOkay, FileExcluded, ShortcutResolved, FolderExpanded
}
public abstract class ScrubResult
{
public ScrubResultType Type
{
get
{
if ( this is ScrubFileOkay )
return ScrubResultType.FileOkay;
else if ( this is ScrubFileExcluded )
return ScrubResultType.FileExcluded;
else if ( this is ScrubShortcutResolved )
return ScrubResultType.ShortcutResolved;
...
}
}
public abstract bool IsError { get; }
}
public class ScrubFileOkay : ScrubResult
{
public string FilePath;
public override bool IsError { get { return false; } }
}
public class ScrubFileExcluded : ScrubResult
{
public string FilePath;
public string FileType;
public override bool IsError { get { return true; } }
}
public class ScrubShortcutResolved : ScrubResult
{
public string ShortcutPath;
public string TargetPath;
public override bool IsError { get { return false; } }
}
public class ScrubFolderExpanded : ScrubResult
{
public string[] ExpandedPaths;
public string[] ExcludedFiles;
public string FolderPath;
public override bool IsError { get { return false; } }
}
{
FileOkay, FileExcluded, ShortcutResolved, FolderExpanded
}
public abstract class ScrubResult
{
public ScrubResultType Type
{
get
{
if ( this is ScrubFileOkay )
return ScrubResultType.FileOkay;
else if ( this is ScrubFileExcluded )
return ScrubResultType.FileExcluded;
else if ( this is ScrubShortcutResolved )
return ScrubResultType.ShortcutResolved;
...
}
}
public abstract bool IsError { get; }
}
public class ScrubFileOkay : ScrubResult
{
public string FilePath;
public override bool IsError { get { return false; } }
}
public class ScrubFileExcluded : ScrubResult
{
public string FilePath;
public string FileType;
public override bool IsError { get { return true; } }
}
public class ScrubShortcutResolved : ScrubResult
{
public string ShortcutPath;
public string TargetPath;
public override bool IsError { get { return false; } }
}
public class ScrubFolderExpanded : ScrubResult
{
public string[] ExpandedPaths;
public string[] ExcludedFiles;
public string FolderPath;
public override bool IsError { get { return false; } }
}
I've defined an enumeration which contains all the possible "return types." Then because each return type might need to carry back different information I've used polymorphism to create a class for each possible return type. With a system like this in place I can write code as follows:
ScrubResult sr = FileScrubber.Scrub( "..." );
if ( sr.IsError )
{
if ( sr.Type == ScrubResultType.FileExcluded )
{
ScrubFileExcluded sfe = (ScrubFileExcluded)sr;
MessageBox.Show(
String.Format( "File {0} of type {1} is excluded",
sfe.FilePath, sfe.FileType ) );
}
}
else
{
// handle result based on type
}
if ( sr.IsError )
{
if ( sr.Type == ScrubResultType.FileExcluded )
{
ScrubFileExcluded sfe = (ScrubFileExcluded)sr;
MessageBox.Show(
String.Format( "File {0} of type {1} is excluded",
sfe.FilePath, sfe.FileType ) );
}
}
else
{
// handle result based on type
}
This is an actual example of something I was seriously considering doing. I ended up deciding that the User implications of such a situation were scary enough to not warrant adding this. At least not for now.
When the return types don't have different information to carry back then the different classes aren't required and a single class with a "Type" property that returns an enumeration value is good enough. I have used the simpler version in code.
I like this pattern, but I've never really seen it anywhere. Have you seen it? Do you like it? Have you solved the same problem in a different way?
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.