After giving it some further thought, if you want to do this in the cleanest way possible, you could use a combination of the Strategy and Service Locator pattern to accomplish your goal in an object-oriented manner.
Here is the overall strategy (no pun intended):
- Put your implementation for each strategy in a separate class.
- Register each strategy in a static list.
- When you need to handle an object of an unknown type, iterate through the registered strategies until you find one that works.
The neat part about this approach is that you can make the condition of whether or not to follow a specific strategy based on criteria more custom than just Type. Let's say you have one interface that you actually want to handle in two different ways depending on the value of a property, or if you want to apply the same strategy to multiple interfaces. It's all about how you implement it.
This is definitely overkill for classes like factories, etc., where the if/else if pattern should suffice and is in fact quite appropriate. Assuming you've explored other options that are simpler or more oo-friendly, here's a thoroughly over-engineered example with the pure purpose of avoiding a switch
on Type, resulting in Ultimately Extensible Code (TM).
Let's say you are dealing with two external, non-related interfaces, IBlinger
and ISnapper
:
interface IBlinger
{
public string BlingName { get; }
}
interface ISnapper
{
public string SnapName { get; }
}
And you are trying to write a class that handles writing each interface to the console in a custom way. You would first define an interface IWriteHandler
as follows:
interface IWriteHandler
{
bool Write(object target);
}
And then provide implementations:
class BlingWriter : IWriteHandler
{
public bool Write(object target)
{
var bling = target as IBlinger;
if (bling == null)
return false;
Console.WriteLine("BLING: " + bling.BlingName);
return true;
}
}
class SnapWriter : IWriteHandler
{
public bool Write(object target)
{
var snap = target as ISnapper;
if (snap == null)
return false;
Console.WriteLine("SNAP: " + snap.SnapName);
return true;
}
}
And here's an example usage:
class Writer
{
static List<IWriteHandler> _handlers = new List<IWriteHandler>();
static Writer()
{
//add them to the list in order of preference, in case the types overlap
//due to inheritance.
_handlers.Add(new BlingWriter());
_handlers.Add(new SnapWriter());
}
public void Write(object unknown)
{
foreach (var handler in _handlers)
{
if (handler.Write(unknown))
return;
}
//default
Console.WriteLine("UNKNOWN: " + unknown.ToString());
}
}
And if that seems too "boilerplatey", you could make it more generic:
abstract class GenericWriteHandler<T> : IWriteHandler where T : class
{
public bool Write(object target)
{
var asT = target as T;
if (asT == null)
return false;
WriteInternal(asT);
return true;
}
protected abstract void WriteInternal(T target);
}
Which could be implemented as:
class SnapWriter : GenericWriteHandler<ISnapper>
{
protected override void WriteInternal(ISnapper target)
{
Console.WriteLine("SNAP: " + target.SnapName);
}
}
OR, if you like lambdas/don't like creating lots of classes, you could make:
class ActionWriteHandler<T> : IWriteHandler where T : class
{
Action<T> _a;
public ActionWriteHandler(Action<T> action)
{
_a = action;
}
public bool Write(object target)
{
var asT = target as T;
if (asT == null)
return false;
_a(asT);
return true;
}
protected abstract void WriteInternal(T target);
}
Where the Writer
class is initialized like:
static Writer()
{
_handlers.Add(new ActionWriteHandler<IBlinger>(b => Console.WriteLine(b.BlingName)));
_handlers.Add(new ActionWriteHandler<ISnapper>(s => Console.WriteLine(s.SnapName)));
}
In fact, you could make the entire Writer
class generic too, but that is for another day...
Related question (which happens to have a similar answer): https://stackoverflow.com/questions/3834091/strategy-pattern-with-no-switch-statements