01 · Problem
Wofür braucht man das?
Beim Arbeiten mit Maps wiederholen sich Muster wie get-transform-set, selektives Kopieren und Konfliktauflösung beim Zusammenführen.
02 · Denkmodell
Das mentale Modell
Map bleibt die Datenstruktur; Helfer sollten lediglich atomare Collection-Operationen ausdrücken und klar sagen, ob sie die Instanz mutieren oder eine neue erzeugen.
Aktualisiert, filtert und vereinigt Map-Einträge mit explizitem Verhalten für fehlende Werte. Werte behalten ihre Referenzidentität; Callbacks sind synchron und können werfen.
03 · Kontrollfluss
Was passiert in welcher Reihenfolge?
- Mit has zwischen fehlendem Schlüssel und vorhandenem undefined unterscheiden.
- Vorhandenen Wert aktualisieren oder einen expliziten Initialwert erzeugen.
- Das Ergebnis genau einmal per set speichern.
- Bei Merge-Konflikten eine dokumentierte Resolver-Funktion anwenden.
04 · Bauteile
Die entscheidenden Verträge
updateTransformiert vorhandene Werte oder initialisiert fehlende Einträge.filter / mergeFiltert in eine neue Map oder vereinigt mutierend.05 · Selbst bauen
Selbst implementieren
Der wichtigste Entwurfspunkt ist nicht die Vererbung, sondern eine eindeutige Abwesenheits- und Mutationssemantik.
- Prüfe Existenz immer mit has statt über den gelesenen Wert.
- Trenne create und update, damit undefined nicht als versteckter Initialwert dient.
- Lass mutierende Methoden this zurückgeben oder benenne immutable Varianten ausdrücklich.
class UpdatingMap<K, V> extends Map<K, V> {
update(key: K, change: (value: V) => V, create: () => V): this {
const next = this.has(key) ? change(this.get(key) as V) : create();
this.set(key, next);
return this;
}
select(predicate: (value: V, key: K) => boolean) {
const result = new UpdatingMap<K, V>();
for (const [key, value] of this) {
if (predicate(value, key)) result.set(key, value);
}
return result;
}
}06 · Verifizieren
Was solltest du testen?
- Fehlender Schlüssel, vorhandenes undefined und vorhandenes null bleiben unterscheidbar.
- Filter verändert die Quelle nicht und erhält Schlüssel-Wert-Paare.
- Merge-Resolver wird ausschließlich bei echten Konflikten aufgerufen.
Selbst ausprobieren
Formuliere die erste Prüfaussage als fehlschlagenden Test, implementiere die kleinste Korrektur und ergänze einen Grenzfall.
Fehlender Schlüssel, vorhandenes undefined und vorhandenes null bleiben unterscheidbar.
Ansatz vergleichen
Dein Test sollte beim gezielten Verletzen der Invariante fehlschlagen und nach der Korrektur wieder bestehen. Halte den Aufbau deterministisch und prüfe ein beobachtbares Ergebnis.
07 · Grenzen
Kompromisse und Stolperfallen
- Unterklassen nativer Collections können bei Serialisierung und Cross-Realm-Nutzung überraschen.
- Mutierende und immutable Operationen in derselben API brauchen besonders klare Namen.
Werte behalten ihre Referenzidentität; Callbacks sind synchron und können werfen.
08 · Weiterdenken
Quellcode und verwandte Ideen
yarn tsx packages/better-map/examples/basic.ts