Domanda
Qual è un modo più elegante di avere il codice qui sotto dove voglio restituire una classe derivata in base al tipo di un'altra classe.
if (option_ is Rectangle)
{
modelInputs = new Foo();
}
else if (option_ is Circle)
{
modelInputs = new Bar();
}
else if (option_ is Triangle)
{
modelInputs = new Bar2();
}
Soluzione
Chiedi a Rectangle, Circle e Triangle di implementare IHasModelInput:
interface IHasModelInput
{
IModelInput GetModelInput();
}
allora puoi farlo
IModelInput modelInputs = option_.GetModelInput();
Altri suggerimenti
La mia opinione: il tuo " inelegante " il modo va bene. È semplice, leggibile e fa il lavoro.
Avere Rectangle, Circle e Triangle implementare la funzione di fabbrica necessaria tramite IHasModelInput funzionerebbe, ma ha un costo di progettazione: ora hai abbinato questo set di classi al set di classi IModelInput ( Foo, Bar e Bar2). Potrebbero essere in due librerie completamente diverse e forse non dovrebbero conoscersi.
Di seguito è riportato un metodo più complicato. Ti offre il vantaggio di poter configurare la logica di fabbrica in fase di esecuzione.
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;
}
Ma poi fai la domanda: quale preferiresti leggere?
È possibile inserire gli input e gli output in un Hashtable o archiviare i tipi che creano ogni classe all'interno di ciascuna delle classi create e quindi utilizzare Activator.CreateInstance per eseguire il factoryin ':
Hashtable ht = new Hashtable();
ht.Add(typeof(Rectangle), typeof(Bar));
ht.Add(typeof(Square), typeof(Bar2));
modelInputs = Activator.CreateInstance(ht[option.GetType()]);
Ad ogni modo, Activator.CreateInstance è un modo piuttosto interessante di far funzionare le fabbriche in .NET. Goditi e usa il potere che ti ho dato saggiamente, figliolo.
Puoi associare un tipo a " opzione_ " ;, se lo consente, e quindi crearne un'istanza.
Di solito utilizzo un metodo factory come questo quando voglio convertire una stringa in un tipo in fase di esecuzione, uso un dizionario che mappa una stringa in un tipo.
Come questo da un recente progetto:
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;
}
}
}