Подтвердить что ты не робот

Аннотация Factory Шаблон проектирования

Я работаю над внутренним проектом для своей компании, и часть проекта состоит в том, чтобы анализировать различные "Задачи" из XML файла в коллекцию задач, которые будут запущены позже.

Поскольку каждый тип задачи имеет множество разных связанных полей, я решил, что лучше всего представлять каждый тип задачи с отдельным классом.

Для этого я построил абстрактный базовый класс:

public abstract class Task
{
    public enum TaskType
    {
        // Types of Tasks
    }   

    public abstract TaskType Type
    {
        get;
    }   

    public abstract LoadFromXml(XmlElement task);
    public abstract XmlElement CreateXml(XmlDocument currentDoc);
}

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

Основной пример:

public class MergeTask : Task
{

    public override TaskType Type
    {
        get { return TaskType.Merge; }
    }   

    // Lots of Properties / Methods for this Task

    public MergeTask (XmlElement elem)
    {
        this.LoadFromXml(elem);
    }

    public override LoadFromXml(XmlElement task)
    {
        // Populates this Task from the Xml.
    }

    public override XmlElement CreateXml(XmlDocument currentDoc)
    {
        // Serializes this class back to xml.
    }
}

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

XmlNode taskNode = parent.SelectNode("tasks");

TaskFactory tf = new TaskFactory();

foreach (XmlNode task in taskNode.ChildNodes)
{
    // Since XmlComments etc will show up
    if (task is XmlElement)
    {
        tasks.Add(tf.CreateTask(task as XmlElement));
    }
}

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

Однако мне не нравится мой код для TaskFactory.CreateTask. Этот метод принимает XmlElement, а затем возвращает экземпляр соответствующего класса Task:

public Task CreateTask(XmlElement elem)
{
    if (elem != null)
    {
        switch(elem.Name)
        {
            case "merge":
                return new MergeTask(elem);
            default:
                throw new ArgumentException("Invalid Task");
        }
    }
}

Поскольку мне приходится разбирать XMLElement, я использую огромный (10-15 случаев в реальном коде) переключатель, чтобы выбрать, какой дочерний класс нужно создать. Я надеюсь, что есть какой-то полиморфный трюк, который я могу сделать для очистки этого метода.

Любые советы?

4b9b3361

Ответ 1

Я использую рефлексию для этого. Вы можете сделать factory, который в основном расширяется без необходимости добавления дополнительного кода.

убедитесь, что у вас есть "using System.Reflection", поместите следующий код в свой метод создания экземпляров.

public Task CreateTask(XmlElement elem)
{
    if (elem != null)
    { 
        try
        {
          Assembly a = typeof(Task).Assembly
          string type = string.Format("{0}.{1}Task",typeof(Task).Namespace,elem.Name);

          //this is only here, so that if that type doesn't exist, this method
          //throws an exception
          Type t = a.GetType(type, true, true);

          return a.CreateInstance(type, true) as Task;
        }
        catch(System.Exception)
        {
          throw new ArgumentException("Invalid Task");
        }
    }
}

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

Ответ 2

Создайте "Prototype" instanace для каждого класса и поместите их в хэш-таблицу внутри factory, с строкой, которую вы ожидаете в XML в качестве ключа.

поэтому CreateTask просто находит правильный объект Prototype, get() из хэш-таблицы.

затем вызовите LoadFromXML.

вам нужно предварительно загрузить классы в хэш-таблицу,

Если вы хотите, чтобы он был более автоматическим...

Вы можете сделать классы "самостоятельно регистрирующимися", вызвав метод статического регистра на factory.

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

Тогда будет достаточно статического массива подклассов Task, чтобы "упомянуть" их. Или используйте рефлексию для обозначения классов.

Ответ 3

Как вы относитесь к инъекции зависимостей? Я использую Ninject, и поддержка контекстной привязки в нем идеально подходит для этой ситуации. Посмотрите на это сообщение в блоге о том, как вы можете использовать контекстное связывание с созданием контроллеров с IControllerFactory при их запросе. Это должен быть хороший ресурс о том, как использовать его для вашей ситуации.

Ответ 4

@jholland

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

Enum?

Я признаю, что он чувствует себя взломанным. Сначала отражение кажется грязным, но как только вы приручите зверя, вам понравится то, что он позволяет вам делать. (Помните рекурсию, она чувствует себя грязной, но ее хорошо)

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

BTW: оператор is также является отражением.

http://en.wikipedia.org/wiki/Reflection_(computer_science)#Uses

Ответ 5

@Tim, я закончил использование упрощенной версии вашего подхода и ChanChans. Вот код:

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;
            }
        }
    }

Ответ 6

@ChanChan

Мне нравится идея отражения, но в то же время я всегда стеснялся использовать отражение. Мне всегда казалось, что это "хак", чтобы обойти что-то, что должно быть проще. Я рассмотрел этот подход, а затем решил, что оператор switch будет быстрее для того же объема запаха кода.

Вы заставили меня думать, я не думаю, что нужно перечислить тип, потому что я всегда могу сделать что-то вроде этого:

if (CurrentTask is MergeTask)
{
    // Do Something Specific to MergeTask
}

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

Ответ 7

Enum?

Я имел в виду свойство Type и перечисление в моем абстрактном классе.

Отражение это тогда! Я отмечу, что вы ответите так, как принято примерно через 30 минут, просто чтобы дать время, чтобы кто-то еще мог взвесить. Это веселая тема.

Ответ 8

Спасибо, что оставил его открытым, я не буду жаловаться. Это интересная тема, я бы хотел, чтобы вы могли полиморфно создать экземпляр.
Даже рубин (и его превосходное мета-программирование) должен использовать для этого механизм отражения.

Ответ 9

@Dale

Я не инспектировал nInject близко, но из моего высокого уровня понимания инъекции зависимостей, я считаю, что он выполнил бы то же самое, что и предложение ChanChans, только с большим количеством слоев трещины (er abstraction).

В одной ситуации, когда мне просто нужно это здесь, я думаю, что использование какого-то кода с обработанным отражением - это лучший подход, чем наличие дополнительной библиотеки для ссылки на нее и только вызов ее на одно место...

Но, может быть, я не понимаю преимущества, которые nInject дал бы мне здесь.

Ответ 10

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

С Ninject, главное, что мне понравилось в этом, заключается в том, что когда ему нужно использовать отражение, это не так. Вместо этого он использует преимущества генерации кода .NET, которые делают его невероятно быстрым. Если вы чувствуете, что отражение будет быстрее в контексте, который вы используете, это также позволяет вам настроить его таким образом.

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