Generisk programmering i C# på .net del 1

Indledning

Følgende er en serie på tre artikler om generisk programmering i tre artikler:

1.       Om interfaces og abstrakte base klasser

2.       Om nedarvning, casts og generics

3.       Om Polymorfi

 

Det er vigtigt at forstå at denne serie kun overfladisk gennemgår emnerne.

Serien er altså kun ment som en slags appetitvækker, der skal sætte dig igang med generisk programmering i C# på .net.

 

Det er også vigtigt at du selv prøver at lave dine egne kode eksempler når du har læst om et emne, på den måde får du den ’hands on’ oplevelse, der skal til for at forstå emnerne...

 

Meget af det som gennemgåes i denne serie gælder også i andre objektorienterede sprog; for eks. Java og C++, dog kan der være visse forskelle, som jeg ikke har taget med i betragtning.

Formålet med generisk programmering

Formålet med generisk programmering er at undgå gentagelser i koden.

Kode, der bruges flere steder skal ikke skrives igen og igen, men ved hjælp af generisk programering skal det placeres ét sted.

Og kan dermed genbruges.

 

Desuden foregår vedligeholdelse ét sted, hvilket er en stor fordel.

Har du en funktionalitet spredt udover hele din kodebase, og finder en fejl i den funktionalitet, skal du ud at rette alle steder, hvor den findes ( finder du mon dem alle).

 

 Har du én gang siddet og vedligeholdt andres spaghetti kode, véd du hvorfor det er godt at lave generisk programmering.

 

Generisk kode er nemmere at overskue, end kode, der ikke er ordentlig opdelt og hvor funktionaliteten ligger spredt med let hånd ud over det hele.

 

Det er altid værd at overveje, når du koder at andre kommer og skal se på det du laver og at de ikke har den samme baggrund som du, for at forstå din kode.

Iøvrigt siger erfaringen at kode du selv har lavet for et par måneder siden ligeså godt kunen være lavet af en anden...

Design patterns

Det er vigtigt at bruge tid på at sætte sig ind i det man kalder design patterns.

Design patterns er mønstre som man har opdaget går igen og igen i god generisk kode.

 

 Hver gang du står overfor et problem,  er det værd at overveje om nogen måske har stået i dette problem eller et ligende før dig; Og fundet en god generel  løsning på problemet.

 

Design patterns blev første gang publiceret i bogform af fire herrer man kalder Gang Of For (GoF),  her er tolv grundlæggende design patterns, som kan bruges i mange problemstillinger når du koder. De tolv design patterns kan du finde her http://www.dofactory.com/Patterns/Patterns.aspx .

 

Endvidere har en fyr ved navn Martin Fowler arbejdet en del videre med design patterns, og fundet rigtig mange flere, det er værd at tage et kig på hans hjemmeside http://www.martinfowler.com/

 

 

Lær de forskellige teknikker i generisk programmering, og find den der passer i den situation du står i. Så undgår du alt for mange kommentarer som denne, Fra dem der skal vedligeholde din kode i fremtiden:

 

//#!£!$!%!$£$$€$€6 <- Dette skulle forestille tegneserie bandeord, men det lader til at Anders And er bedre til det end jeg ;-)

Interfaces og Abstract base klasse

Indledning

I denne artikel skal vi se lidt nærmere på interfaces og akstrakte base klasser.

Det er to funktionaliteter i C# programmerings sproget, der minder en hel del om hinanden.

Interfaces

Først vil jeg altså fortælle lidt om interfaces, hvad de er og hvorfor du skal bruge dem. 

Så hvad er et interface?

Jo, det kan betragtes som en slags kontrakt mellem to klasser:

Altså et sæt af metoder og properties, som en klasse implementerer og som en anden klasse har brug for.

 

Et eksempel på et interface som er meget almindelig brugt og som du sikkert har brugt uden at tænke over det er IEnumerable<T> (her bruges generics som jeg vil komme ind på i en senere artikel i denne serie)

Ienumerable bruges eks. Som regel i foreach loops.

 

IEenumerable har defineret én metode nemlig GetEnumerator() (der iøvrigt returnerer endnu et interface).

 

Det betyder at en klasse der implementerer IEnumerable  lover at implementerer metoden GetEnumerator - hverken mere eller mindre.

 

Endvidere betyder det at en klasse, der ønsker at gøre brug af IEnumerable véd præcis hvad den har at gøre med, nemlig den ene metode.

Ønsker man at enumerere sig igennem en klasses indhold kan det altså være praktisk hvis den implementerer netop dette interface.

 

Samtidig er det vigtigt at forstå at der ikke er lovet mere end at der findes en sådan funktionalitet, som specificeret i interfacet, hverken mere eller mindre.

Der kan (og vil som oftest) være mange flere metoder og properties end de som er defineret i interfacet, men de er uinteressante for den som skal bruge interfacet.

 

Så et interface er altså en slags kontrakt mellem to klasser, men yderligere kan et interfaces også bruges til at 'fange' ligheder mellem to klasser uden at tvinge dem til at nedarve fra den samme base klasse.

Eksempelvis ville det være stærkt uhensigtsmæssigt at alle klasser der skal loopes over via et foreach loop skulle nedarve fra den samme klasse, da man kun kan nedarve fra en klasse i .Net.

Dette løses nemt og bekvemt med interfaces, hvor man blot fanger det klasser som man skal loop’e over har til fælles - nemlig at de har en enumerator.

 

Afkobling mellem klasser.

En vigtig faktor når man designer applikationer, er hvor tæt to klasser, der arbejder sammen, skal knyttes til hinanden.

Et eksempel:

Jeg har på et tidspukt lavet en simpel asp.net form, hvor man via noget XML kan definerer forskellige input felter.

Ifølge kravspec.'en skulle data, som blev indtastet i formen, sendes til en email adresse.

Så det ville være naturligt at lave en klasse der, udfra noget XML, skaber en asp.net form.

Og herefter lave en anden klasse, der kunne sende en email, udfra nogle felter.

Så kunne den første klasse blot have en instans af email klassen og sende mails via denne klasse.

Dette ville virke og fungerer i praksis.

 

MEN MEN MEN, hvad nu hvis jeg pludselig ønsker at gemme data i en database i stedet for at sende en email.

Så skulle jeg til at lave om i den første klasse ( den der skaber en form), selvom der ikke var noget galt med den, udover at den var koblet for tæt til email klassen.

 

Så det jeg istedet gjorde var at definerer det jeg kaldte et Ibackend interface, dette interface har følgende metode:

 bool Submit(FieldList fieldList, ref string result);

 

Alt jeg kræver af en backend er altså at man skal kunne submitte en felt liste til den og at den skal give mig svar tilbage i en streng om hvordan det gik. Dermed har jeg ikke koblet mig til en klasse, men et interface, som mange klasser kan implementere.

 

Nu skal jeg bare skyde en factory klasse (dette er et design pattern, kender du ikke design patterns, er det det næste du skal til at igang med ;-), ind som kan finde ud af skabe en Ibackend for mig.

Jeg laver en klasse som udfra konfiguration kan skabe en instans af en emailbackend klasse.

 

Se, jeg er rigtig glad for at jeg gjorde det, for her den anden dag kom der en projektleder op til mig og spurgte: "den der form to email ting du lavede den for noget tid siden, kan den egentlig smide noget i en database". Hertil kunne jeg svare at den kan den ikke, men jeg skal bare have lave en backend, der implementerer Ibackend og så er den hjemme....

 

Dette at en anden klasse skydes ind imellem og skaber en instans udfra noget konfiguration, kaldes iøvrigt dependency injection og det hat Martin Fowler skrevet noget fornuftigt om...

 

Kan bruges til at gemme implementation.

Interfaces kan yderligere bruges til at skjule implementation for brugerer af et library man laver.

Ved at bruge interfaces, ser brugeren kun de metoder, man ønsker de skal se.

 

Abstrakte base klasser

Som du forhåbentlig allerede har fundet ud af så kan man ikke lave noget som helst funktionalitet i interfaces, bare definerer en kontrakt.

 

Til tider kan man have brug for at implementerer noget funktionalitet i en base klasse uden at den klasse skal kunne bruges til andet end at være en base klasse.

 

Dertil er abstrakte base klasser designet.

 

En abstrakt base klasse, er en klasse som man ikke kan lave en instans af.

Men man kan have implementationer af metoder, og have metoder som ikke er implementeret.

For eksempel har jeg en base klasse for en data access klasse, hvor jeg definerer to abstrakte metoder:

Map() og GetDataRowByID().

 

Map() mapper en DataRow til en business entity.

GetDataRowByID() henter en DataRow fra databasen.

 

Se i en abstrakt base klasse kan jeg ikke vide hvordan man mapper til en business entity og heller ikke hvordan man henter en DataRow fra databasen.

Men jeg kan sige at man skal kunne gøre det.

 

Det har jeg gjort ved at markerer metoderne som abstrakte, dermed skal alle klasser som arver fra min abstrakte base klasse implementere denne metode (man får en compilerfejl, hvis man ikke implementerer en abstrakt metode).

 

Men jeg kan vide at når jeg har de to metoder så kan jeg godt fremskaffe en businessentity udfra et id, nemlig sådan her:

public virtual businessentity GetById(string id)

        {

            DataRow data = GetDataRowById(id);

 

            if (data != null)

            {

                return Map(data);

            }

 

            return null;

        }

 

På den måde behøver jeg ikke i alle mine nedarvede klasser at lave denne simple mappning.

Det sparer arbejde hver gang jeg skal implementerer en ny klasse. MEN det er derudover også en stor fordel at jeg kun har denne implementation ét sted. Nu kan jeg nemlig gå ind ét sted og rette og så slår det igennem i alle nedarvede klasser. Det letter altså vedligeholdelse og konsistens betragteligt.