God kode: Oppførsel - Et alternativ til arv
En veldig vanlig feil når man lager funksjonalitet som mange objekter skal dele, gjerne med kodebiblioteker, er å benytte seg for mye av arv.
Så jeg tenkte å skrive enkelt eksempel for å vise en annen måte man kan imeplementere gjenbrukbar oppførsel uten å bruke arv.
Scenariet er en grid, som du ønsker at skal ha uniformt utseende og oppførsel. Det kan være fordi du har mange prosjekter som bruker samme komponenter og du vil ha lik oppførsel i applikasjonenene fra alle prosjekter.
For å starte med en grid som alle kjenner og tilbyr (uhorvelig) mye ferdig funksjonalitet så arver din grid fra UltraGrid.
public class Grid : UltraGrid
{
Grid()
{ Font=StandardFont;}
}
Du setter noen standarder i denne slik at alle grider som brukes av utviklerne får de samme egenskapene som standard oppførsel.
Med tid så vokser denne klassen. Den kan bli flere tusen linjer kode, og inneholder alle kjente tillatte måter å bruke den på.
I ettertid er det lett å se problemet. Vi kan kort oppsummere noen:
- Vedlikeholdbarhet. Hvordan vedlikeholde en klasse med hundrevis av metoder i tusenvis av linjer kode, effektivt ?
- Duplisering ? Hvordan kan man være sikker på at det man lager ikke er lagd før ?
- Testbarhet ? Slike klasser er umulige å holde testbare.
Spørsmålet da er hvordan kommer vi oss vekk fra det.
Et godt alternativ da er å lage oppførsels-klasser som kan brukes med denne Grid-klassen.
Du vil at alle brukere av griden skal ha et kontekst-menyinnslag for eksport til excel. Istedenfor å legge til en metode i Grid-klassen kan du lage en oppførsels-klasse som håndterer excel-eksport.
public class ExcelEksportOppførsel
{
public ExcelEksportOppførsel(Grid grid)
{
...
}
public void LeggTilKontekstmenyinnslagForEksport()
{
....
}
}
Denne klassen tar en grid inn som parameter til konstruktør og håndterer nødvendig funksjonalitet for å eksportere til excel.
Men den kan fremdeles forbedres noe. For å gjøre den enda mer testbar kan vi endre konstruktør:
public ExcelEksportOppførsel(Grid grid) {}
Dermed kan vi ta inn et mockbart IGrid-objekt istendefor en ikke-mockbar grid.
Den siste bedringen vi skal gjøre er å lage en enklere måte å bruke objektet på:
public class ExcelEksportOppførsel
{
public ExcelEksportOppførsel(IGrid grid)
{
...
}
public void LeggTilKontekstmenyinnslagForEksport()
{
....
}
public static void LeggtilKontekstmenyinnslagPåGrid(IGrid grid)
{
ExcelEksportOppførseloppførsel=new ExcelEksportOppførsel(grid);
oppførsel.LeggTilKontekstmenyinnslagForEksport();
}
}
Denne klassen er testbar, har en klar mening, og er vedlikeholdbar. Et godt utgangspunkt for å lage flere slike oppførsler.
Din grid-klasse kan også ha en egenskap som sier noe om at den skal støtte exceleksport, og så bruke denne oppførsels-klassen. Isåfall vil det for en bruker av grid-klassen din ikke endre bruksmåte å legge funksjonaliteten ut i egne oppførsels-klasser.
Sluttkommentar
Det er ikke nødvendigvis akkurat DENNE oppførselen som er den beste måten å organisere koden på.
Et annet forslag er å lage 2 oppførsler. Der den ene håndterer det å legge til kontekstmenyinnslag, og den andre selve exceleskporten. Så kan oppførslen for å legge til menyinnslag knytte en exceloppførslen sammen med menyinnslaget.
Avhengig av hvilke behov du har kan flere alternative måter å organisere det på være riktig.