Как избавиться от оператора switch в заводском методе, когда создание основано на информации, переданной во время выполнения

#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();
    }
}