Tutorial - OOP C# Arv

1. Introduktion

I denna tutorial gå jag igenom hur vi kan jobba med arv inom den objektorienterade programmeringen. Detta kommer vara en praktisk övning, själva teorin bakom arvet gås igenom i moment03.

Denna tutorial kommer bygga vidare på en tidigare tutorial där vi bygger upp klassen Car. Har du inte gjort denna tutorial så hittar du den här.

2. Skapa projekt

Jag skapar ett nytt projekt som jag döper till VehicleDemo och sedan överför jag de gamla filerna Car.cs (från vår första tutorial) till detta projekt. Det går att göra på flera sätt, antingen koiperar du filerna till projektmappen eller så går det att välja add->Add Files from Folder om du högerklickar på projektet. Se till att du kopierar filerna och inte flyttar eller länkar dem. Flyttar du dem så finns de inte kvar i ditt gamla projekt. Länkar du dem så kommer du att jobba mot samma filer som ligger i ett annat projekt och då kommer även detta projekt att påverkas.

En sak till bhöver vi justera innan vi kan jobba vidare. tittar du på en av de första raderna i våra cs-filer så ser du att det står namespace CarDemo vilket kan ge oss problem. Ett namespace (namnrymd på svenska) gör at vi kan dela upp stora projekt i olika delar så att de inte påverkar varandra. Hamnar koden i olika namespace så är det inte säkert att den fungerar så som det är tänkt. I Xamarin så namnges namespace vanligtvis utifrån projektets namn, nu heter vårt projekt VehicleDemo och då bör även vårt namespace heta samma. Ändra detta direkt för att inte få problem senare.

2.1 Uppgiften

Jag har ännu inte presenterat uppgiften eller vad det är vi skall göra. Tanken är att vi skall bygga ut vår applikation där vi kan lagra bilar i en lista som också innehåller lastbilar. Det skall bli grunden till ett system som en bilhandlare skall kunna använda. Innan vi går hela vägen och skapar listan så behöver vi lära oss hur vi skall göra med våra klasser och hur de skall se ut.

3. Skapa våra klasser

UML Vi har redan klassen Car.cs som vi utgår ifrån. Vi fokuserar på klassens medlemsvariabler för denna klass.

Vi skall förbereda systemet att också kunna lagra information om lastbilar och behöver då bygga en klass som vi förslagsvis döper till Lorry. Om vi utgår ifrån klassen Car så ser vi att all info som redan finns där är relevant även för vår lastbil, vi låter alltså dessa finnas kvar. Skulle detta vara ett skarp system som skulle säljas till kund så skulle det ju finnas mängder av ytterligare medlemmar men vi vill hålla det relativt enkelt i denna övning.

UML Vi behöver ändå lägga till något specifikt för en lastbil och det borde vara hur mycket den får lasta. Vi skapar en UML för vår lastbil och lägger till en medlemsvariabel för lasten (load).

Så där, nu har vi en unik medlemsvariabel för Lorry men vi ser ju hur lika dessa två klasser är. Om det nu är så att jag väljer att ändra logiken för year, så är det rimligt att jag vill ändra detta i bägge klasserna. Det är här som vi drar nytta av principen kring arv och istället för att bägge klasserna har flera saker som är identiska så är det bättre för oss att skapa en extra klass som både Car och Lorry kan få ärva ifrån. Vi skapar klassen Vehicle och flyttar allt som är likadant i bägge klasserna hit. Vi ritar också upp alla tre klasserna i ett UML-diagram så att vi ser hur det ser ut, vi jobbar just nu bara med medlemsvariablerna, alla properties och metoder tar vi hand om senare.

UML UML UML

Det vi ser här nu är ju att allt som tidigare låg i klassen Car finns i klassen Vehicle och att Lorry har en medlemsvariabel som är unik för den klassen.

UMLNu kan vi börja bygga arvet kring dessa tre klasser. Vehicle kommer vara vår föräldraklass/basklass och låta Car och Lorry ärva egenskaper och metoder från den. Vi börjar att rita upp hur det skall se ut i ett UML-schema.
Metoderna är fortfarande inte utritade i UML-diagrammet men behöver finnas med när vi jobbar med klasserna sedan.

4. Skapa arvet mellan Vehicle och Car

Dags att börja koda. Det finns två filer som du bör ha i ditt projekt, Car.cs och Program.cs.

4.1 Skapa Vehicle.cs

Eftersom Vehicle tog över alla medlemmar från Car så byter vi helt enkelt namn på Car.cs till Vehicle.cs. när det är gjort glöm inte att klassens namn och namnet på konstruktorerna behöver ändras så att de speglar den nya klassens namn. De ändringar du behöver göra finns i koden nedan på rad 5, 19 och 24.

UML

Nu skulle vi kunna skapa några objekt av typen Vehicle ifrån klassen Program.cs, vi gör detta för att testa att applikationen fortfarande fungerar.

Kod Kod

Och här ser vi att den fungerar som det skall, det enda vi har ändrat på är att skapa ett objekt av typen Vehicle istället för objektet Car. Vi skall senare se till att det inte skall gå att skapa ett sådant objekt, men först skall vi ta hand om vår nya klass Car.

4.2 Nya klassen Car

Högerklicka på projektet och välj Add -> New file... , välj sedan General i vänsterkolumnen och sedan Empty Class. Döp denna klassen till Car så skapas det en ny fil med namnet Car.cs. Filen innehåller klassen Car med konstruktorn Car(), i övrigt är klassen tom. Se bilden.

Kod

Vi skall nu tala om att klassen Car skall ärva från Vehicle och det gör vi genom att efter klassens namn skriva ett kolon och från vilken klass den skall ärva.

Kod

Nu har jag talat om att ett objekt Car har ärvt alla egenskaper från Vehicle, det innebär att allt som Vehicle kan göra borde ett objekt av typen Car kunna göra. Vi testar att skapa ett objekt av typen Car.

Kod

Riktigt så enkelt var det naturligtvis inte, det finns alltså ingen konstruktor i klassen Car som tar fem parametrar. Om vi tittar på klassen så ser vi ju att den enda konstruktor som finns där är en konstruktor som inte tar några parametrar alls, vi behöver skapa en sådan.

4.3 Konstruktorer i arv

Det enklaste är ju att skapa en exakt kopia av konstruktorn som finns i klassen Vehicle. Vi testar att göra så.

Kod Programmet körs

Det fungerar fint. Men det finns ett problem med detta. Anledningen till att vi vill jobba med arv är ju för att skriva en mer effektiv kod och undvika att kopiera och ändra på flera olika ställen. Det vi vill göra i klassen Car blir ju då att anropa den konstruktorn som redan finns i klassen Vehicle, framförallt för att dessa två konstruktorer på sikt inte skall innehålla olika information. Vi kopplar konstruktorn från Car så att den anropar konstruktorn i Vehicle.

Kod

Jag passade samtidigt på att ta bort den tomma konstruktorn som skapades då filen skapades, allt som inte skall finnas eller användas är bäst att det tas bort, det kan handla om både prestanda och säkerhet men framförallt om att hålla koden ren.
Anropet till basklassens konstruktor sker i min kod på rad 10 men det är mest skrivet så för att det skall få plats att ta en skärmbild, det skulle lika gärna kunna vara skrivet på rad 9 och hade det varit en konstruktor med färre parametrar hade det varit att föredra att skriva det på en rad.

Testa att köra applikationen så att det fungerar även för dig.

4.4 Medlemsmetoder i arv

Det fungerade ju inte med konstruktorer i vårt arv, hur fungerar det då med medlemsmetoder? Vi testar att skapa ett objekt av typen Car och ändrar sedan i en medlemsvariabel innan vi skriver ut objektet. Tänk nu på att ingen medlemsfunktion finns i klassen Car utan alla medlemsmetoder kommer via arv från Vehicle. Koden och testet ser ut så här;

Kod Programkörning

Bra. Då vet vi att vi iaf kan återanvända medlemsmetoder i arvet, vi skall senare kika på hur vi gör med medlemsmetoder som förändras i en subklass.

4.5 Förbjuda objekt av typen Vehicle

Eftersom all struktur från gamla klassen Car numera ligger i klassen Vehicle men ärvs till Car så är Vehicle en klass som vi endast vill använda till arvet. Det skall alltså inte gå att skapa ett objekt av typen Vehicle. Detta görs genom att använda nyckelordet abstract.

Kod

Testar vi nu att skapa ett objekt av typen Vehicle kommer vi få ett error, vilket är bra eftersom vi inte vill kunna skapa detta objekt.

Kod

Felet vi får är att vi försöker skapa ett objekt av en abstrakt klass eller ett interface. Ett interface är en typ som bara har metoddeklarationer men inga medlemsvariabler. Interface används om vi skulle behöva ärva från två klasser, t.ex. en Husbil som vill ärva av klasserna Hus och Bil vilket inte går i C#, då får vi använda oss av interface istället.

Vi kommer senare i denna tutorial kika på hur vi skapar abstrakta metoder.

5. Skapa arvet mellan Vehicle och Lorry

Då är det dags att koppla(?) på vår lastbil också. Den skall ju ärva från Vehicle precis som Car men den skall också ha ytterligare en medlemsvariabel enligt den UML som vi skapade tidigare i denna tutorial.

5.1 Skapa klassen Lorry.cs

Vi börjar att skapa filen Lorry.cs, som en kopia av Car, och ser till att vi kan skapa ett objekt av typen Lorry från programklassen.

Kod Programkörning

Bra så långt. Nu är det dags att lägga till den specifika medlemsvariabeln som finns i klassen Lorry. Då måste variabeln (load) först deklareras och sedan läggs till i konstruktorn.

Kod

Det som händer i konstruktorn är att ni vi anropar base() så körs basklassens konstruktor med de parametrar som skickas in till den. När vi sedan kommer innanför måsvingarna så är objektet redan skapat och vi kan göra de tillägg och/eller förändringar vi vill göra.

Kod

I programmet måste det nu läggas till en parameter.

Programkörning

Körningen sker utan error men medlemmen load skrivs inte ut och det beror på att vi inte har fått med oss den i medlemsmetoden toString().

5.2 Fixa till medlemsmetoden ToString()

Nu finns det några olika sätt att bygga denna metoden. I Vehicle så är metoden ToString() deklarerad som override vilket innebär att den metoden kör över den ärvda metoden ToString() och då är det den senaste som gäller. Eftersom denna kod fungerar och redan finns med bland våra exempel så skall jag visa ett alternativ. Det jag vill göra är att inifrån Lorry's medlemsmetod ToString() så kommer jag anropa basklassens ToString() och sedan bygga på denna.

Kod

Vid körning ser vi att nu skrivs det ut en annan informationstext än den som skrivs ut för lastbilar.

Programkörning

5.3 Deklarera medlemsmetod som abstract

Jag skall visa hur abstract fungerar genom att bygga om ToStringList() så att den skapar olika output beroende på vilken typ av objekt det är. Abstract innebär att metoden i basklassen bara deklareras men den kommer aldrig kunna användas. Däremot tvingar den subklasserna att implementera just denna metod och skapa unikt innehåll för varje klass.

Jag börjar att deklarera metoden som abstract i klassen Vehicle.

Kod

När jag testar att köra detta så får jag ett error som visar på det som jag pratade om tidigare, metoden ToStringList har inte implementerats i Lorry och Car.

Kod

Då får vi implementera denna metod och vi kan ju använda oss av den kod som redan finns i vår gamla metod (den som jag kommenterade bort när jag skapade den nya metoddefinitionen). Jag använder koden precis som den är skriven för klassen Car men jag vill även skriva ut maxlast för min lastbil. Jag förbereder för en extra kolumn i utskriften.

Kod

Klassen Car med metoden skriven på exakt samma sätt som den som tidigare fanns i Vehicle, lägg märke till att vi här måste skriva override i metoddeklarationen för att visa att vi skriver över en metod som redan är definierad.

Kod

I klassen Lorry passade jag på att skriva om metoden så att jag fick med maxlast. Så som metoden var skriven tidigare att det skapades en grundsträng och sedan lades till saker fungerade dåligt när jag skulle lägga till ytterligare saker så jag vände på det och lagrade om fordonet var till salu före och sedan skapade jag strängen i ett svep.

Jag skriver till i testkoden så att det skrivs ut och testar sedan applikationen.

Kod Programkörning

Då borde vi egentligen vara klara men det finns en sak jag retar mig på och ni gör säkerligen det samma. Det handlar om rad 44 i klassen Lorry.

I strängbyggandet använder jag medlemsvariabeln load istället för en property för den medlemmen som jag gör för de andra medlemmarna. Det kommer inte göra någon skillnad när vi kör applikationen men det gör att vi är inkonsekventa när vi gör på ett sätt i klassen Vehicle och på ett annat sätt i klassen Lorry. Därför bör vi förändra detta.

Kod

Jag passar samtidigt på att ändra lite i konstruktorn så att jag använder mig av den nya propertyn.

Kod

Samma typ av ändring görs även i metoden ToStringList, när vi har properties så skall dessa användas.

Kod

Jag väljer denna gången att inte ta hand om negativa maxlaster liknande det vi gjorde för year. Det är fritt fram att bygga er egna logik kring detta.

6. Sammanfattning & vidareutveckling

Nu har vi en applikation som bygger på arv och koden är bättre strukturerad. För tillfället kanske du tycker att det inte gör så stor skillnad men tänk när denna listan skall byggas ut även för motorcyklar, arbetsmaskiner, truckar, bussar och alla andra möjliga och omöjliga fordon med specifika önskemål om vad som skall lagras.

Denna applikation kan naturligtvis vidareutvecklas på många olika sätt och vi kommer bygga vidare på denna när vi skall bygga ihop vårt arv med en lista för att skapa en applikation som liknar den tidigare vi gjort fast med våra nya klasser.

Innan vi helt släpper denna applikation så behöver vi lägga in de ändringarna som vi har gjort i vår UML. Eftersom UML är vårt sätt att tydligt visa hur klasserna ser ut och hänger ihop så är det viktigt att inte slarva med detta utan ta sig den tid som krävs för att den skall stämma överens med verkligheten.

UML

Lägg märke till att vi visar att klasser och metoder är abstrakta genom att göra dem kursiva.

Innan du är helt klar se till att gå igenom koden, snygga till de avsnitt som är slarviga och ta bort eventuella testkoder som du har lekt med och som inte längre behövs. Spara också en kopia på hela applikationen innan då stänger ner.

7. Hela applikationens kod

Här kommer nu i vanlig ordning hela applikationens kod som den ser ut om du har följt denna tutorial.

7.1 Program.cs

kod

7.2 Vehicle.cs

kod kod kod kod

7.3 Car.cs

kod

7.4 Lorry.cs

kod kod