Базовые классы как фабрики?

14

В выходные я писал некоторый код, и мне захотелось написать фабрику как статический метод в базовом классе.

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

Я чувствую, что это не может быть связано с тем, что базовый класс знает о производном классе.

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

Что-то вроде:

class Animal
{
  public static Animal CreateAnimal(string name)
  {
     switch(name)
     {
        case "Shark":
          return new SeaAnimal();
          break;
        case "Dog":
          return new LandAnimal();
          break;
        default:
          throw new Exception("unknown animal");
     }
  }
}
class LandAnimal : Animal
{
}

class SeaAnimal : Animal
{
}
Аарон Анодид
источник
Как вы будете тестировать свои фабрики?
с кодом в этом вопросе я бы не стал. но ответ помог мне
продвинуться
Учтите, что наличие в вашем классе животных класса «Морской мир» и «Земной мир» затрудняет управление в изолированной обстановке.

Ответы:

13

Что ж, преимущество отдельного фабричного класса в том, что его можно смоделировать в модульных тестах.

Но если вы этого не сделаете или не сделаете полиморфным каким-либо другим способом, тогда статический метод Factory для самого класса в порядке.

прецизионный самописец
источник
спасибо, не могли бы вы привести пример того, на что вы ссылаетесь, когда говорите полиморфным другим способом?
Аарон Анодид
Я имею в виду, что если вы когда-нибудь захотите иметь возможность заменить один фабричный метод другим. Насмешка над ним для целей тестирования - лишь один из самых распространенных примеров этого.
фунтовые
18

Вы можете использовать Generics, чтобы избежать оператора switch и отделить последующие реализации от базового класса:

 public static T CreateAnimal<T>() where T: new, Animal
 {
    return new T();
 }

Использование:

LandAnimal rabbit = Animal.CreateAnimal();  //Type inference should should just figure out the T by the return indicated.

или

 var rabbit = Animal.CreateAnimal<LandAnimal>(); 
Ник Nieslanik
источник
2
@PeterK. Фаулер ссылается на операторы switch в Code Smells, но его решение состоит в том, что они должны быть извлечены в классы Factory, а не заменены. Тем не менее, если вы всегда будете знать тип во время разработки, дженерики - еще лучшее решение. Но если вы этого не сделаете (как и предполагалось в первоначальном примере), то я бы сказал, что использование отражения, чтобы избежать переключения в фабричном методе, может быть ложной экономией.
фунтовые
операторы switch и цепочки if-else-if совпадают. Я использую первый из-за проверки времени компиляции, которая заставляет обрабатывать случай по умолчанию
Аарон Анодид
4
@PeterK, в выражении switch код находится в одном месте. В полномасштабной ОО один и тот же код может быть распределен по нескольким отдельным классам. Есть серая область, где сложность наличия многочисленных фрагментов менее желательна, чем оператор switch.
@ Torbjorn, у меня была именно эта мысль, но никогда не так лаконично, спасибо, что
выразил
5

В крайнем случае, фабрика также может быть универсальной.

interface IFactory<K, T> where K : IComparable
{
    T Create(K key);
}

Затем можно создать фабрику объектов любого типа, которая, в свою очередь, может создать объект любого типа. Я не уверен, если это проще, это, безусловно, более общий характер.

Я не вижу ничего плохого в операторе switch для небольшой фабричной реализации. Как только вы разберетесь с большим количеством объектов или, возможно, с различными иерархиями классов объектов, я думаю, что более общий подход лучше подходит.

Джон Рейнор
источник
1

Плохая идея. Во-первых, это нарушает принцип открытого-закрытого. Для любого нового животного вам придется снова связываться с вашим базовым классом, и вы можете его сломать. Зависимости пойдут не в ту сторону.

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

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

Мартин Маат
источник
0

Этот вопрос для меня актуален - вчера я писал почти такой код. Просто замените «Animal» тем, что имеет отношение к моему проекту, хотя я остановлюсь на «Animal» для обсуждения здесь. Вместо оператора «switch» у меня была несколько более сложная серия операторов «if», включающая не только сравнение одной переменной с определенными фиксированными значениями. Но это деталь. Статический фабричный метод казался изящным способом проектирования вещей, так как дизайн возник из рефакторинга ранних быстрых и грязных кодов.

Я отклонил этот проект на основании того, что базовый класс обладает знанием производного класса. Если классы LandAnimal и SeaAnimal маленькие, аккуратные и простые, они могут находиться в одном исходном файле. Но у меня есть большие грязные методы для чтения текстовых файлов, не соответствующих каким-либо официально определенным стандартам - я хочу, чтобы мой класс LandAnimal находился в его собственном исходном файле.

Это приводит к циклической зависимости файла - LandAnimal происходит от Animal, но Animal уже должен знать, что существуют LandAnimal, SeaAnimal и пятнадцать других классов. Я вытащил фабричный метод, поместил его в свой собственный файл (в моем основном приложении, а не в моей библиотеке Animals). Наличие статического фабричного метода казалось милым и умным, но я понял, что на самом деле он не решает никаких проблем проектирования.

Я понятия не имею, как это относится к идиоматическому C #, так как я часто переключаю языки, я обычно игнорирую идиомы и соглашения, свойственные языкам, и стеки разработчика вне моей обычной работы. Во всяком случае, мой C # может выглядеть "питоническим", если это имеет смысл. Я стремлюсь к общей ясности.

Кроме того, я не знаю, было бы преимущество использования метода статической фабрики в случае небольших, простых классов, которые вряд ли будут расширены в будущей работе - в некоторых случаях было бы неплохо иметь все это в одном исходном файле.

DarenW
источник