Come evitare le dipendenze tra i moduli

2

Abbiamo due moduli, chiamiamoli U e F.

Il modulo U ha entità del tipo:

class Upload {
    Long id;
    User uploadedBy;
    Date uploadedAt;
}

Il modulo F ha entità del tipo:

class Field {
    Long id;
    String displayName;
    Type type;
}

Entrambi hanno i tipici bean manager con creare, modificare, eliminare e alcune GUI a seconda di essi.

Quindi ora ciò che un cliente desidera avere è che noi aggiungiamo alle entrate del modulo U in modo che sembrino così:

class Upload {
    // fields as above
    List<Field> fields;
}

Non penso che sia la migliore idea, perché creerebbe una dipendenza tra F e U che non è necessaria per la maggior parte dei clienti. Così ora stiamo cercando di trovare una buona architettura che ci permetta di modellare ciò che il cliente vuole senza creare dipendenze.

Una cosa che mi è venuta in mente è stata creare un modulo E con entità del genere:

class ExtendedUpload1 {
    Upload upload;
    List<Field> fields;
}
// or...
class ExtendedUpload2 extends Upload {
    List<Field> fields;
}

Quindi solo quel modulo avrà dipendenze sia da F che da U, e sia F che U possono vivere separatamente. Ma abbiamo un'API tra GUI e server, che significa classi di valori separati e interfacce con cui entrambe le parti lavorano. E mentre possiamo iniettare funzionalità aggiuntive sia per la GUI che per il server, l'estensione dinamica dell'API è il vero problema. Dovremmo copiare tutte le classi API per lavorare con i nuovi valori.

Quali schemi architettonici si applicano a quel caso d'uso? Come potremmo risolvere elegantemente il problema?

    
posta Steffi S. 16.02.2015 - 14:32
fonte

4 risposte

1

Se ti ho capito bene, la tua situazione è la seguente:

  • hai clienti soddisfatti della classe% co_de vaniglia
  • almeno un altro ha bisogno di un'estensione
  • vuoi evitare la creazione di una classe Upload perché ti causa troppa seccatura (per quale motivo mai)
  • se possibile, vuoi evitare l'introduzione di una dipendenza tra F e U

Penso che in questa situazione, è meglio modificare la classe ExtendedUpload , anche se questa non è la soluzione preferita. Quindi alcune varianti di

 class Upload {
     // fields as above
     List<X> extensionFields;
 }

è probabilmente il modo migliore per andare. Per i clienti che non hanno bisogno delle estensioni, Upload continua a essere solo una lista vuota. Quindi il "cliente predefinito" è solo un caso speciale del "cliente esteso" (è necessario implementarlo), che è naturalmente incluso nella lista vuota, quindi non ci dovrebbe essere alcuna gestione esplicita necessaria per il "cliente predefinito" quando lo implementate attentamente.

Ciò che rimane è scegliere un tipo per extensionFields :

  • o vai con il tuo primo approccio, X . Solo tu sai quanto è un problema quando vivi con quella dipendenza.

  • oppure hai impostato X=Field , come suggerito da @dodev. Ciò evita l'accoppiamento diretto, infatti sposta l'accoppiamento dal tempo di compilazione al tempo di esecuzione

  • o imposti X=FieldInterface , dove X=UploadField è una classe che risiede nel modulo U, simile a UploadField , forse ridotta agli attributi di cui hai veramente bisogno da Field . Il vantaggio è che si evita la dipendenza e che Field ha il suo "ciclo di vita", indipendente da "Campo". Il rovescio della medaglia è che questo può causare una duplicazione del codice e, beh, UploadField ha il suo "ciclo di vita". Se un "campo di aggiornamento" deve essere sempre identico a "campo", ora devi mantenere due classi e mantenerle in-sync.

Quindi scegli la tua scelta, nessuna delle soluzioni è perfetta, questo è un trade-off che solo tu puoi risolvere.

    
risposta data 17.02.2015 - 10:36
fonte
2

Se comprendo correttamente la tua domanda, non vuoi menzionare l'exstince del modulo F nel codice del modulo U , rendendoli accoppiati liberamente, cioè il modulo F può essere sostituito alcune volte in futuro.

In questo caso, il primo che viene in mente:

Program to an interface, not an implementation - GoF

Fondamentalmente Upload::fields può essere di tipo List<FieldInterface> e il tipo Field può implementare FieldInterface . In questo modo U non ha bisogno di sapere nulla su F , l'unica cosa su cui U dipende è l'interfaccia astratta FieldInterface .

    
risposta data 16.02.2015 - 15:13
fonte
0

Se la lingua che usi supporta i generici, che ne dici di qualcosa come la seguente:

    class Upload<ExtendedData> {
      Long id;
      User uploadedBy;
      Date uploadedAt;
      List<ExtendedData> data;
    }

In questo modo, Caricamento e campo possono vivere in diversi namespaces / moduli, ma possono essere combinati nel caso specifico del client in cui l'utente desidera caricare istanze Field.

    
risposta data 17.02.2015 - 22:41
fonte
0

Programma per un'interfaccia viene solitamente utilizzato per il disaccoppiamento. Non importa quanto sia implementata l'interfaccia. I moduli F e U possono avere le proprie implementazioni. Se è necessario il disaccoppiamento completo, spostare l'interfaccia in un nuovo modulo I, in cui è possibile definire l'API. In questo modo, F e U sono consumatori per il modulo I e sono ancora indipendenti l'uno dall'altro.

    
risposta data 18.02.2015 - 07:36
fonte

Leggi altre domande sui tag