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.