Hi Leute,
ich sitze seit einer ganzen Weile an einem kleinen Framework was mir und hoffentlich vielen Anderen die Arbeit erleichtern soll.
Allerdings habe ich immer wieder Punkte gefunden, die meine Pläne damit und mein Verständnis von einer sauberen und langlebigen Architektur stören.
Zuerst einmal, was ich überhaupt will:
Wer das MVVM-Pattern kennt, wird vielleicht auch das Konzept kennen, eine ViewModelBase-Klasse zu schreiben, die dann anhand des Property-Namen entsprechende Methoden bereit stellt, die dann das PropertyChanged-Event werfen.
So ähnlich soll meine ObservableObject-Klasse auch aussehen, allerdings soll sie flexibler und (das ist wichtig) stark erweiterbar in den Funktionen sein. SIe soll INotifyPropertyChanging und INotifyPropertyChanged implementieren.
Auf einfachster Ebene sieht es so aus, dass es folgende Methoden gibt:
C#-Quelltext
1: 2: 3: 4:
| object GetValue(string propertyName); bool SetValue(string propertyName, object value); void RaisePropertyChanging(string propertyName); void RaisePropertyChanged(string propertyName); |
SetValue gibt zurück, ob der Wert tatsächlich geändert und das PropertyChanged geworfen wurde.
Der Rest ist denke ich klar.
Soweit so einfach, ich würde der Klasse dafür einfach ein Dictionary geben wo dann Name und Wert drin stehen.
Nun soll die Klasse aber auch noch die Möglichkeit bieten, verschiedene Verhaltensweisen für jede Property zu implementieren.
Ein Beispiel könnte eine Property sein, die von einer anderen Property abhängig ist. Wenn sich diese andere Property ändert, soll für die abhängige Property der neue Wert berechnet und die Notifications ebenfalls geworfen werden. Oder es werden nur Daten durch gereicht.
Das wäre ein Feature, dass ich später bei einem weiteren kleinen Framework zwingend benötige.
Außerdem muss das ganze System so flexibel sein, dass ich z.B. Validierung einfach einbauen kann.
Somit ist schon mal klar, dass ein einfaches Dictionary nicht mehr reicht, stattdessen habe ich ein Dictionary, das den Namen zu einem bestimmten PropertyEntry-Object mappt. Da steht dann solches Zeug drin, wie das aktuelle Objekt und zusätzliche Informationen, die z.B. für die Abhängigkeiten zuständig sind.
Mein letzter Stand sah wie folgt aus:
Für jede Property muss eine Implementierung eines IPropertyEntry-Interfaces implementiert werden, das folgende Member hat:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7:
| event EventHandler ValueChanging; event EventHandler ValueChanged; Type PropertyType { get; } string PropertyName { get; } bool CanWrite { get; } object GetValue(); void SetValue(object value); |
So konnte dann jede Implementierung selber entscheiden, was passiert, wenn ein Wert abgefragt oder geschrieben wird, sie kann auch über die zwei Events über Änderungen informieren.
Da das ziemlich viele Anforderungen an eine simple Property sind, habe ich dann entsprechende Klassen geschrieben, die ein paar häufig genutzte Funktionen ab decken. Eine Klasse, die eine Property ohne besondere Aktivitäten darstellt (beinhaltet nur ein Feld), eine Klasse, die Abhängigkeiten zu anderen Properties regelt, etc.
Dennoch bleiben hier große Anforderungen an eine simple Property, die ich so weit wie möglich reduzieren würde.
Der Ideal-Zustand ist denke ich, dass ich auch nicht mehr von ObservableObject ableiten muss um Funktionen zu ergänzen, sondern nur eine Art Extension registrieren muss.
So könnten dann auch verschiedene Extensions unterschiedlich kombiniert werden.
Die Frage ist nur: Wie baue ich das am besten auf?
Spontan fallen mir zwei Möglichkeiten ein:
Ein Interface, das alle Funktionen beinhaltet. Eine Implementierung ist eine Extension.
Das wäre einfach, hat aber das Problem, dass eine Erweiterung an diesem Interface sofort alle Extensions unbrauchbar macht.
Die zweite Idee ist ein Interface für verschiedene Funktionen, die dann einzeln registriert werden müssen.
Das ist bedeutend flexibler, aber auch aufwendiger und in der Nutzung komplizierter.
Hier könnte man es aber vereinfachen, wenn die Registrierung ungefähr so aussieht:
C#-Quelltext
1:
| void RegisterExtensions(Action<ExtensionSettings> setExtensions); |
und Nutzung so:
C#-Quelltext
1: 2: 3: 4: 5:
| RegisterExtensions((settings => { settings.ExtensionA = new MyCoolExtensionA(); settings.ExtensionB = new MyCoolExtensionB(); }); |
Ich hoffe, ihr versteht, was ich vor habe und was mein Problem ist.
Habt ihr da vielleicht Ideen/Tipps/Vorschläge/Patterns, die mir helfen können?
Oder einen konkreten Vorschlag, wie ich das aufbauen könnte?