Best practice per incapsulare un parametro che richiede l'implementazione di più interfacce

0

Incontro questo problema più volte, ma non sono mai sicuro del modo migliore per affrontarlo.

Fondamentalmente, alcuni metodi che scrivo richiedono che l'oggetto di implementazione supporti più interfacce. L'esempio che ho in mente è che una struttura di tabella deve essere in grado di IIterate sui suoi record e IStreamable del suo contenuto, dove

IIterate = interface
  procedure First;
  procedure Next;
  property EOF: boolean;
end;

IStreamable = interface // outputs current record in table only
  function StreamOut: TArray<byte>;
  procedure StreamIn(const Input: TArray<byte>);
end;

Ora non penso di voler combinare queste interfacce, dato che è raro che i metodi richiedano IStreamable e non ogni IStreamable class possa IIterate (il mio caso d'uso ha anche strutture a record singolo).

Il metodo che sto cercando di scrivere è

CopyTable(InIterate: IIterate; InStreamable: IStreamable; OutIterate:IIterate; OutStreamable:IStreamable); 

Dove le interfacce In e Out devono essere implementate dallo stesso oggetto. Al momento sto solo chiedendo una delle interfacce e uso una chiamata Supports per ottenere il secondo, anche se questo non ti dà alcuna protezione in fase di compilazione. L'alternativa ovvia è usare la struttura sopra e controllare che entrambe le interfacce abbiano la stessa classe di implementazione, ma che sembri brutta.

C'è un modo migliore per lavorare con questo tipo di problema e ottenere un po 'di sicurezza in fase di compilazione?

Il linguaggio di implementazione è Delphi (XE2), anche se penso che questa sia una domanda agnostica abbastanza linguistica per le lingue che non hanno ereditarietà multipla.

    
posta Matt Allwood 03.12.2015 - 10:48
fonte

2 risposte

2

Innanzitutto, Iterate è un nome zoppo per quell'interfaccia, dovrebbe essere chiamato Iterator , quindi chiamerò Iterator in questa risposta, perché sono allergico alla cattiva denominazione.

Dici di incontrare questo problema spesso, e devo crederti, ma dovrei anche menzionare la mia esperienza, che è che raramente, se non mai, ho incontrato questo bisogno nel ultimi due decenni che ho fatto OOP.

Di solito, quando sembra emergere un tale bisogno, è un'indicazione che alcuni altri aspetti del design necessitano di refactoring.

Il primo sospetto da esaminare è la funzione CopyTable() : perché ha bisogno sia di Iterator sia di Streamable per la tabella di origine e di destinazione? Perché non è sufficiente eseguire lo stream-out della tabella di origine in byte e quindi eseguire lo streaming dei byte nella tabella di destinazione? E perché è chiamato anche CopyTable quando probabilmente è solo CopyStreamable() o CopyStreamableIterator() in peggiore?

Se non c'è nulla che possa essere fatto sulla funzione, l'approccio migliore secondo me è combinare le due interfacce in una sola. Il nome CopyStreamableIterator() che descrive ciò che la funzione fa immediatamente ti dice che hai bisogno di un'interfaccia StreamableIterator che combina Streamable e Iterator . Non c'è nulla di sbagliato in questo, e non devi neanche fare in modo che la tua classe% co_de implementa la nuova interfaccia, puoi semplicemente implementare un Table (vedi pattern decoratore ) che accetta un StreamableIteratorDecorator e un Streamable come parametri del costruttore e implementa l'interfaccia Iterator delegandole. (Anche se probabilmente dichiarerai la tua classe StreamableIterator come l'implementazione di questa interfaccia per essere eseguita in modo molto veloce, perché il tuo Table implementa già questi metodi, quindi è bello andare.)

Nota: presumo che in Delphi, come in Java e C #, una classe possa estendere solo una classe ma più interfacce e che un'interfaccia possa estendere più interfacce, ma anche se per caso questa non è vero, questa funzionalità può essere emulata aggregando le implementazioni dell'interfaccia in classi e aggiungendo alle classi e alle interfacce metodi che restituiscono sub-interfacce.

Modifica

Pensandoci un po 'di più, mi sto rendendo conto che il tuo problema potrebbe risiedere in quell'interfaccia di Table mal chiamata. Questa interfaccia sta cercando di tagliare gli angoli, quindi è costruita in modo tale da forzare la manipolazione dello stato interno dell'oggetto sottostante (implementato). Questo causa problemi, perché ti costringe a disporre di due interfacce separate, una per modificare lo stato interno della tabella per selezionare una riga e un'altra per leggere / scrivere la riga attualmente selezionata.

In una lingua come C # e Java la tua tabella implementerebbe IIterate , e IIterable<IStreamable> avrebbe solo un metodo, IIterable<T> . A sua volta, newIterator() : IIterator<T> sarà definito come segue:

IIterator<T> = interface //javaesque
  function Next : T;
  property EOF: boolean;
end;

o come segue:

IIterator<T> = interface //csharpesque
  function Current : T;
  function HasNext : boolean;
  procedure MoveNext;
end;

Quindi, invece di selezionare le righe, l'iterazione sarebbe che produce ogni riga che può quindi essere trasmessa in streaming. Quindi, devi solo passare un iteratore (o meglio ancora un iteratore) alla tua funzione IIterator<T> .

L'aggiunta di righe alla tabella di destinazione può essere facilmente realizzata implementando un'interfaccia CopyTable() che aggiunge semplicemente il contenuto di IConsumer<IStreamable> come nuova riga alla fine della tabella.

    
risposta data 03.12.2015 - 15:08
fonte
1

Java lo ha risolto semplicemente consentendo l'ereditarietà multipla per le interfacce. Ciò che è più interessante è anche il supporto per intersection types (ma solo per i generici). Quindi è possibile scrivere semplicemente:

<T extends InIterate & IStreamable > void something(T argument) {
    InIterate i = argument;
    IStreamable s = argument;
}

È sicuro sul tipo e non sono richiesti cast. In Java 8 è anche possibile utilizzare tale sintassi come tipo per espressioni lambda ma non esiste ancora un modo per dichiarare semplicemente la variabile come InIterate & IStreamable

    
risposta data 03.12.2015 - 13:46
fonte

Leggi altre domande sui tag