Абстрактный шаблон дизайна фабрики

Я работаю над внутренним проектом для своей компании, и часть проекта состоит в том, чтобы иметь возможность анализировать различные «Задачи» из 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 случаев в реальном коде) переключатель, чтобы выбрать, какой дочерний класс создать. Я надеюсь, что я могу применить какой-то полиморфный трюк, чтобы очистить этот метод.

Любой совет?

Проверка всех случаев дает преимущество в плане безопасности. отражение ответ открыт для бреши в безопасности, когда кто-то внедряет класс Task (не созданный вами) в ваш путь и выполняет его, получая его имя в XML. Хакеры сделали еще более тяжелые вещи.

Fuhrmanator 04.10.2012 22:39
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
21
1
4 858
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Ответ принят как подходящий

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

убедитесь, что у вас есть «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, а также вы могли сэкономить подвижную часть для обслуживания.

@ChanChan

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

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

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

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

@jholland

I don't think the Type enum is needed, because I can always do something like this:

Enum?

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

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

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

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

Enum?

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

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

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

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

@Дол

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

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

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

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

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

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

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

Jeff Meatball Yang 12.02.2010 19:03

Вы можете зарегистрировать типы вручную, если хотите: Bind (typeof (IController)). To (typeof (HomeController)). Only (When.ContextVariable («controllerName»). EqualTo («Home»)); Сообщение в блоге просто показывает, как вы можете сделать это программно, поскольку все контроллеры наследуются от IController. В приложении ASP.NET MVC у вас обязательно будет много контроллеров, и регистрация каждого из них вручную может быть утомительной.

Dale Ragan 13.02.2010 03:05

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

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

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

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

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

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

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

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

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

Другие вопросы по теме