Использование соглашений в C# для замены If (a is ..) else if (a is ..) конструкций

2 Янв
2012

Задача

Необходимо обрабатывать множество объектов имеющих общий базовый класс и/или интерфейс, например
var messages = List<IMessage>
Для каждого экземпляра необходимо реализовать свое поведение (стратегию) обработки. Причем, по каким-то причинам нежелательно или невозможно использовать наследование, чтобы инкапсулировать алгоритм обработки в класс сообщения и вызывать полиморфный метод.

Варианты

Первое что приходит на ум – это написать каскад условий для каждого возможного типа сообщения. Такая конструкция с кучей условий громоздкая и при добавлении новых вариантов обрастают все новыми и новыми ветвями.
Как вариант каскад условий можно генерировать с помощью T4, ручной труд по написанию условий для каждого нового класса автоматизируется, но все еще необходима пересборка для поддержки новых классов.
Используя соглашение об именовании классов производящих обработку каждого типа сообщений. Получать обработчик для каждого типа сообщений с помощью Reflection и небольшого класса.
Буду рад, если кто-то предложит другие варианты решения.

Решение

Предположим что есть классы сообщений:
 FooMessage, BarMessage
, реализующие интерфейс
IMessage
, в пространстве имен
Messages
.
Код реализующий их обработку помещается в классы:
 FooMessageProcessor, BarMessageProcessor
, реализующие интерфейс
IMessageProcessor
, в пространстве имен
MessagesProcessors
.
       foreach (var msg in messages)
       {
           var msgHandler = AssociatedHandler<IMessage, IMessageProcessor>.GetHandler(msg);
           msgHandler.Process(msg);
       }

Метод GetHandler анализирует имя типа параметра msg, очищает его от суффикса
Message
и получает чистое имя (Foo или Bar), далее в пространстве имен интерфейса
IMessageProcessor
подыскивается и возвращается экземпляр соответствующего класса. Полный код класса
AssociatedHandler
:
    class AssociatedHandler<SourceType, HandlerType>
    {
        private static Dictionary<string, HandlerType> sourceHandlerCache = new Dictionary<string, HandlerType>();
 
        public static HandlerType GetHandler(SourceType obj)
        {
            HandlerType handler = default(HandlerType);
 
            var sourceTypeName = obj.GetType().FullName;
            if (!sourceHandlerCache.TryGetValue(sourceTypeName, out handler))
            {
                var handlerTypeName = ConstructHandlerName(obj);
                handler = InitializeHandlerInstance(handler, handlerTypeName);
 
                if (handler != null)
                    sourceHandlerCache.Add(sourceTypeName, handler);
            }
 
            return handler;
        }
 
        private static HandlerType InitializeHandlerInstance(HandlerType handler, string handlerTypeName)
        {
            var handlerType = Type.GetType(handlerTypeName);
            handler = (HandlerType)Activator.CreateInstance(handlerType);
            return handler;
        }
 
        private static string ConstructHandlerName(SourceType obj)
        {
            string ns = typeof(HandlerType).Namespace;
            string clearSourceInterfaceName = typeof(SourceType).Name.Remove(01);
            string clearHandlerInterfaceName = typeof(HandlerType).Name.Remove(01);
            string sourceClearName = obj.GetType().Name.Replace(clearSourceInterfaceName, «»);
            string handlerTypeName = ns + «.» + sourceClearName + clearHandlerInterfaceName;
            return handlerTypeName;
        }
    }

Производительность такого решения отличается не более чем на 2% по сравнению с каскадом условий.

Другие варианты применения соглашений

Пока не дошли руки до реализации на практике, но возможно в некоторых случаях использовать соглашения таких целей как:
• создание объектов (фабрики)
• оборачивание объектов (враперы)
• подписка на события с особыми именами
• вызов методов с особыми именами
• обращение к свойствам
• использование вместе с DI контейнером
• Цепочки ответственности
По материалам Хабрахабр.



загрузка...

Комментарии:

Наверх