Classi di base come fabbriche?


14

Stavo scrivendo del codice durante il fine settimana e mi sono ritrovato a voler scrivere una fabbrica come metodo statico in una classe base.

La mia domanda è semplicemente sapere se si tratta di un approccio ac # idomatico?

La mia sensazione che potrebbe non essere proviene dal fatto che la classe base ha conoscenza della classe derivata.

Detto questo, non sono sicuro di un modo più semplice per ottenere lo stesso risultato. Tutta un'altra classe di fabbrica sembra (almeno per me) come complessità non necessaria (?)

Qualcosa di simile a:

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

Come testerai le tue fabbriche?

con il codice in questa domanda, non lo farei. ma la risposta mi ha aiutato ad
avanzare

Considera che aver attirato Seaworld e Landworld nella tua classe Animal, tutto sommato rende più difficile la gestione in un ambiente isolato.

Risposte:


13

Bene, il vantaggio di una classe di fabbrica separata è che può essere deriso nei test unitari.

Ma se non hai intenzione di farlo, o renderlo polimorfico in nessun altro modo, allora un metodo Factory statico sulla classe stessa è OK.


grazie, potresti fare un esempio di ciò a cui ti riferisci quando dici polimorfico in qualche altro modo?
Aaron Anodide,

Voglio dire che se mai vuoi essere in grado di sostituire un metodo di fabbrica con un altro. Deridere a scopo di test è solo uno degli esempi più comuni di ciò.
pdr

18

È possibile utilizzare Generics per evitare l'istruzione switch e disaccoppiare anche le implementazioni a valle dalla classe base .:

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

Uso:

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

o

 var rabbit = Animal.CreateAnimal<LandAnimal>(); 


2
@PeterK. Fowler si riferisce alle istruzioni switch in Code Smells, ma la sua soluzione è che dovrebbero essere estratti nelle classi Factory, piuttosto che essere sostituiti. Detto questo, se hai sempre intenzione di conoscere il tipo in fase di sviluppo, generics è una soluzione ancora migliore. Ma se non lo fai (come implica l'esempio originale), direi che usare la riflessione per evitare un passaggio a un metodo di fabbrica può essere una falsa economia.
pdr

istruzioni switch e catene if-else-if sono uguali. io uso il primo a causa del controllo del tempo di compilazione che forza la gestione del caso predefinito
Aaron Anodide

4
@PeterK, in un'istruzione switch il codice è in un unico posto. In OO completo lo stesso codice può essere distribuito su più classi separate. C'è un'area grigia in cui la complessità aggiunta di avere numerosi frammenti è meno desiderabile di un'istruzione switch.

@Thorbjorn, ho avuto quel pensiero esatto, ma mai così succintamente, grazie per
avermi espresso le

5

Portata all'estremo, anche la fabbrica potrebbe essere generica.

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

Quindi si può creare qualsiasi tipo di fabbrica di oggetti, che a sua volta potrebbe creare qualsiasi tipo di oggetto. Non sono sicuro che sia più semplice, è sicuramente più generico.

Non vedo nulla di sbagliato in un'istruzione switch per un'implementazione di piccola fabbrica. Una volta che ti trovi in ​​un gran numero di oggetti o in possibili diverse gerarchie di classi di oggetti, penso che un approccio più generico sia più adatto.


1

Cattiva idea. Innanzitutto, viola il principio aperto-chiuso. Per ogni nuovo animale dovresti sbagliare di nuovo con la tua classe di base e potenzialmente potresti romperla. Le dipendenze andrebbero nella direzione sbagliata.

Se hai bisogno di creare animali da una configurazione, un costrutto come questo sarebbe una specie di OK, anche se usare la riflessione per ottenere il tipo che corrisponde al nome e crearne un'istanza usando le informazioni sul tipo ottenuto sarebbe un'opzione migliore.

Ma dovresti comunque creare una classe factory dedicata, slegata dalla gerarchia delle classi animali e restituire un'interfaccia IAnimal piuttosto che un tipo base. Quindi sarebbe utile, avresti ottenuto qualche disaccoppiamento.


0

Questa domanda è tempestiva per me - stavo scrivendo quasi esattamente questo codice ieri. Sostituisci semplicemente "Animale" con tutto ciò che è rilevante nel mio progetto, anche se rimarrò con "Animale" per motivi di discussione qui. Invece di un'istruzione 'switch', avevo una serie un po 'più complessa di istruzioni' if ', che implicavano più del semplice confronto di una variabile con determinati valori fissi. Ma questo è un dettaglio. Un metodo di fabbrica statico sembrava un modo pulito per progettare le cose, dato che il progetto è emerso dal refactoring di precedenti casini di codice rapidi e sporchi.

Ho rifiutato questo disegno sulla base della classe base che aveva conoscenza della classe derivata. Se le classi LandAnimal e SeaAnimal sono piccole, pulite e semplici, possono trovarsi nello stesso file di origine. Ma ho grandi metodi disordinati per leggere file di testo non conformi a standard definiti ufficialmente: voglio la mia classe LandAnimal nel suo file sorgente.

Ciò porta alla dipendenza circolare dei file: LandAnimal è derivato da Animal, ma Animal deve già sapere che esistono LandAnimal, SeaAnimal e altre quindici classi. Ho tirato fuori il metodo factory, l'ho messo nel suo file (nella mia applicazione principale, non nella mia libreria Animals) Avere un metodo factory statico mi è sembrato carino e intelligente, ma mi sono reso conto che in realtà non risolveva alcun problema di progettazione.

Non ho idea di come questo si riferisca al linguaggio C # idiomatico, dato che cambio molte lingue, di solito ignoro modi di dire e convenzioni peculiari delle lingue e gli stack di sviluppo al di fuori del mio solito lavoro. Semmai il mio C # potrebbe sembrare "pitonico" se questo è significativo. Miro alla chiarezza generale.

Inoltre, non so se ci sarebbe un vantaggio nell'utilizzare il metodo factory statico nel caso di classi piccole e semplici che difficilmente potrebbero essere estese in lavori futuri - avere tutto in un file sorgente potrebbe essere bello in alcuni casi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.