Сведения о вопросе

Faridun

07:33, 9th August, 2020

Теги

c#   design-patterns   factory    

Абстрактный Шаблон Дизайна Фабрики

Просмотров: 447   Ответов: 10

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

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

Какой-нибудь совет?



  Сведения об ответе

fo_I_K

21:41, 8th August, 2020

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

убедитесь, что у вас есть "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");
        }
    }
}

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


  Сведения об ответе

lesha

03:59, 2nd August, 2020

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

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

тогда позвоните на него LoadFromXML.

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

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

Вы можете создать классы "self-registering", вызвав метод статического регистра на фабрике.

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

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


  Сведения об ответе

SEEYOU

00:34, 11th August, 2020

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


  Сведения об ответе

Chhiki

17:43, 15th August, 2020

@jholland

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

Перечисление?

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

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

BTW: оператор is-это тоже отражение.

http://en.wikipedia.org/wiki/Reflection_(вычислительная_техника)#использует


  Сведения об ответе

SILA

14:11, 7th August, 2020

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


  Сведения об ответе

park

21:06, 1st October, 2020

@ChanChan

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

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

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

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


  Сведения об ответе

DO__IT

11:00, 22nd August, 2020

Перечисление?

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

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


  Сведения об ответе

repe

09:04, 8th August, 2020

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


  Сведения об ответе

nYU

07:19, 18th August, 2020

@Dale

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

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

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


  Сведения об ответе

KOMP

20:48, 6th August, 2020

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

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

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться