Come disaccoppiare semplice fabbrica e implementazione predefinita?

5

Ho una semplice classe ( FileResources ) con metodi di factory statici che forniscono un'implementazione predefinita ( DefaultFileResource ).

public final class FileResources {
    private FileResources() {}
    public static FileResource get(Path path) {
        return new DefaultFileResource(path);
    }
}

Il problema con questa classe è che è strettamente associato all'implementazione predefinita ( DefaultFileResource ).

Dato un nuovo requisito che la parte delle classi dell'API ( FileResources ) non può dipendere direttamente dalle classi che non fanno parte dell'API ( DefaultFileResource ) ho pensato che l'unica soluzione è l'inversione del controllo, o l'iniezione di dipendenza o un < s> service provider localizzatore di servizio.

È possibile disaccoppiare FileResources dall'implementazione predefinita ( DefaultFileResource )?

Per quanto ne so, questo è possibile solo iniettando la dipendenza in FileResources tramite un metodo costruttore, campo o setter.
Il problema principale è che il costruttore di FileResources è privato, inoltre ha solo metodi di factory static.

L'alternativa è il modello di metodo di fabbrica. Ci sarebbe una fabbrica astratta (una classe API) e ci sarebbero fabbriche concrete (classi non API) che potrebbero essere anche iniettate per disaccoppiare le classi API e non API.

In entrambi i casi è necessario un contenitore di dipendenze per gestire il cablaggio.

Che ne pensi? Sto pensando troppo a questo?

    
posta Vorzard 11.03.2014 - 18:38
fonte

2 risposte

6

No, non è necessario un contenitore IOC per disaccoppiare le classi API e non API. Perché non sono realmente accoppiati.

FileResources è accoppiato a Path e a FileResource, ma non a DefaultFileResource. Cioè, se cambi Path o FileResource, c'è il rischio di interrompere FileResources. Anche se non di grandi dimensioni. Quelli sono strati piuttosto sottili; questo è un accoppiamento accettabile.

Tuttavia, se cambi DefaultFileResource, se è ancora conforme a FileResource , non puoi interrompere FileResources.

Non interromperesti nemmeno i test per FileResorces. Testano solo che viene restituito un DefaultFileResource, nelle condizioni predefinite, e va bene.

Ancora più importante, il tuo codice chiamante non è accoppiato a nessuna particolare implementazione di FileResource. Quello è buono. In realtà è il punto centrale di un metodo di fabbrica. (E questo è un Metodo di Fabbrica.Non esiste una distinzione di Classe di Fabbrica, nel contesto in cui l'hai usata, non importa se il Metodo di Fabbrica esiste in un'altra classe.)

Tuttavia, il tuo codice chiamante è abbinato a FileResources. In particolare, se si modifica l'implementazione di FileResources, ci sono tutte le possibilità che si interrompano i test di unità che stanno testando il codice chiamante, perché il metodo get non può essere eliminato.

Questa è una preoccupazione molto più grande di qualsiasi conoscenza che FileResources ha su DefaultFileResource.

L'unico motivo per cui si utilizza un contenitore IOC per la factory è se si desidera creare DefaultFileResource con le sue dipendenze. Eviterei questo a meno che tu non ne abbia bisogno e, se ne avessi bisogno, darei uno sguardo al pattern Service Locator e perché dovrebbe essere evitato, prima che decidessi sull'uso di quel container IOC.

    
risposta data 11.03.2014 - 19:20
fonte
1

Cosa fare in realtà dipende da quanto lontano vuoi andare nelle astrazioni. Un modo semplice per fare è aggiungere un metodo .getInstance(Path path) statico alla classe FileResource astratta (se si tratta di un'interfaccia, la si trasformerebbe in una classe astratta), che restituirebbe quindi un'istanza predefinita. Questo potrebbe non soddisfare esattamente le tue esigenze ( FileResource ora dipenderebbe invece dall'implementazione predefinita) e la tua app potrebbe incorrere in problemi di ereditarietà se cambi un'interfaccia in una classe astratta.

La seconda opzione è creare un FileResourceFactory . Può essere una classe o un'interfaccia con classi di implementazione. Una singola classe di implementazione è più semplice, ma crea dipendenze dalle implementazioni, mentre un'interfaccia significa più complessità ma potenzialmente meno accoppiamento.

Poiché si tratta di una classe statica, non è possibile eseguire l'iniezione della dipendenza tramite il costruttore, ma è possibile creare un metodo setter per una FileResourceFactory statica e iniettarla quando necessario. Tuttavia, qui sorgono due avvertimenti:

  • Non puoi garantire che il factory statico sia stato impostato prima che il tuo metodo get venga chiamato
  • Stai vagando pericolosamente vicino alla zona della variabile globale con un membro statico su una classe come questa

Voglio anche sottolineare che, ad un certo punto, qualcuno alla fine ha bisogno di afferrare qualcosa di concreto. Con l'approccio basato sull'iniezione di dipendenze, puoi spingere questo molto lontano, ma alla fine qualcuno dipenderà dall'ottenere un'istanza concreta da qualche parte.

    
risposta data 11.03.2014 - 18:55
fonte