Monsterklassen und das SRP
Phase 1: Das initiale Konzept
Nehmen wir an, es gäbe eine Klasse, die folgende Aufgabe erledigt:

Folgerichtig gibt es jede Menge aufeinander abgestimmter Methoden, um die Struktur aufzubauen:
public IJSONArray AddArray( string key) { ... } public IJSONObject AddObject( string key) { ... } public IJSONProperty SetProperty( string key) { ... }Und so weiter. Mit den zurückgelieferten Teilen kann man dann ebenso verfahren. Außerdem gibt es auf der Ausgabeseite eine oder mehrere Methoden zum Rendern:
public void Render( Stream destination) { ... }Bis hierher muß man als Anwender der Klasse nicht wissen, wie das ganze intern funktioniert. Es ist einfach, intuitiv anwendbar, sozusagen "neat and clean".
Phase 2: Verschlimmbesserung
Jetzt kommt aber der Fall, wo sich der Code des Anwenders nicht mehr an das erinnern kann, was er zuvor gerade getan hat. Es scheint daher erforderlich, ja geradezu alternativlos, in den Interna der diensteifrigen JSON-Renderer-Klasse nach bereits angelegten Properties zu gründeln, Listen zu enumerieren, Werte wieder auszulesen und was nicht alles noch.
Ergo werden einfach schnell (klicki-klacki) ein paar Get-Methoden angeheftet und fertig.
Fertig? Ja, aber total. Das Konzept sieht nämlich jetzt plötzlich so aus:

Und noch viel schlimmer: die auf die obige Aufgabe hin optimierte Klasse sieht sich damit plötzlich völlig anderen Anforderungen ausgesetzt, was zB. zu Performanceproblemen führen kann und dem bekannten Effekt „Warum hat der das nur so komisch geschrieben, da hätte man doch gleich XYZ machen können, das wäre doch viel besser gewesen?“. Ja klar – im Lichte der neuen Anforderungen vielleicht, aber wir reden ja auch nicht mehr über das ursprüngliche Konzept!
Außerdem führt dieser Weg des unbedachten Konzept-Verwässerns geradewegs in Richtung der Monsterklassen, die wir alle gut kennen: Die wollen immer alles können, aber genau betrachtet geht nichts wirklich richtig gut oder performant. Das konkrete Beispiel läßt sich beliebig austauschen, und jeder findet wohl beim Nachdenken selbst schnell ein paar Klassen, Subsysteme und Units, auf die das zutrifft.
Was ist denn nun aber die Lösung? Gibt es Vorschläge?
Wie so oft gibt es keine Patentlösung, sondern man sollte das ganze fallabhängig betrachten. Im konkreten Fall war es vermutlich pure Faulheit, den aufrufenden Algorithmus ordentlich zu designen und sich stattdessen lieber bei den Interna einer anderen Klasse zu bedienen.
„Wieso denn aber nicht? Die Werte sind doch da drin?! Ich habe die doch selbst übergeben!“
Sicher, aber es sind eben nicht mehr deine Daten, sondern die des JSON-Renderers. Und es ist einzig und allein dem Renderer überlassen, was er damit macht – dich als Aufrufer geht das fei garnix mehr an. Der Pfeil im ursprünglichen Konzept geht nämlich nicht grundlos nur in eine Richtung, sondern das ist ein Teil der Idee.
Test (neudeutsch: Self Assessment)
Wer nun immer noch meint, es wäre überhaupt kein Problem, hat das mit der Kapselung und Separation of Concerns irgendwie wohl doch noch nicht ganz verstanden. Schauen wir mal:
Welche Antwort wählst Du? A oder B?
Antwort A: Schnittstellen sollten
- möglichst umfangreich sein, da so die Funktionalität meiner Implememtierung am besten zugänglich ist.
- möglichst direkten Zugriff auf Strukturen und Werte ermöglichen, da zB. der Zugriff über Count und einen Listenindex immer der performanteste Weg ist
Antwort B: Schnittstellen sollten
- Nicht größer als nötig und möglichst gut auf die zu erledigende Aufgabe abgestimmt sein, weil sie so leichter implementiert und wiederverwendet werden können
- Ein gut designtes Set aus Methoden bereitstellen, so daß zB. die genaue Implementierung eines Iterators für den Anwender völlig transparent ist
Auflösung
Natürlich ist Antwort B richtig. Die Option A führt früher oder später unweigerlich zu fetten Monsterklassen und aufgrund der viel zu engen Kopplung dazu, daß das äußerste linke Ende der Software über die Interna der Klasse ganz rechts außen Bescheid wissen muß.
Das aber ist tödlich, denn damit erzeugt man statt einer schönen, modularen Lösung aus lose gekoppelten und leicht wart- und wiederverwendbaren Modulen ein unauflösbares Gestrüpp an Abhängigkeiten, das jeden Wartungsprogrammierer zur Verzweiflung treibt.