Forretningsobjekter, databinding og WPF
Efter at have brugt CSLA i omkring et år, er min absolut favorit-detalje hans brug af GetProperty<T>(SomeProperty) ;og SetProperty<T>(SomeProperty,value); sammen med en static SomeProperty som registreres. Han anvender det bl.a. til n’level undo og for at følge state i objekternes levetid (IsNew, IsDirty, IsDeleted osv.). Ideen er at alle properties registreres i en matrice, og alle ændringer noteres i matricen efterhånden som de sker, så det altid er muligt at returnere til en given state.
Det er ikke så meget funktionaliteten, jeg er vild med, som det er syntaxen. Istedet for at have flere linjer kode til at håndtere INotifyPropertyChanged, state osv. i set’eren for properties, sker og vedligeholdes alt dette ‘plumbing’ kode centralt i nogle ganske generelle metoder. Desuden er den typestærk (fordi der anvendes generics). Hvis man vil omgå hele det manageri, anvendes blot LoadProperty<T>(SomeProperty, value);. Overordentlig elegant, hvis du spørger mig.
Kombineret med at det ikke var nødvendigt at spilde tid med backing fields for properties, var reduktionen af kodelinjer enorm for ’standard’ properties. Derfor var det med en del vemod at jeg lavede en prototype uden CSLA. Det var indtil jeg opdagede DependencyObject og DependencyProperty. Jeg havde godt læst, at man kunne anvende DependencyProperty udenfor grafiske elementer (og uden for WPF skulle det vise sig), men var ikke overbevist om at det en god ide.
Lad mig sige det med det samme. Den er ikke ligeså elegant som CSLA’s løsning - men det er deroppe af - til gengæld er den noget mere letvægts (hvilket tæller meget i min bog) og enkel at implementere, hvis man gør sig selv den tjeneste at implementere den ‘korrekt’ (det vil i det her tilfælde sige at bruge den til noget, den ikke er lavet til.) Første tilbageskridt er at den ikke er generisk - og dermed ikke typestærk. Der skal kastes variable - og der kan derfor opstå problemer, hvis man ikke er vågen. De er dog forholdsvis nemme at rette (og finde), hvis man anvender regelmæssig test.
Den fremgangsmåde, som jeg vil anbefale er følgende:
- Nedarv fra DependencyObject i en abstrakt klasse, som danner grundlag for dine forretningsobjekter - hvis du anvender DDD, skal du have et niveau under igen - en udspecificering af rod-objekter og ikke-rod-objekter i dine aggregater.
- Override SetValue, så du har et sted at styre state.
- For hver property laver du en protected static readonly DependencyProperty, som du registrerer som beskrevet her.
- Derudover laver du dine normale properties med get’er og set’er som vanligt.
En lille note til 3′eren - hvis du laver dem public, kortslutter du den almindelige logik for at styre hvem der kan ændre hvad. Selvom du har en private set’er, kan man uden videre lave en someObject.SetValue(SomeObject.SomeProperty, someValue);, hvilket er mildt sagt uheldigt. Man kan vælge at tillade det for properties, som har public get’ere og set’ere, men jeg vil på det kraftigste fraråde det, med mindre der er en specifik grund til det. Det er simpelthen for risikabelt især med flere udviklere. Hvis du laver dem private vil nedarvede klasser ikke kunne se dem og de bliver svære at teste på (fx stubs).
Nå, men til det vigtigste - hvad får man ud af at anvende DependencyProperties på denne måde? Fuld databinding uden at røre en finger! Det virker bare. Elegant kode (efter min mening) som er let at vedligeholde. Det eneste der egentlig mangler er en typestærk variant evt. via generics, men mon ikke der er nogen derude, der laver det på et tidspunkt… hint, hint…