Creating an object from from an ID (or name)

前端 未结 5 1250
借酒劲吻你
借酒劲吻你 2020-12-21 10:09

I have an abstract class that a lot of child classes inherit:

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


        
5条回答
  •  情歌与酒
    2020-12-21 10:49

    There are many way to create a type from a string. Reflection can be one of them, it's slow (but if you're processing web requests probably you won't care of this) but you won't need to keep a long list of hard-coded strings and/or to have an ugly long switch/case statement.

    If speed is not a problem then let's start with something easy:

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

    Using reflection you do not need switch/case and, it doesn't matter how many types you have, you won't change your factory code to accomodate new implementations of CrawlerBase. Simply add a new implementation et-voila.

    How it works? Well you can create an instance of a class from its Type definition (using Activator.CreateInstance) so the problem is to get the Type. Type class provides a static GetType method that can be used to create a Type from its (full) name. In this case we supply a namespace, it must be changed to the real one, if you keep all your classes in the same namespace you may write something like this:

    string rootNamespace = typeof(CrawlerBase).Namespace;
    

    And then the maked-up version:

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

    Improvements 1

    This is a very naive implementation, a better version should cache Type in a dictionary:

    private static Dictionary _knownTypes =
        new Dictionary();
    
    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;
    }
    

    Now you're OK, you do not need to manage a (possibly) long list of strings, you do not need to change an endless switch/case if you add/remove/change a type and to add a new Crawler all you have to do is to derive a new class. Please note that this function isn't thread-safe so, if you'll have multiple threads, you have to add a lock to protect dictionary access (or a ReadWriterLockSlim, it depends on your usage pattern).

    Improvements 2

    What's next? If your're requirements are more strict (about security, for example) you may need to add something more (I think it's not your case so you may not even need the type dictionary). What to do more? First you may check that the created type derives from CrawlerBase (using IsAssignableFrom and to provide a more meaningful exception instead of InvalidCastException), second you may decorate your classes with an attribute. Only classes with that attributes will be created (so you can keep private implementations that even if marked as public cannot be created from your users). There is much more than this but a simple implementation usually is enough to solve this kind of problems (when the input doesn't come from user but from robust configuration file or from the program itself).

提交回复
热议问题