Базові класи як фабрики?


14

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

Моє запитання полягає в тому, щоб просто знати, чи це підхід # idomatic?

Моє відчуття, що це може бути не тим, що базовий клас має знання похідного класу.

Однак, я не впевнений у більш простому способі отримати той же результат. Цілий інший заводський клас здається мені (принаймні, як) непотрібною складністю (?)

Щось на зразок:

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
{
}

Як ви протестуєте свої заводи?

з кодом у цьому питанні, я б не став. але відповідь допомогла просунути мене на шляху інтеграції більше тестування в мій стиль кодування
Аарон Анодід

Подумайте, що, потягнувшись і у Seaworld, і у Landworld у своєму класі Animal, загалом це ускладнює поводження в ізольованих умовах.

Відповіді:


13

Що ж, перевага окремого фабричного класу полягає в тому, що його можна глузувати в одиничних тестах.

Але якщо ви цього не збираєтеся робити або робити це поліморфним будь-яким іншим способом, то статичний заводський метод для самого класу в порядку.


дякую, ви можете навести приклад того, про що ви маєте на увазі, коли ви говорите про поліморфний будь-який інший спосіб?
Аарон Анодід

Я маю на увазі, що якщо ви хочете мати можливість замінити один заводський метод на інший. Знущання над ним з метою тестування - лише один із найпоширеніших прикладів цього.
пдр

18

Ви можете використовувати Generics, щоб уникнути оператора перемикання та від'єднати наступні реалізації від базового класу.

 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>(); 


2
@PeterK. Фоулер посилається на оператори переключення в Code Smells, але його рішення полягає в тому, що їх слід витягувати до заводських класів, а не замінювати. Це означає, що якщо ви завжди будете знати тип на час розробки, generics - це ще краще рішення. Але якщо ви цього не зробите (як випливає з оригінального прикладу), я б заперечував, що використання рефлексії для уникнення перемикання на заводському методі може бути помилковою економією.
пдр

перемикання операторів і if-else - якщо ланцюги однакові. Я використовую перший через перевірку часу компіляції, що змушує обробляти справу за замовчуванням
Аарон Анодід

4
@PeterK, в операторі перемикання код знаходиться в одному місці. У повномасштабному OO один і той же код може бути розповсюджений на кілька окремих класів. Є сіра область, де додаткова складність наявності численних фрагментів менш бажана, ніж оператор переключення.

@Thorbjorn, у мене була така точна думка, але ніколи не настільки коротка, дякую за те, що
висловився

5

Якщо взяти до кінця, фабрика також може бути загальною.

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

Тоді можна створити будь-який тип фабричних об'єктів, який, у свою чергу, міг би створити будь-який тип об’єкта. Я не впевнений, що це простіше, це, звичайно, більш загальне.

Я не бачу нічого поганого в заяві перемикача для невеликої фабричної реалізації. Після того, як ви потрапили у велику кількість об'єктів чи можливих ієрархій різних класів, я думаю, що більш загальний підхід краще підходить.


1

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

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

Але вам все одно слід створити виділений заводський клас, від'єднавшись від ієрархії класів тварин, і повернути інтерфейс IAnimal, а не базовий тип. Тоді це стане в нагоді, ви б досягли певної розв’язки.


0

Це питання для мене своєчасне - я писав майже саме такий код вчора. Просто замініть "Animal" на те, що є релевантним для мого проекту, хоча я дотримуватись "Animal" заради обговорення тут. Замість заяви «switch» у мене був дещо складніший ряд висловлювань «if», що передбачав більше, ніж просто порівняння однієї змінної з певними фіксованими значеннями. Але це деталь. Статичний заводський метод видався акуратним способом проектування речей, оскільки конструкція виникла з рефакторингу раніше швидких і брудних помилок коду.

Я відхилив цю конструкцію на підставі того, що базовий клас має знання похідного класу. Якщо класи LandAnimal та SeaAnimal невеликі, акуратні та прості, вони можуть бути в одному вихідному файлі. Але у мене є великі безладні методи для читання текстових файлів, які не відповідають жодному офіційно визначеному стандарту - я хочу, щоб мій клас LandAnimal був у своєму власному вихідному файлі.

Це призводить до кругової залежності файлів - LandAnimal походить від Animal, але Animal вже повинен знати, що існують LandAnimal, SeaAnimal та п’ятнадцять інших класів. Я витягнув заводський метод, вклав його у свій власний файл (у моєму головному додатку, а не в моїй бібліотеці тварин). Статичний заводський метод здався милим і розумним, але я зрозумів, що він насправді не вирішує жодної проблеми дизайну.

Я поняття не маю, як це стосується ідіоматичного C #, оскільки я дуже перемикаю мови, я зазвичай ігнорую ідіоми та умовності, властиві мовам та стекам розробників поза моєю звичайною роботою. Якщо щось моє C # може виглядати "пітонічним", якщо це має сенс. Я прагну загальної ясності.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.