Как избежать переключения в заводском методе дочерних классов

#c# #design-patterns

#c# #шаблоны проектирования

Вопрос:

Допустим, у нас есть семейство классов (cards, ради этого), и нам нужно создать их экземпляры на основе некоторого идентификатора. Заводской метод будет выглядеть следующим образом:

 public Card GetCard(int cardNumber) 
{
   switch(cardNumber) 
   {
     case 13: return new King();
     case 12: return new Queen();
     case 11: return new Jack();          
   }

   //...
}
  

Чего я хочу, так это избежать этого switch . Почему? Возможно, я хочу повторно использовать это сравнение в функции.

То, что я придумал, примерно так:

 private Dictionary<int, Type> cardTypes = 
 { 
   {13, typeof(King)},
   {12, typeof(Queen)},
   {11, typeof(Jack)}
 };

 public Card GetCard(int cardNumber) 
 {        
    var cardType = cardTypes[cardNumber];
    var instance = Activator.CreateInstance(cardType);
    return (Card)instance;
 }
  

Однако это решение использует отражение, которое является дорогостоящим, а также проблематичным, когда у вас более одного «идентификатора» (например, 1 и 14 дают Ace оба — должен ли я добавить 2 ключа в словарь?).

Какова наилучшая практика в этом сценарии?

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

1. Я лично думаю, что если коллекция типов фиксирована, четко известна И коротка (в человеческом смысле), фабричный шаблон требует значительных изменений, ваше первое решение может быть достаточно хорошим. Если он должен быть расширяемым Или доступным для чтения в будущих неизвестных типах ИЛИ огромным, может подойти factory. Но это наиболее спорно 😉

Ответ №1:

Вместо сохранения типа в словаре, вы могли бы сохранить Func<Card> :

 private Dictionary<int, Func<Card>> cardFactories = 
{
    { 13, () => new King() },
    // etc
}

public Card GetCard(int cardNumber) 
{        
    var factory = cardFactories[cardNumber];
    return factory();
}
  

В случае cards я бы, вероятно, сделал их неизменяемыми для начала и просто заполнил словарь самими картами, но это другое дело 🙂

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

1. @Caspar: Да, вы, безусловно, могли бы использовать константы вместо этого. На самом деле суть вопроса не в этом, хотя именно это я и пытался решить…

2. это отличное решение, спасибо! a. Что насчет проблемы с Ace? вы бы повторили функцию Ace дважды для 1 и 14? B. Что, если каждой карточке нужно получить параметр, скажем, Suit (цветной) объект? Не могли бы вы создать делегат Card delg(Suit) и затем использовать (suit) => new King(suit) в словаре?

3. @yellowblood: Это будет зависеть от реальной ситуации, которая, предположительно, не для cards. Зачем вам иметь два идентификатора для одного и того же значения? Можете ли вы сначала канонизировать, например?

4. @Caspar: Я думаю, что магические числа имели бы смысл в этой вещи; на самом деле, все cardFactories являются константой. (конечно, я бы это сделал static reaonly )