Un'alternativa migliore alle implementazioni incompatibili per la stessa interfaccia?

2

Sto lavorando su un pezzo di codice che esegue un'attività impostata in diversi ambienti paralleli in cui il comportamento dei diversi componenti nell'attività è simile ma molto diverso.

Questo significa che le mie implementazioni sono abbastanza diverse ma sono tutte basate sulle relazioni tra le stesse interfacce, qualcosa del genere:

IDataReader
   -> ContinuousDataReader
   -> ChunkedDataReader
IDataProcessor
   -> ContinuousDataProcessor
   -> ChunkedDataProcessor
IDataWriter
   -> ContinuousDataWriter
   -> ChunkedDataWriter

Quindi in entrambi gli ambienti abbiamo un IDataReader, IDataProcessor e IDataWriter e quindi possiamo usare Dependency Injection per assicurarci di averne uno corretto per l'ambiente corrente, quindi se stiamo lavorando con i dati in blocchi usiamo il ChunkedDataReader , ChunkedDataProcessor e ChunkedDataWriter e se abbiamo dati continui abbiamo le versioni continue.

Tuttavia, il comportamento di queste classi è abbastanza diverso internamente e non è certamente possibile passare da ContinuousDataReader a ChunkedDataReader anche se sono entrambi IDataProcessors . Mi sembra che non sia corretto (forse una violazione di LSP?) E certamente non è un modo di lavorare teoricamente corretto. È quasi come se l'interfaccia "reale" qui fosse la combinazione di tutte e tre le classi. Sfortunatamente nel progetto a cui sto lavorando con le scadenze a cui stiamo lavorando, siamo praticamente bloccati con questo design, ma se avessimo un po 'più di spazio al gomito, quale sarebbe un approccio di design migliore in questo tipo di scenario?

    
posta glenatron 21.09.2013 - 16:15
fonte

2 risposte

6

Il comportamento interno delle varie classi non è veramente rilevante. La grande domanda è se a ChunkedDataProcessor importa quale tipo di IDataReader e IDataWriter è accoppiato con.

Se un ChunkedDataProcessor non può funzionare con un ContinuousDataReader , ma accetta comunque un IDataReader , allora hai un problema nel tuo progetto. Potrebbe essere considerata una violazione dell'LSP, ma a mio parere, ChunkedDataProcessor accetta solo l'interfaccia sbagliata.

Supponendo che ci siano componenti nel sistema che possono funzionare con le varianti continue o chunked senza preoccuparsi di quale sia, io userei un progetto come questo:

interface IDataReader;
interface IContinuousDataReader : extends IDataReader;
interface IChunkedDataReader : extends IDataReader;

class ChunkedDataReader : implements IChunkedDataReader;

class ChunkedDataProcessor {
    IChunkedDataReader reader;
    //...
};

L'interfaccia IChunkedDataReader non ha nemmeno bisogno di aggiungere ulteriori metodi, ma la sua esistenza ti consente di imporre vincoli aggiuntivi all'implementazione che potrebbero non essere adatti per il più generico IDataReader . In ChunkedDataProcessor puoi quindi utilizzare quei vincoli aggiuntivi di IChunkedDataReader .

    
risposta data 21.09.2013 - 18:13
fonte
4

Per le lingue che supportano i generici, di solito è un mezzo efficace per gestire l'ereditarietà in parallelo con una o più "categorie". Ad esempio, la tua interfaccia potrebbe diventare:

IDataReader<TData>
IDataWriter<TData>
IDataProcessor<TData>

E quindi potresti implementare le tue classi come:

ChunkedDataReader : IDataReader<ChunkedData>
ChunkedDataWriter : IDataWriter<ChunkedData>
ChunkedDataProcessor : IDataProcessor<ChunkedData>

Oppure potresti persino dichiarare una singola classe che implementa tutti questi, come ad esempio:

ChunkedDataFile : IDataReader<ChunkedData>, IDataWriter<ChunkedData>,
    IDataProcessor<ChunkedData>

Quindi potresti avere una singola classe dipendente lungo le linee di:

class Foo<TData>
{
    public Foo(IDataReader<TData>, IDataWriter<TData>, IDataProcessor<TData>) { ... }
}

E questo costringerebbe tutte le istanze a operare sullo stesso tipo di dati in fase di compilazione.

Sfortunatamente, immagino che ciò implicherà una considerevole riprogettazione / re-architecting nel tuo caso, perché non hai avuto il tempo di definire cosa siano realmente i "dati chunked" o "dati continui". Questo è il problema - ora hai le cose in uno stile un po 'più procedurale rispetto ad oggetti, stai nominando le classi dopo le cose che fanno invece delle cose che sono . I verbi e le raccolte di verbi sono ciò che interfacce - non classi - sono per.

Quello che hai è ciò che a volte viene chiamato Modello di dominio anemico . Hai un importante concetto di dominio "chunked" o "continuous" data - ma non hai avuto il tempo di definire ciò che effettivamente è, in un oggetto standalone. Invece, ci sono solo un mucchio di procedure (racchiuse in interfacce e implementazioni specifiche del tipo) scritte attorno ad esso.

Se ti concentri maggiormente sui dati, invece di ciò che potresti fare per i dati, potresti trovare una soluzione pulita anche se non disponi di generici nel tuo set di strumenti. Ma personalmente, non riesco a pensare a un modo più pulito.

    
risposta data 21.09.2013 - 18:52
fonte