Определение, какой класс использовать во время выполнения - PullRequest
4 голосов
/ 19 января 2012

Я работаю над унаследованным кодом, который выглядит очень похоже на это:

public class BaseParser
{
    public BaseParser(Stream stream) 
    {
        // Do something with stream
    }
}

public class JsonParser : BaseParser
{
    public JsonParser(Stream stream) 
        : base(stream)
    {
        // Do something
    }
}

public class XmlParser : BaseParser
{
    public XmlParser(Stream stream, string someOtherParam) 
        : base(stream)
    {
        // Do something
    }
}

У нас есть приложение, которое анализирует входящие файлы с помощью определенного анализатора.Чтобы определить, какой тип нам нужно создать, в фабричном методе есть большой блок IF / ELSE:

public static BaseParser Create(string type)
{
    if (type == "xml")
        return new XmlParser(new FileStream("path", FileMode.Open), "test");

    if (type == "json")
        return new JsonParser(new FileStream("path", FileMode.Open));

    // more...

    return null;
}

Так как в одной сборке находится большое количество анализаторов (давайте назовем его Demo.dll) Я бы хотел разделить каждый из этих парсеров на свои собственные сборки.Классы «core», такие как BaseParser, останутся в Demo.dll, а остальные парсеры и любые зависимости будут жить в Demo.Json.dll, Demo.Xml.dll и т. Д. *

Ядро"библиотека загрузит эти сборки во время выполнения.

string[] paths = Directory.GetFiles(Environment.CurrentDirectory, "Demo.*.dll");

foreach (string path in paths)
    Assembly.LoadFrom(path);

List<BaseParser> parsers = AppDomain.CurrentDomain
    .GetAssemblies()
    .SelectMany(x => x.GetTypes())
    .Where(x => x.IsSubclassOf(typeof(BaseParser)))
    .Select(x => (BaseParser) Activator.CreateInstance(x))
    .ToList();

Проблема начинается здесь.Приведенный выше код не будет работать, так как каждый из старых анализаторов не имеет конструктора без параметров.Кроме того, я не могу придумать хороший способ заменить блок IF / ELSE и позволить каждому анализатору определить, какой файл он может обрабатывать.Я думал о возможном добавлении виртуального метода в базовый анализатор:

public virtual bool ShouldHandle(string type)
{
    return false;
}

... и каждый производный синтаксический анализатор будет переопределять этот виртуальный метод.Однако есть две проблемы:

  1. Нет конструктора без параметров, и конструктор каждого класса может иметь различное количество параметров.Это не то, что я могу изменить, так как эти унаследованные классы используются везде.

  2. Мне нужно создать экземпляр класса, прежде чем я смогу вызвать метод ShouldHandle.Это создает проблему для классов, которые читают из потока в конструкторе.

Есть ли альтернативный путь, которым я могу воспользоваться, чтобы разбить эти парсеры на их собственные сборки?

РЕДАКТИРОВАТЬ:

Это приложение .NET 3.5.К сожалению, нет MEF.

Ответы [ 3 ]

3 голосов
/ 19 января 2012

Я обнаружил, что использование абстрактного фабричного класса может помочь в этих ситуациях.

У каждой сборки есть фабрика, специфичная для создания классов, находящихся в этой сборке.

Фактически у вас есть фабрика, которая создает бетонный завод для создания вашего конкретного парсера.

Делая это таким образом, вы также можете установить логику, с помощью которой вы проверяете наличие сборки, прежде чем пытаться создать конкретную фабрику, связанную с ней.

Я знаю, что она не обходит структуру if else (хотя ее можно заменить на switch, передавая параметр для каждого класса, но это позволяет вам разбить ее и упростить обрабатывать и поддерживать.

1 голос
/ 19 января 2012

Вы можете создавать их экземпляры с помощью заводских методов без параметров или заводских методов, которые принимают общий объект конфигурации (некоторый пакет свойств или, по сути, слабо типизированный словарь).Они должны быть частью каждой отдельной сборки плагина.

Но одна вещь довольно очевидна: вам не следует создавать экземпляр FileStream, как вы это делаете сейчас.То, как вы делаете это сейчас, означает, что ваши классы синтаксического анализатора, вероятно, располагают этим потоком, и он не должен быть их обязанностью.Такие объекты должны быть расположены в том же месте, где они были созданы:

using (var stream = new FileStream("path", FileMode.Open))
{
   var parserFactory = ParserFactoryFactory.GetFactory("xml");
   var parser = parserFactory.CreateParser(propertyBag);
   return parser.Parse(stream);
}
0 голосов
/ 19 января 2012

Есть ли у вас возможность использовать IoC-фреймворк, такой как CastleWindsor, чтобы справиться с этим? Мне кажется, вы описываете систему плагинов, которую любая инфраструктура IoC может удобно обрабатывать независимо от сигнатур конструктора.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...