Sono le fabbriche che restituiscono solo un tipo di oggetto danneggiato?

3

Qualcuno in un post StackOverflow (non ho aggiunto la domanda ai preferiti purtroppo) ha commentato che le fabbriche che restituiscono un solo tipo di oggetto sono un odore di codice.

Mi trovo a scrivere questo tipo di fabbriche molto spesso. A volte perché non voglio gonfiare il mio codice con sovraccarichi del costruttore e qualche volta perché voglio solo dare un nome a un oggetto con attributi specifici. Una fabbrica sarebbe quindi simile a questa:

class CarFactory {
    public static Car createDefault() { return new Car(0, 0, 0, 0, 0, Color.BLACK) }
    public static Car createFancy() { return new Car(1, 1, 1, 1, 1, Color.RAINBOW) }
    ...
}

Questo è solo un esempio generale, ma penso che le chiamate sembrino molto meglio nel codice rispetto alle chiamate del costruttore con un carico di valori minacciosi.

Tuttavia questi tipi di fabbriche di solito non prendono decisioni in fase di runtime (come spesso fanno i metodi di produzione). E probabilmente non creerò nemmeno le interfacce (come usa il pattern Abstract Factory Pattern). È solo una classe che fornisce metodi di creazione statici, in modo che il mio codice non diventi ingombrante e ho un solo metodo per cambiare se penso che una macchina di lusso dovrebbe essere rossa con strisce.

Questi tipi di Classi di Fabbriche sono necessariamente cattivi?

    
posta Luca Fülbier 23.06.2016 - 00:09
fonte

4 risposte

7

Lo scopo del schema di fabbrica è di fornire un'astrazione tra la creazione dell'oggetto e il codice che necessita per creare oggetti. Ci sono molte ragioni per cui questo potrebbe essere desiderabile:

  • Gli oggetti potrebbero essere raggruppati e una fabbrica può gestire il pool. Questo è ciò che fa il Integer.valueOf(int) di Java.

  • Gli oggetti potrebbero avere costruttori ingannevoli da usare. Questo è spesso il segno di un oggetto che fa troppo, ma a volte è proprio come deve essere un oggetto. Una factory può fornire una singola posizione per richiamare il costruttore complesso.

  • Gli oggetti potrebbero avere uno stato che richiede sia una chiamata del costruttore che un'inizializzazione post-costruzione. Mentre una cosa brutta, potresti essere arretrato contro un muro e costretto a farlo a causa di vincoli di linguaggio o di struttura. Una factory ti consente di garantire che tutta la logica di creazione passa attraverso un chokepoint che può essere testato e riutilizzato, assicurando che tutti gli oggetti tutti siano creati correttamente.

Nessuno di questi risponde alla tua domanda direttamente, comunque.

YAGNI è un ottimo mantra, ma a volte sai che sarà necessario è, e è meno sforzo di farlo bene la prima volta che fare un lungo refactoring in seguito. Se abbozzo un progetto in cui so che ci sarà una complessa logica di costruzione, potrei andare avanti e creare una fabbrica prima, quindi non ho bisogno di eseguire noiosi processi di refactoring in seguito. Anche con un IDE, questo può richiedere ancora molte modifiche manuali.

Le fabbriche che restituiscono un singolo oggetto, fabbriche inflessibili , sono testabili. Se sai che ottieni sempre il giusto tipo di auto da quella fabbrica, il codice che usa le auto è più facile da testare. Forse ho un codice che blocca le auto insieme come un bambino che gioca con Hot Wheels . Se c'è un difetto nella mia logica di crash, è a causa della stessa logica di crash? Forse sta costruendo auto in modo errato? Una fabbrica come quella che hai menzionato potrebbe rendere più facile l'identificazione del codice difettoso. Se il test dell'auto è corretto ma il crash test fallisce, questo mi dice qualcosa di diverso da entrambi i test che falliscono.

Alla fine questa è una domanda un po 'soggettiva, ma ci sono ragioni oggettive per scegliere in un modo o nell'altro (fabbrica o istanziazione diretta). No, questo non è necessariamente un segno di codice errato. Osserva il design nel suo insieme e soppesa i costi e i benefici di entrambe le opzioni nel contesto del tuo progetto.

    
risposta data 23.06.2016 - 00:35
fonte
3

Non penso che ciò che stai mostrando nel campione sia cattivo, ma ci sono alcune alternative.

Puoi creare un tipo che rappresenti il tipo di auto, ad esempio, in minima parte

enum CarKind { Default, Fancy };

Quindi hai un semplice costruttore in Car che prende il CarKind , quindi i clienti lo fanno

new Car ( CarKind.Default )

invece di CarFactory.createDefault () .

Il vantaggio di questo è che puoi trattare il tipo di auto da creare come un valore che può essere memorizzato o passato in giro, mentre con il metodo CarFactory, potresti trovare i client costruendo il proprio meccanismo per quello (come usare un booleano per decidere se chiamare CarFactory. createDefault o createFancy .

Ciò si estende maggiormente nella direzione del modello di build rispetto al modello di fabbrica. L'idea del modello di builder è che hai un oggetto che rappresenta ciò che vuoi costruire (ad esempio Car ), e può essere manipolato (passato in giro, modificato) e infine invocato per generare il Car di scelta .

Penso che questo sia più quello che cerchi rispetto al modello di fabbrica, dal momento che come dici tu non stai restituendo sottotipi diversi. Lo scopo del modello di builder è specificamente quello di affrontare un gran numero di parametri o opzioni nei costruttori, che stai suggerendo nel tuo esempio di codice.

(Tuttavia, puoi combinare anche factory e builder, quindi, ad esempio, un oggetto builder può costruire e restituire sottotipi diversi secondo necessità.)

    
risposta data 23.06.2016 - 00:30
fonte
2

Sono un odore? No, non lo sono. Rendono il nostro codice migliore separando le responsabilità di creare un oggetto e di usarlo.

Non so se la tua lingua ha il concetto di "usare" una risorsa, quindi di eliminare automaticamente una risorsa, ma può ostacolare la testabilità .

Considerare il seguente codice da un'implementazione del servizio.

public DomainObject Get(int id)
{
     using (var dbContext = new DbContext())
     {
         return dbContext.Get(id).ToDomainObj();
     }
}

Vogliamo mantenere l'ambito di DbContext piccolo, ma ora non possiamo testare facilmente il servizio o scambiare DbContext senza modificare ogni% di istruzione di% co_de nel servizio. Abbiamo due opzioni.

  1. Iniettare DbContext, aumentare la durata della sua vita e spingere la responsabilità di rilasciarlo su un livello.
  2. Crea una factory semplice come il tuo campione, inietti it e disaccoppia la nostra persistenza dal servizio disaccoppiando la creazione di questi oggetti.
public DomainObject Get(int id)
{
     using (var dbContext = this.factory.Create())
     {
         return dbContext.Get(id).ToDomainObj();
     }
}

Ora, questo può essere ripulito un po 'perché Funcs and I delegati possono essere utilizzati come semplici fabbriche . Invece di avere una classe (o un gruppo di classi) con un singolo metodo, puoi semplicemente passare un delegato nel costruttore del servizio.

 public Service(Func<DbContext> factory)
 {
      this.CreateDbContext = factory;
 }

public DomainObject Get(int id)
{
     using (var dbContext = this.CreateDbContext())
     {
         return dbContext.Get(id).ToDomainObj();
     }
}

Dove crei il servizio in questo modo.

var service = new Service(()=> new DbContext());

Mi scuso per tutti gli esempi di C #. Non so quanto questo valga per la tua lingua particolare, ma spero che ciò dimostri che il tipo di fabbriche di cui stai parlando non sono davvero un odore. In realtà rendono il nostro codice migliore e più flessibile.

    
risposta data 23.06.2016 - 01:40
fonte
1

Penso che tu abbia alluso alla risposta di quando è male:

these kinds of factories usually don't do runtime decision making

A mio parere, le fabbriche dovrebbero generalmente essere utilizzate solo quando non sai al momento della compilazione cosa devi creare.

Mentre l'esempio di fabbrica che hai fornito potrebbe effettivamente servire a uno scopo utile in qualunque contesto sia usato, personalmente darei un altro nome in modo da evitare di scambiarlo con una fabbrica reale con uno scopo veramente utile.

    
risposta data 23.06.2016 - 01:08
fonte

Leggi altre domande sui tag