3

我有一个许多子类继承的抽象类:

  public abstract class CrawlerBase
    {
        public abstract void Process(string url);
    }

我正在处理这个循环:

foreach (var item in result)
                {
                    object crawler = null;

                    switch (item.Type)
                    {
                        case "Trials":
                            var t = new Trials(); 
                            ct.Process(item.URL); //repetitive code.  
                            break;

                        case "Coverage":
                            var c = new Coverage();
                            c.Process(item.URL); //repetitive code.
                            break;
                        default:
                            break;
                    }

                   // crawler.Process(item.URL);
                }

现在 item.type 字符串将取决于需要实例化的子类。由于我的所有子类都继承了我的基类,因此在每个 case 语句中调用 .Process() 会非常重复。我想将对象“爬虫”转换为正在实例化的子类,并在 switch 语句的末尾调用 crawler.Process(),如注释所示。我怎样才能做到这一点?

4

5 回答 5

4

这不起作用吗?

foreach (var item in result)
{
    CrawlerBase crawler = null;

    switch (item.Type)
    {
        case "Trials":
            crawler = new Trials(); 
            break;
        case "Coverage":
            crawler = new Coverage();
            break;
        default:
            break;
    }

    if(crawler != null)
        crawler.Process(item.URL);
}
于 2012-06-07T18:58:04.307 回答
2

用工厂方法制作字典:

var factories = new Dictionary<string, Func<CrawlerBase>>
{
  {"Trials", () => new Trials()},
  {"Coverage", () => new Coverage()},
// and so on
}

...并扔掉switch

foreach (var item in result)
{
  factories[item.Type]().Process(item.URL);
}
于 2012-06-07T18:59:02.480 回答
2

有很多方法可以从字符串创建类型。反射可以是其中之一,它很(但如果您正在处理 Web 请求,您可能不会关心这个),但您不需要保留一长串硬编码字符串和/或有一个丑陋的长 switch/case 语句。

如果速度不是问题,那么让我们从简单的事情开始:

CrawlerBase crawler = (CrawlerBase)Activator.CreateInstance(
    Type.GetType("MyNamespace." + item.Type));

crawler.Process(item.URL);

使用反射,您不需要 switch/case,并且无论您有多少类型,您都不会更改工厂代码以适应CrawlerBase. 只需添加一个新的实现 et-voila。

这个怎么运作?Type那么你可以从它的定义(使用)创建一个类的实例,Activator.CreateInstance所以问题是要获得Type. Type类提供了一个静态GetType方法,可用于Type从其(完整)名称创建一个。在这种情况下,我们提供了一个命名空间,它必须更改为真正的命名空间,如果您将所有类都保留在同一个命名空间中,您可以编写如下内容:

string rootNamespace = typeof(CrawlerBase).Namespace;

然后是化妆版:

CrawlerBase crawler = (CrawlerBase)Activator.CreateInstance(
    Type.GetType(rootNamespace + "." + item.Type));

crawler.Process(item.URL);

改进 1

这是一个非常幼稚的实现,更好的版本应该缓存Type在字典中:

private static Dictionary<string, Type> _knownTypes =
    new Dictionary<string, Type>();

private static GetType(string name)
{
    string fullName = typeof(CrawlerBase).Namespace
        + "." + name;

    if (_knownTypes.ContainsKey(fullName))
        return _knownTypes[fullName];

    Type type = Type.GetType(fullName);
    _knownTypes.Add(fullName, type);

    return type;
}

现在你没事了,你不需要管理一个(可能)很长的字符串列表,如果你添加/删除/更改类型并添加一个新的 Crawler,你不需要更改无穷无尽的开关/案例。要做的是派生一个新的类。请注意,这个函数不是线程安全的,所以如果你有多个线程,你必须添加一个锁来保护字典访问(或者一个ReadWriterLockSlim,这取决于你的使用模式)。

改进 2

下一步是什么?如果您的要求更严格(例如关于安全性),您可能需要添加更多内容(我认为这不是您的情况,因此您甚至可能不需要类型字典)。还要做什么?首先,您可以检查创建的类型是否派生自CrawlerBase(使用IsAssignableFromand 来提供更有意义的异常而不是InvalidCastException),其次您可以使用属性装饰您的类。只会创建具有该属性的类(因此您可以保留私有实现,即使标记为公共也无法从您的用户创建)。不仅如此,但一个简单的实现通常足以解决这类问题(当输入不是来自用户而是来自健壮的配置文件或来自程序本身时)。

于 2012-06-07T18:59:23.680 回答
1

你不能这样做吗?

 {
   CrawlerBase crawler = null;

   switch (item.Type)
   {
        case "Trials":
            crawler = new Trials();   
            break;

        case "Coverage":
            crawler = new Coverage();
            break;
        default:
            break;
    }
   crawler.Process(item.URL);

}

于 2012-06-07T18:59:06.490 回答
1

正如 Redi 指出的那样,您确实在任何实例上调用 call Process,因为它们都派生自同一个基类。你的 switch case 中仍然有一些重复的代码,如果你从 switch (if/else) 移到映射构造,可以简化这些代码。

Dictionary<string, Type> Types = new Dictionary<string, Type>
{
    { "Trial", typeof(Trials) },
    { "Coverage", typeof(Coverage) },
};

void Crawl()
{
    string itemType = "Trial";
    CrawlerBase crawler = (CrawlerBase)Activator.CreateInstance(Types[itemType]);
    crawler.Process("url");
}

如果您将工厂方法作为值放入字典中,您也可以使用 Dennis 之类的委托来执行此操作。

于 2012-06-07T19:06:06.600 回答