tags:

views:

216

answers:

5

What's a more elegant way of having the code below where i want to return a derived class based on the type of another class.

            if (option_ is Rectangle)
            {
                modelInputs = new Foo();
            }
            else if (option_ is Circle)
            {
                modelInputs = new Bar();
            }
            else if (option_ is Triangle)
            {
                modelInputs = new Bar2();
            }
A: 

You could associate a type with "option_", if it allows, and then just create an instance of it.

Ty
+7  A: 

Have Rectangle, Circle and Triangle implement IHasModelInput:

interface IHasModelInput
{
    IModelInput GetModelInput();
}

then you can do

IModelInput modelInputs = option_.GetModelInput();
mmiika
+3  A: 

My opinion: your "inelegant" way is fine. It's simple, readable and does the job.

Having the Rectangle, Circle and Triangle implement the necessary factory function via IHasModelInput would work, but it has a design cost: you've now coupled this set of classes with the IModelInput set of classes (Foo, Bar and Bar2). They could be in two completely different libraries, and maybe they shouldn't know about one another.

A more complicated method is below. It gives you the advantage of being able to configure your factory logic at runtime.

    public static class FactoryMethod<T>  where T : IModelInput, new()
    {
        public static IModelInput Create()
        {
            return new T();
        }
    }

    delegate IModelInput ModelInputCreateFunction();

    IModelInput CreateIModelInput(object item)
    {

        Dictionary<Type, ModelInputCreateFunction> factory = new Dictionary<Type, ModelInputCreateFunction>();


        factory.Add(typeof(Rectangle), FactoryMethod<Foo>.Create);
        factory.Add(typeof(Circle),    FactoryMethod<Bar>.Create);
        // Add more type mappings here




        IModelInput modelInput;
        foreach (Type t in factory.Keys)
        {
            if ( item.GetType().IsSubclassOf(t) || item.GetType().Equals(t))
            {
                modelInput = factory[t].Invoke();
                break;
            }
        }
        return modelInput;
    }

But then ask the question: which one would you rather read?

Andrew Stapleton
I agree that coupling the Rectangle, Circle etc set of classes to the model inputs is not so great. In these cases I've usually just gone for a dictionary of input types to output types and used Activator.CreateInstance.
Andrew Kennan
+1  A: 

You could put the inputs and outputs in a Hashtable, or store the types which create each class inside each one of the classes you create and then use Activator.CreateInstance to do the factoryin':

Hashtable ht = new Hashtable();
ht.Add(typeof(Rectangle), typeof(Bar));
ht.Add(typeof(Square), typeof(Bar2));

modelInputs = Activator.CreateInstance(ht[option.GetType()]);

Either way, Activator.CreateInstance is a pretty cool way of making factories work in .NET. Enjoy, and use the power I have given you wisely, son.

Dave Markle
A: 

I usually use a factory method like this when I want to convert a string to a type at runtime, I use a Dictionary that maps a string to a Type.

Like this from a recent project:

public class TaskFactory
{
    private Dictionary<String, Type> _taskTypes = new Dictionary<String, Type>();

    public TaskFactory()
    {
        // Preload the Task Types into a dictionary so we can look them up later
        foreach (Type type in typeof(TaskFactory).Assembly.GetTypes())
        {
            if (type.IsSubclassOf(typeof(CCTask)))
            {
                _taskTypes[type.Name.ToLower()] = type;
            }
        }
    }

    public CCTask CreateTask(XmlElement task)
    {
        if (task != null)
        {
            string taskName = task.Name;
            taskName =  taskName.ToLower() + "task";

            // If the Type information is in our Dictionary, instantiate a new instance of that task
            Type taskType;
            if (_taskTypes.TryGetValue(taskName, out taskType))
            {
                return (CCTask)Activator.CreateInstance(taskType, task);
            }
            else
            {
                throw new ArgumentException("Unrecognized Task:" + task.Name);
            }                               
        }
        else
        {
            return null;
        }
    }
}
FlySwat