从 ID(或名称)创建对象
本文关键字:创建对象 ID | 更新日期: 2023-09-27 18:36:27
我有一个抽象类,很多子类都继承了它:
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 字符串将取决于需要实例化的子类。由于我所有的子类都继承了我的基类,因此调用 .进程() 在每个案例语句中。 我想将对象"爬虫"强制转换为正在实例化的子类,并调用爬网器。process() 在 switch 语句的末尾,如注释所示。 我该怎么做?
这行不通吗?
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);
}
有很多方法可以从字符串创建类型。反射可以是其中之一,它很慢(但如果你正在处理 Web 请求,你可能不会关心这个),但你不需要保留一长串硬编码字符串和/或有一个丑陋的长 switch/case 语句。
如果速度不是问题,那么让我们从简单的事情开始:
CrawlerBase crawler = (CrawlerBase)Activator.CreateInstance(
Type.GetType("MyNamespace." + item.Type));
crawler.Process(item.URL);
使用反射,您不需要开关/大小写,并且无论您有多少种类型,您都不会更改工厂代码以适应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;
}
现在你没事了,你不需要管理(可能)一长串字符串,如果你添加/删除/更改一个类型,你不需要改变一个无休止的开关/大小写,并且要添加一个新的爬虫,你所要做的就是派生一个新类。请注意,此功能不是线程安全的,因此,如果您有多个线程,则必须添加一个锁来保护字典访问(或ReadWriterLockSlim
,这取决于您的使用模式)。
改进 2
下一步是什么?如果你的要求更严格(例如关于安全性),你可能需要添加更多的东西(我认为这不是你的情况,所以你甚至可能不需要类型字典)。还要做什么?首先,您可以检查创建的类型是否派生自CrawlerBase
(使用 IsAssignableFrom
并提供更有意义的异常而不是 InvalidCastException
),其次您可以使用属性装饰您的类。将仅创建具有该属性的类(因此您可以保留私有实现,即使标记为公共也无法从用户创建)。远不止于此,但一个简单的实现通常足以解决此类问题(当输入不是来自用户而是来自健壮的配置文件或程序本身时)。
用工厂方法制作字典:
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);
}
你不能这样做吗?
{
CrawlerBase crawler = null;
switch (item.Type)
{
case "Trials":
crawler = new Trials();
break;
case "Coverage":
crawler = new Coverage();
break;
default:
break;
}
crawler.Process(item.URL);
}
正如 Redi 指出的那样,您可以在任何实例上调用调用 Process,因为所有实例都派生自同一个基类。在开关案例中,您仍然有一些重复的代码,如果您从开关(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 这样的委托一起执行此操作,如果您将工厂方法作为值放入字典中。