简体   繁体   English

抽象工厂设计模式

[英]Abstract Factory Design Pattern

I'm working on an internal project for my company, and part of the project is to be able to parse various "Tasks" from an XML file into a collection of tasks to be ran later. 我正在为我的公司开发一个内部项目,项目的一部分是能够将XML文件中的各种“任务”解析为稍后要运行的任务集合。

Because each type of Task has a multitude of different associated fields, I decided it would be best to represent each type of Task with a seperate class. 因为每种类型的Task都有许多不同的相关字段,所以我认为最好用一个单独的类来表示每种类型的Task。

To do this, I constructed an abstract base class: 为此,我构建了一个抽象基类:

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

Each task inherited from this base class, and included the code necessary to create itself from the passed in XmlElement, as well as serialize itself back out to an XmlElement. 每个任务都继承自此基类,并包含从传入的XmlElement创建自身所需的代码,以及将自身序列化为XmlElement。

A basic example: 一个基本的例子:

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

The parser would then use code similar to this to create a task collection: 然后,解析器将使用与此类似的代码来创建任务集合:

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

All of this works wonderfully, and allows me to pass tasks around using the base class, while retaining the structure of having individual classes for each task. 所有这些都非常有效,并且允许我使用基类传递任务,同时保留为每个任务创建单独类的结构。

However, I am not happy with my code for TaskFactory.CreateTask. 但是,我对TaskFactory.CreateTask的代码不满意。 This method accepts an XmlElement, and then returns an instance of the appropriate Task class: 此方法接受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");
        }
    }
}

Because I have to parse the XMLElement, I'm using a huge (10-15 cases in the real code) switch to pick which child class to instantiate. 因为我必须解析XMLElement,所以我使用了一个巨大的(实际代码中的10-15个案例)开关来选择要实例化的子类。 I'm hoping there is some sort of polymorphic trick I can do here to clean up this method. 我希望我能在这里做一些多态的技巧来清理这个方法。

Any advice? 有什么建议?

I use reflection to do this. 我用反射来做到这一点。 You can make a factory that basically expands without you having to add any extra code. 您可以创建一个基本上扩展的工厂,而无需添加任何额外的代码。

make sure you have "using System.Reflection", place the following code in your instantiation method. 确保你有“使用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");
        }
    }
}

Another observation, is that you can make this method, a static and hang it off of the Task class, so that you don't have to new up the TaskFactory, and also you get to save yourself a moving piece to maintain. 另一个观察结果是,你可以将这个方法设置为静态并将其挂在Task类之外,这样你就不需要新建TaskFactory了,而且你也可以自己保存一个移动的东西来维护。

Create a "Prototype" instanace of each class and put them in a hashtable inside the factory , with the string you expect in the XML as the key. 创建每个类的“Prototype”实例,并将它们放在工厂内的哈希表中,并将XML中的字符串作为键。

so CreateTask just finds the right Prototype object, by get() ing from the hashtable. 所以CreateTask只是通过哈希表中的get()来找到正确的Prototype对象。

then call LoadFromXML on it. 然后在上面调用LoadFromXML。

you have to pre-load the classes into the hashtable, 你必须将类预先加载到哈希表中,

If you want it more automatic... 如果你想要它更自动......

You can make the classes "self-registering" by calling a static register method on the factory. 您可以通过在工厂中调用静态寄存器方法来使类“自行注册”。

Put calls to register ( with constructors) in the static blocks on the Task subclasses. 将调用寄存器(带有构造函数)放在Task子类的静态块中。 Then all you need to do is "mention" the classes to get the static blocks run. 然后你需要做的就是“提及”类来运行静态块。

A static array of Task subclasses would then suffice to "mention" them. 然后,任务子类的静态数组就足以“提及”它们。 Or use reflection to mention the classes. 或者使用反射来提及类。

How do you feel about Dependency Injection? 您对依赖注入感觉如何? I use Ninject and the contextual binding support in it would be perfect for this situation. 我使用Ninject,其中的上下文绑定支持对于这种情况是完美的。 Look at this blog post on how you can use contextual binding with creating controllers with the IControllerFactory when they are requested. 请看这篇博客文章 ,了解如何在请求时使用IControllerFactory创建控制器时使用上下文绑定。 This should be a good resource on how to use it for your situation. 这应该是如何在您的情况下使用它的一个很好的资源。

@jholland @jholland

I don't think the Type enum is needed, because I can always do something like this: 我认为不需要Type枚举,因为我总能做到这样的事情:

Enum? 枚举?

I admit that it feels hacky. 我承认它感觉很乱。 Reflection feels dirty at first, but once you tame the beast you will enjoy what it allows you to do. 反射在开始时感觉很脏,但是一旦你驯服了野兽,你就会享受它允许你做的事情。 (Remember recursion, it feels dirty, but its good) (还记得递归,感觉很脏,但很好)

The trick is to realize, you are analyzing meta data, in this case a string provided from xml, and turning it into run-time behavior. 诀窍是要意识到,您正在分析元数据,在这种情况下是从xml提供的字符串,并将其转换为运行时行为。 That is what reflection is the best at. 这就是最好的反思。

BTW: the is operator, is reflection too. BTW:是运营商,也是反思。

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

@Tim, I ended up using a simplified version of your approach and ChanChans, Here is the code: @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;
            }
        }
    }

@ChanChan @ChanChan

I like the idea of reflection, yet at the same time I've always been shy to use reflection. 我喜欢反思的想法,但与此同时,我总是害羞地使用反射。 It's always struck me as a "hack" to work around something that should be easier. 它总是让我感到震惊,因为它可以解决一些应该更容易的事情。 I did consider that approach, and then figured a switch statement would be faster for the same amount of code smell. 我确实考虑过这种方法,然后认为一个switch语句对于相同数量的代码味道会更快。

You did get me thinking, I don't think the Type enum is needed, because I can always do something like this: 你确实让我思考,我认为不需要Type枚举,因为我总能做到这样的事情:

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

Perhaps I should crack open my GoF Design Patterns book again, but I really thought there was a way to polymorphically instantiate the right class. 也许我应该再次打开我的GoF设计模式书,但我真的认为有一种方法可以多态地实例化正确的类。

Enum? 枚举?

I was referring to the Type property and enum in my abstract class. 我在抽象类中引用了Type属性和枚举。

Reflection it is then! 然后反思吧! I'll mark you answer as accepted in about 30 minutes, just to give time for anyone else to weigh in. Its a fun topic. 我会在大约30分钟内将你的答案标记为已被接受,只是为了让其他人有时间权衡。这是一个有趣的话题。

Thanks for leaving it open, I won't complain. 谢谢你让它开放,我不会抱怨。 It is a fun topic, I wish you could polymorphicly instantiate. 这是一个有趣的话题,我希望你能多态地实例化。
Even ruby (and its superior meta-programming) has to use its reflection mechanism for this. 即使是ruby(及其优秀的元编程)也必须使用它的反射机制。

@Dale @Dale

I have not inspected nInject closely, but from my high level understanding of dependency injection, I believe it would be accomplishing the same thing as ChanChans suggestion, only with more layers of cruft (er abstraction). 我没有仔细检查nInject,但是从我对依赖注入的高级理解,我相信它将完成与ChanChans建议相同的事情,只有更多层次的抽象(抽象)。

In a one off situation where I just need it here, I think using some handrolled reflection code is a better approach than having an additional library to link against and only calling it one place... 在我只需要它的一次性情况下,我认为使用一些手动反射代码比一个额外的库链接反对并只调用它一个地方更好的方法...

But maybe I don't understand the advantage nInject would give me here. 但也许我不明白nInject会给我这里的优势。

Some frameworks may rely on reflection where needed, but most of the time you use a boot- strapper, if you will, to setup what to do when an instance of an object is needed. 某些框架可能依赖于需要的反射,但是大多数情况下,如果您愿意,可以使用引导程序来设置在需要对象实例时要执行的操作。 This is usually stored in a generic dictionary. 这通常存储在通用字典中。 I used my own up until recently, when I started using Ninject. 直到最近,当我开始使用Ninject时,我一直使用自己的。

With Ninject, the main thing I liked about it, is that when it does need to use reflection, it doesn't. 使用Ninject,我最喜欢它的是,当它确实需要使用反射时,它不会。 Instead it takes advantage of the code generation features of .NET which make it incredibly fast. 相反,它利用了.NET的代码生成功能,使其速度极快。 If you feel reflection would be faster in the context you are using, it also allows you to set it up that way. 如果您觉得在您使用的上下文中反射会更快,它也允许您以这种方式进行设置。

I know this maybe overkill for what you need at the moment, but I just wanted to point out dependency injection and give you some food for thought for the future. 我知道这对你目前所需要的东西来说可能有点过头了,但我只是想指出依赖注射并给你一些思考未来的食物。 Visit the dojo for a lesson. 参观道场上课。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM