У нас есть табличка в базе данных с поставщиками, каждому поставщику соответствует класс обработчика, который является наследником от базового класса. Надо сделать генератор для создания обработчиков.

В голову пришло три способа организации:

  1. switch .. case

  2. Добавить в табличку столбец и туда поместить постфиксы, дальше создавать объект на осное имени наследника, который должен включать этот постфикс

  3. Поместить индекс в атрибут класса-наследника и создавать объект, используя это значение

Первый способ:

    public class parcerA: ParcerBace {}

    public class parcerB: ParcerBace {}

    public abstract class ParcerBace
    {
        switch (id)
        {
          case 1: return new parcerA();
          case 2: return new parcerB();
        }
        return null;
    }

Дешево и сердито, но если этих обработчиков много, то мы получаем длинную простыню

Второй способ:

    public class parcerA: ParcerBace {}

    public class parcerB: ParcerBace {}

    public abstract class ParcerBace
    {
        public static ParcerBace creator(string postfix)
        {
            Type tFind = typeof(ParcerBace);
            Type tR = Assembly.GetAssembly(tFind).GetTypes().FirstOrDefault(t => t.IsSubclassOf(tFind) && t.Name== "parcer" + postfix);
            if (tR != null) return (ParcerBace)Activator.CreateInstance(tR);
            return null;
        }
    }

Тут довольно короткий код, но требуется следить за соответствием этих постфиксов в базе и наименованием объектов, что порождает ошибки

Третий способ:

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class IDAttribute : Attribute
    {
        public IDAttribute(int id) 
        {
            this.id = id;
        }
        public int id { get; set; }
    }

    [ID(1)]
    public class parcerA :ParcerBace {}

    [ID(2)]
    public class parcerB : ParcerBace {}

    public abstract class ParcerBace
    {
        public static ParcerBace creator(int id)
        {
            Type tFind = typeof(ParcerBace);
            Type tR = Assembly.GetAssembly(tFind).GetTypes().FirstOrDefault(t => t.IsSubclassOf(tFind) && (t.GetCustomAttribute(typeof(IDAttribute)) as IDAttribute).id == id);
            if (tR != null) return (ParcerBace)Activator.CreateInstance(tR);
            return null;
        }
    }

А теперь сравним производительность указанных способов:, создадим 1 000 000 объектов каждым из способов и сравним время. Очевидно, что от количества наследников время тоже будет зависеть

5 наследников

  1. способ 00:00.0249570

  2. способ 00:01.5358061

  3. способ 00:10.4656699

10 наследников

  1. способ 00:00.0433666

  2. способ 00:01.7637981

  3. способ 00:11.0782414

Улучшение способа 2 и 3 на основе обсуждения

Привожу код для поиска по именам, при поиске по идентификаторам, спрятанным в атрибуты делаем аналогичный перенос

public abstract class ParcerBace
{
    static Dictionary<string, Type> parcers;
    static ParcerBace()
    {
        Type tFind = typeof(ParcerBace);
        parcers = Assembly.GetAssembly(tFind).GetTypes().Where(t => t.IsSubclassOf(tFind) && t.Name.StartsWith("parcer")).ToDictionary(t => t.Name.Replace("parcer", ""));
    }
    public static ParcerBace creator(string postfix)
    {
        Type tR;
        parcers.TryGetValue(postfix, out tR);
        if (tR != null) return (ParcerBace)Activator.CreateInstance(tR);
        return null;
    }
}

Если предполагается много вызовов, то это даст значительный рост производительности

2. способ 10 наследников 1000000 вызовов 00:00.1450798

3. способ 10 наследников 1000000 вызовов 00:00.1883757

Комментарии (6)


  1. ALogachev
    09.09.2024 18:10

    Гуру поправят, но имхо если звать

    Assembly.GetAssembly(tFind).GetTypes().FirstOrDefault(t => t.IsSubclassOf(tFind) && t.Name== "parcer" + postfix);

    или

    Type tR = Assembly.GetAssembly(tFind).GetTypes().FirstOrDefault(t => t.IsSubclassOf(tFind) && (t.GetCustomAttribute(typeof(IDAttribute)) as IDAttribute).id == id);

    на каждый чих - в с случае миллиардов записей получится очень долго... как минимум хочется закешировать нужные типы по ид в Dictionary или Хештаблице


    1. SOProger Автор
      09.09.2024 18:10

      Само собой, если требуется высокая производительность, то первый способ будет быстрее и без создания дополнительных сложностей с кэшированием

      Если же задача загрузить обработчик в зависимости от выбранного пользователем поставщика. То получаем разовую подгрузку, что не скажется негативно на производительности


    1. SOProger Автор
      09.09.2024 18:10

      Добавил в статью проверку производительности и хеширование в словаре, спасибо


  1. doctorw
    09.09.2024 18:10
    +3

    Зачем наследовать от одного базового класса? Тут же банальная абстрактная фабрика.


  1. xFFFF
    09.09.2024 18:10

    Какое-то очень геморройное решение. Я бы сделал обработку через Стратегию, ну либо Абстрактная фабрика. Либо можно реализовать воркфлоу со скриптами, чтобы не перекомпилировать каждый раз.


  1. ncix
    09.09.2024 18:10
    +1

    Похоже, вы на пути изобретения паттерна Фабрика, но путь еще до конца не пройден.