Autor Beitrag
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Mo 06.06.16 16:54 
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:
ausblenden 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:
ausblenden 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:

ausblenden C#-Quelltext
1:
void RegisterExtensions(Action<ExtensionSettings> setExtensions);					


und Nutzung so:

ausblenden 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?
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 06.06.16 20:22 
Ich weiß nicht wohin du damit willst aber gefühlt landest du in der Overengineering Ecke.
Bevor man die ganze Komplexität ins Model packt würde ich eher darüber nachdenken die Komplexität in einen Codegenerator auszulagern.

Bedenke wenn ein potentieller Nutzer deines Framework ein Problem mit dem Nutzen hat kann er das sinnvoll ohne dem ganzen Wissen das du hast tun oder wird er vermutlich frühzeitig an irgendwelchen Mappings, Injections, Reflectionzaubereien oder was auch immer scheitern und es einfach nicht durchschauen.
Eine registrierbare Extension heißt für mich auch das Verhalten des Models kann sich potentiell zur Laufzeit ändern (halt mit der Registrierung/Deregistrierung). Braucht man sowas wirklich? Wenn ist das eher die Ausnahmen.

Ich würde dir raten die ganzen schlauen Ideen die du hast in ein generiertes Model zu stecken. Das kann man mit Hilfe von partiellen Klassen/Methoden möglichst so gestalten das es programmatisch erweiterbar ist aber eben später regenerierbar bleibt.
Palladin007 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Di 07.06.16 16:39 
Für eine reine ViewModelBase - ja, unterschreibe ich sofort :D


Ich möchte aber auch darauf aufbauend ein BusinessObjects-Framework umsetzen. Ich habe dazu nur CSLA gesehen, wir nutzen das in der Firma. Ich persönlich bin Freund davon und es macht deutlich mehr, als ich für mich brauchen würde.
Ein solches BusinessObject wrapped im Prinzip ja über z.B. ein Repository, LazyLoading wird ein Thema, eine undo/redo-Funktion, Caching, ... Sowas würde ich alles zentral auf der Business-Ebene plazieren, wo ich dann auch weitere Funktionen wie z.B. Import/Export unter bringen kann, ohne das Data-Layer oder das Presentation-Layer anfassen zu müssen.

Zur Laufzeit ändern sollte sich eigentlich nichts - ich sehe aber auch nicht das Problem, warum das nicht gehen sollte :D
Anfangs habe ich diese Information, was für Properties es mit welchen Infos gibt, statisch gehalten, bin dann aber später zu dem Schluss gekommen, dass ich davon einfach keinen noch so kleinen Vorteil habe.


Die Idee mit dem Codegenerator finde ich aber gut, über Attribute ließe sich das dann einfach an der entsprechenden Property regeln.
Eventuell fällt mir da auch was ein, wie man das als Anwender erweitern kann - Unity Interception kann ja sowas in der Art, das könnte ich nutzen

Ich weiß aber noch nicht, wie ich das bei dem BusinessObject-Framework nützen kann ohne dass ich mich dabei einschränke ...



PS:
Wobei ich dann aber auch das Problem hätte, dass ich ein ähnliches System, wie ich das schon vor hatte, im Hintergrund haben müsste, damit ich die Daten, die über die Attribute transportiert werden, in eine nutzbare Objekt-Struktur gebracht werden können.
Andernfalls ist man immer davon abhängig, dass im Codegenerator eine solche Funktion umgesetzt ist, die ich brauche - Eigene ergänzen oder eine Bestehende modifizieren kann ich nicht.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Di 07.06.16 17:25 
Zitat:
Ich persönlich bin Freund davon und es macht deutlich mehr, als ich für mich brauchen würde.


Ich habe bereits in mehreren Projekten versucht möglichst generische Modelsysteme zu entwickeln. Letztlich war es immer so das ich im nächsten Projekt nicht die Probleme hatte die ich zuvor in dem Vorprojekt mit dem generischen Modelsystem lösen wollte. Ich hätte das natürlich passend erweitern können. Aber dann hätten da eben auch viele Teile drin gesteckt die ich nun nicht brauche. Letztlich war es immer einfacher ein angepasstes Model, auf das aktuelle Problemfeld zugeschnitten, zu schreiben. Oder eben was Fertiges vom Markt zu nehmen.

Zitat:
Die Idee mit dem Codegenerator finde ich aber gut, über Attribute ließe sich das dann einfach an der entsprechenden Property regeln.


Attributierte Interfaces aus denen man konkrete Modelklassen generiert wäre ein denkbarer Weg. Da sehe ich keine großen Folgeprobleme.
Das Verfahren Interfaces + Codegenerator -> konkrete Modelklassen hat eigentlich immer gut funktioniert ohne übermäßige Komplexität zu erzeugen die nicht von allen Projektteilnehmern verstanden wird.
Palladin007 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1282
Erhaltene Danke: 182

Windows 11 x64 Pro
C# (Visual Studio Preview)
BeitragVerfasst: Di 07.06.16 18:11 
Ja, die Idee mit den Attributen gefällt mir gut, das würde das Hindernis mit der Basis-Klasse beseitigen und ich könnte auch ohne Umwege eine andere Basisklasse erlauben.

Aber wie funktioniert ein solcher Code-Generator?
Ich kenne T4 und könnte mir vorstellen, das auf eine partiale Klasse los zu lassen. Alles was partial ist, wird dann nach Attributen analysiert und in der generierten Datei implementiert. Bei Properties geht das meines Wissens aber nicht. Eine Ableitung zu generieren finde ich dagegen eher doof.

Die andere Variante wäre, dass ich das alles mit Unity Interception kombinieren, dann wäre man aber gezwungen, den IoC-Container mit zu nutzen. Ob das jetzt gut oder schlecht ist, darüber lässt sich streiten. Eigene eigene Implementierung würde gerne vermeiden - ich hab mir das ein paar mal vor genommen und fest gestellt: Nur, wenn es definitiv keinen anderen Weg gibt :D

Die Funktionalität, die in die Property eingefügt wird, wäre nicht kompliziert. Ich würde dann als Singleton eine Art Manager bereit halten, der in jeder Property aufgerufen wird. Der sammelt und cached dann die nötigen Informationen und ruft das auf, was gewollt ist. So bleibt dann auch der Generator immer gleich (zumindest solange keine Bugs auftreten), das Verhalten oder die Features kann ich trotzdem überarbeiten, ändern oder austauschen.



PS:
Also ich muss sagen, je mehr ich über deine Idee nachdenke, desto besser gefällt sie mir :D

PPS:
Sehe ich das richtig, dass das auch etwas von Aspect Oriented Programming hat?
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Di 07.06.16 20:23 
Zitat:
Aber wie funktioniert ein solcher Code-Generator?


Im einfachsten Fall Text rein anderer Text raus ;) Was dazwischen passiert ist natürlich die Kunst. Es gibt da aber nicht denn einen Weg.

Eine Idee wäre z.B. (sowas in der Art habe ich noch nicht gemacht es ist also tatsächlich nur eine Idee) besagtes attributiertes Interface. Für Visual Studio würde ich dann ein Custom Tool schreiben das am File mit dem Interface hängt und daraus dann die nötigen konkreten Klassen generiert.
Falls du noch kein Custom Tool in Visual Studio gesehen (oder es nicht bemerkt hast) erzeuge mal in einem Projekt eine neue Resourcen Datei (resx). In den File Properties wirst du sehen das da
ein ResXFileCodeGenerator Custom Tool dran hängt das jedesmal wenn sich die resx ändert automatisch die dazugehörende meineLiebeResource.Designer.cs erzeugt. Genauso so ein Custom Tool kann man auch selbst schreiben und in Visual Studio registrieren.
Einen Codegenerator kann man aber auch genauso einfach als irgendein Kommandozeilentool schreiben das Source Code Dateien erzeugt.

Für das parsen des Interfaces und der Attribute habe ich dann keine konkrete Idee welchen Weg man da am besten einschlägt. File selbst parsen, CodeDomParser, Roslyn, eigener Parsergenerator wie z.B. ANTLR ... der Möglichkeiten sind da viele.