Dann also kein neuer Thread, aber ordentlich lange Beiträge
Zitat:
1.) jeder Stream kann intern auf einen einzigsten verlinkten Stream verweisen.
2.) besondere Streams arbeiten als Multiplexer/Verteiler per Listen von mehreren Streams, diese Verteiler/Multiplexer arbeiten im In/Output wie ein einzigster Stream, geben/holen aber ihren In/Output an mehrere Streams weiter.
Das sind Möglichkeiten, das Decorator-Pattern anzuwenden. Beides geeignet, allerdings stimme ich 1) nicht zu. Schließlich sind
FileInputStream,
SocketOutputStream oder
ArrayInputStream die jeweiligen Enden dieser Ketten und benötigen deshalb keine weiteren Referenzen.
Innerhalb einer Hierarchie sollte es deswegen meiner Meinung nach eine Abstract
InputStream klasse geben, von Denen zum einen die
DecoratorStreams bzw die
FilterStreams (ich verwende im Folgenden die letztere Bezeichnung) erben, sowie die Tatsächlichen Eingabeströme (so) und die von Dir vorgeschlagenen Multiplexer etc., im Folgenden als Kompositum bezeichnet, abgeleitet werden.
In der beispielhaften Hierarchie (Abb.1) wird der "lesende Zweig" der Stream-Hierarchie mit der abstrakten Klasse
Stream beschrieben, die Methoden zum Schließen (
Close) und zum überspringen von Datenworten (ich verwende der Einfachheit halber Bytes) mithilfe der Methode
Skip(...) anbietet. Von ihr Erbt die abstrakte Klasse
TInputStream. Sie beschreibt die Signatur eines Eingabestreams mit den beiden zusätzlichen Methoden
Read und
IsEOF. Basierend auf dieser Signatur zeigt das
UML-Diagram weiterhin die bereits erwähnten beispielhaften Klassen.
Anmerken möchte ich weiterhin, dass die Beiden Interfaces
IStream und
IInputStream den von Dir geforderten Interfaces zur Bearbeitung von abstrakten Datenquellen entsprechen, sie werden von den beiden abstrakten Oberklassen
TStream bzw
TInputStream implementiert (die Ermittlung der Chunkgröße habe ich aus Verfachungsgründen vernachlässigt).
Existiere zu jeder Klasse eine entsprechende Konstruktionsfunktion (auch als jew Klassenmethode denkbar), die statt des Exemplars eine Referenz auf das Interface zurückgibt, könnte man so eine Konstruktion zum einlesen einer vershlüsselten Datei so erreichen:
myStream:= DecryptInputStream(BufferedInputStream(FileInputStream('myFile'), 4096));
und mit eine Schleife der Form
Delphi-Quellcode:
while not myStream.IsEOF do
DoSth(myStream.Read);
gepuffterte Daten aus einer Datei entschlüsseln und an eine Methode
DoSth(...) übergeben.
Zitat:
Allerdings entsteht nun ein neues Problem: wie wird es fertiggebracht das jeder Stream am Ende einer Transaktion noch zusätzliche Daten anhängen bzw. entfernen kann ?
Wenn ich Deine Darstellung richtig verstanden habe, sollten sich diese Daten als "normale Daten des Stroms" vollkommen Transparent einarbeiten lassen, weil die jeweiligen Header- und Footer-Informationen nur für den jeweiligen Gegenpart (
InputStream vs.
OutputStream) von Bedeutung ist. Ein Pufferstream sollte deshalb ebenfalls diese Daten Puffern und braucht keine Kenntnis über die "Art der Information" zu besitzen.
Um meine Lösungsidee hierzu beschreiben zu können, möchte ich zunächst die Klasse
TFiFoStream einführen, die sowohl das bereits dargestellte Interface
IInputStream als auch das komplementäre Interface
IOutputStream (mit den beiden Methoden
Write(...) und
Flush) implementiert und als FiFo (First in First Out) Puffer fungiert. Die in ein Exemplar dieser Klasse geschriebenen Daten mithilfe des durch
IOutputStream geforderten Methoden können demnach anschließend über die Methoden des Interfaces
IInputStream gelesen werden (
TFiFoPuffer sollte nicht in die beschriebene Klassen-Hierarchie eingearbeitet werden sondern gesondert mit einer aggregierten Referenzzählung (siehe D7
TAggregatedObject) implementiert werden).
Unter Verwendung eines aggregierten Exemplars dieser Klasse kann
TInputStream die drei Template-Methoden (GoF)
BeforeRead(...),
DoRead(...) und
AfterRead(...) einführen und das Ermitteln des nächsten Datenabschnitts in Abhängigkeit des internen Status an die jeweilige Methode delegieren:
Delphi-Quellcode:
function TInputStream.
Read: Byte;
begin
// no more byte available -> exception
if IsEOF
then
raise E...
Result:= FiFoStream.
Read;
end;
function TInputStream.IsEOF: Boolean;
begin
// ensure next byte and State is prepared
PrepareRead;
Result:= FiFoStream.IsEOF;
end;
function TInputStream.PrepareRead;
begin
// only read if no byte available in fifo and stream not closed
if FiFoStream.IsEOF
and (State<ssClosed)
then
repeat
// do state specific action
case State
of
ssBefore: BeforeRead(FiFoStream);
ssReading: DoRead(FiFoStream);
ssAfter: AfterRead(FiFoStream);
end;
// go to next state
if (State
in [ssBefore, ssAfter])
or (FiFoStream.IsEOF)
then
FState:= Succ(State);
// loop until at least one byte available in fifo or no more byte
until not FiFoStream.IsEOF
or (State=ssClosed);
end;
Wie man erkennen kann, ist die Implementierung der Templatethoden nun relativ einfach in der Form
Delphi-Quellcode:
function TInputStream.BeforeRead(AnOutputStream: IOutputStream);
begin
AnOutputStream.Write(SomeHeaderData);
end;
realisierbar und auch auch das Byteweise lesen oder Verarbeiten in
DoRead(...) stellt kein Problem dar, weil der Status in
PrepareRead erst dann auf
ssAfter gesetzt wird, sobald keine weiteren Bytes in
DoRead(...)
an den FiFo-Puffer übergeben worden.
Fortsetzung folgt...