#c# #xml #factory
#c# #xml #фабрика
Вопрос:
В настоящее время я пытаюсь создать хорошую фабрику классов внутри моего решения.
Я создаю модель, которая будет работать с XML-документами очень точного вида.
У меня есть абстрактный класс ‘TreeElement’, который реализует общие функциональные возможности, и есть много подклассов, которые наследуются от него.
В настоящее время я застрял с заводским методом, который содержит большой оператор switch, от которого я хотел бы избавиться, но я не уверен, как это сделать.
Основная проблема заключается в том, что я не знаю, какой подкласс следует использовать до выполнения, потому что информация находится в потомке XmlNode, который я должен сначала прочитать.
Как это можно решить, чтобы это не выглядело таким образом:
public ITreeElement CreateProperType(...)
{
switch (nodeType)
{
case "A":
return new A();
case "B":
return new B();
...
}
}
Комментарии:
1. существуют и другие варианты, которые потенциально абстрагируют это немного больше, например, словарь, загружаемый при запуске или построении вашей фабрики, со строковым ключом и каким-то делегатом фабрики или просто типом и т.д. Однако я бы сказал, что операторы switch не являются изначально плохими — ваш пример здесь, я думаю, является хорошим местом для одного. Вы абстрагировали свою логику в одном месте, и там наиболее подходящим инструментом кода является switch case — пример моего словаря, на самом деле не дает вам никакой пользы, и вы могли бы утверждать, что делает вещи менее понятными.
Ответ №1:
Если, как в вашем примере, тип узла всегда является именем класса, тогда вы можете использовать Activator
класс.
return (ITreeElement)Activator.CreateInstance ("AssemblyName","MyNamespace." nodeType);
Комментарии:
1. Я не знал об этом, спасибо! К сожалению, большинство имен совпадают, но не все из них. Тем не менее, если нет других возможностей, я думаю, что на этом этапе я мог бы реорганизовать код, чтобы полностью соответствовать именам.
Ответ №2:
Вы могли бы создать класс для каждого типа ITreeElement, который может указывать фабрике, какой тип узла она может обрабатывать, а также может создавать тип. Что-то вроде этого
interface ITreeElementCreator
{
string HandlesNodeType { get; }
ITreeElement CreateTreeElement();
}
Затем вы можете реализовать один для A и один для B следующим образом
class ACreator : ITreeElementCreator
{
public string HandlesNodeType => "A";
public ITreeElement CreateTreeElement()
{
return new A();
}
}
class BCreator : ITreeElementCreator
{
public string HandlesNodeType => "B";
public ITreeElement CreateTreeElement()
{
return new B();
}
}
Затем вы можете позволить своей фабрике получить список всех ITreeElementCreator
реализаций из вашего контейнера IoC, а затем он может найти правильного создателя и попросить его предоставить вам ITreeElement
вот так
class TreeElementFactory
{
private readonly IEnumerable<ITreeElementCreator> treeElementCreators;
public TreeElementFactory(IEnumerable<ITreeElementCreator> treeElementCreators)
{
this.treeElementCreators = treeElementCreators;
}
public ITreeElement CreateProperType(string nodeType)
{
return treeElementCreators.SingleOrDefault(te => te.HandlesNodeType == nodeType).CreateTreeElement() ?? throw new ArgumentException($"Unknown nodeType {nodeType}");
}
}
Это имеет то преимущество, что вы можете добавлять новые типы ITreeElement
без изменения фабрики.
Ответ №3:
Простой способ — использовать словарь типов узлов и соответствующие функции, которые могут создавать типы, подобные этому
class TreeElementFactory
{
private static Dictionary<string, Func<ITreeElement>> creatorFuncs = new Dictionary<string, Func<ITreeElement>>
{
{ "A", () => new A() },
{ "B", () => new B() }
};
public static ITreeElement CreateProperType(string nodeType)
{
if (!creatorFuncs.TryGetValue(nodeType, out var creatorFunc))
throw new ArgumentException($"Unknown nodetype {nodeType}");
return creatorFunc();
}
}