01 · Problem
Wofür braucht man das?
Direktes Hinzufügen oder Entfernen während forEach kann Elemente überspringen, doppelt besuchen oder die Schleifenlogik schwer vorhersagbar machen.
02 · Denkmodell
Das mentale Modell
Die Iteration sieht einen Snapshot. Mutation landet in Queues und wird nur an klaren Flush-Grenzen atomar übernommen.
SafeArray exportiert add, remove, filter und forEach. addQueued leert die Add-Queue nie; dadurch werden alte Adds in jedem Durchlauf erneut angehängt und auch length/isEmpty falsch.
03 · Kontrollfluss
Was passiert in welcher Reihenfolge?
- Add und Remove in getrennten Pending-Strukturen sammeln.
- Vor Iteration einen Flush durchführen und beide Queues leeren.
- Über einen stabilen Snapshot iterieren.
- Währenddessen gesammelte Änderungen erst nach der Schleife übernehmen.
04 · Bauteile
Die entscheidenden Verträge
SafeArray.add / remove
Stellt Werte in Mutationsqueues.
forEach
Flusht, iteriert und verarbeitet Removals noch einmal.
filter
Baut ein neues Array über SafeArray.forEach.
length / isEmpty
Berechnet Werte aus Basisarray und Queues, gegenwärtig durch Add-Duplikate falsch.
05 · Build it yourself
Selbst implementieren
Definiere zuerst, ob Änderungen aus Callback N noch im selben oder erst im nächsten Durchlauf sichtbar werden.
- Konstruktorinput kopieren und Pending-Adds sowie Remove-Set anlegen.
- Flush übernimmt jede Queue genau einmal und leert sie sofort.
- Iteration arbeitet auf einer Kopie oder einem unveränderten Basispuffer.
class DeferredList<T> {
private values: T[] = [];
private adds: T[] = [];
private removes = new Set<T>();
add(value: T) { this.adds.push(value); }
remove(value: T) { this.removes.add(value); }
flush() {
this.values = this.values.filter(value => !this.removes.has(value));
this.values.push(...this.adds);
this.removes.clear();
this.adds = [];
}
}06 · Verifizieren
Was du testen solltest
- Ein Add erscheint nach beliebig vielen Flushes genau einmal.
- Remove während Iteration folgt der dokumentierten Sichtbarkeitsregel.
- Add und Remove desselben Objekts in einem Zyklus haben eine festgelegte Reihenfolge.
07 · Grenzen
Kompromisse und Stolperfallen
- Snapshots sind klar und erzeugen O(n)-Kopien.
- Referenzgleichheit genügt für Objekte nur bei stabiler Identität.
- Deferred Mutation schützt Iterationen, verzögert aber sichtbare Zustandsänderungen.
Das Konstruktorarray sollte kopiert werden, damit externe Mutation die internen Invarianten nicht umgeht.
08 · Weiterdenken