Creating an object from from an ID (or name)

前端 未结 5 1245
借酒劲吻你
借酒劲吻你 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:40

    Does this not work?

    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);
    }
    
    0 讨论(0)
  • 2020-12-21 10:43

    Make dictionary with factory methods:

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

    ...and throw away switch:

    foreach (var item in result)
    {
      factories[item.Type]().Process(item.URL);
    }
    
    0 讨论(0)
  • 2020-12-21 10:46

    As Redi pointed out you do call call Process on any instance since all derive from the same base class. You still have some repetitive code in your switch case which can be simplified if you move away from a switch (if/else) to a mapping construct.

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

    You can do it also with delegates like Dennis if you put a factory method as value into your dictionary.

    0 讨论(0)
  • 2020-12-21 10:48

    you can't do this?

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

    }

    0 讨论(0)
  • 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<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;
    }
    

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

    0 讨论(0)
提交回复
热议问题