====== Klasser & Objekter ====== ====== Objektorienteret Programmering (OOP) ====== Objektorienteret programmering er en programmeringsmetode, hvor man organiserer sin kode i **klasser** og **objekter**, der repræsenterer ting i den virkelige verden.\\ Hver klasse definerer data (felter/variabler) og adfærd (metoder), som objekter af den klasse kan bruge. Vi kan betragte klassen som en **skabelon** (template), der beskriver hvordan et objekt ser ud og opfører sig.\\ Når vi opretter et objekt ud fra en klasse, kalder vi det en **instans** af klassen. **Eksempel på tankegang:**\\ Klassen ''Hund'' beskriver, hvad en hund er (navn, race, vægt) og hvad den kan (gø, løb, spis).\\ Objekterne ''fido'' og ''rex'' er to konkrete hunde – begge instanser af klassen ''Hund''. ---- ===== De 4 Principper i OOP ===== OOP bygger på fire grundprincipper, ofte forkortet **A-P-I-E**: ^ Princip ^ Dansk ^ Kort forklaring ^ | **Abstraction** | Abstraktion | Skjul kompleksitet – vis kun det relevante | | **Polymorphism** | Polymorfi | Samme metodenavn, forskellig opførsel | | **Inheritance** | Nedarvning | En klasse kan overtage egenskaber fra en anden | | **Encapsulation** | Indkapsling | Skjul interne data – kontrollér adgang via metoder | ---- ===== 1. Indkapsling (Encapsulation) ===== **Indkapsling** betyder, at et objekts interne data holdes **privat**, og at adgang til dataene kun sker gennem **metoder** (getters/setters) eller **properties**.\\ Dette beskytter objektet mod fejlagtig brug udefra. **Hvorfor?** Forestil dig en bil med en brændstoftank. Du kan ikke selv stikke hånden ind i tanken og ændre mængden – du bruger en pumpe (metoden). På samme måde bør kode ikke kunne ændre et objekts data direkte. **Uden indkapsling (farligt):** class Bil { public float fuelLevel; } Bil bil = new Bil(); bil.fuelLevel = -8; // Fejl! Negativ brændstof giver ingen mening bil.fuelLevel = 999; // Fejl! Tanken er ikke uendelig **Med indkapsling (sikkert):** class Bil { private float fuelLevel; // skjult for omverdenen private float maxFuel = 40; // Property – læs værdien (read-only udefra) public float FuelLevel { get { return fuelLevel; } } // Metode med validering public void PutFuelInCar(float liter) { if (liter > 0 && fuelLevel + liter <= maxFuel) { fuelLevel += liter; } else { Console.WriteLine("Ugyldig mængde brændstof!"); } } public void Drive() { if (fuelLevel > 0) { fuelLevel -= 0.1f; } } } Nu kan man **ikke** sætte brændstof til en ugyldig værdi – klassen selv kontrollerer det. ---- ===== 2. Abstraktion (Abstraction) ===== **Abstraktion** handler om at skjule unødvendige detaljer og kun vise det, der er relevant for brugeren af klassen.\\ Tænk på en fjernbetjening: du trykker på "skift kanal" uden at vide noget om elektronikken bag. I kode betyder det, at den der **bruger** en klasse ikke behøver at kende den interne implementering – kun hvad klassen **kan** (dens metoder). class Motor { private float temperature; private float rpm; // Brugeren behøver ikke vide HVORDAN motoren starter public void Start() { rpm = 800; temperature = 90; Console.WriteLine("Motor startet"); } public void Stop() { rpm = 0; Console.WriteLine("Motor stoppet"); } } // Bruger af klassen – behøver ikke kende temperature eller rpm Motor m = new Motor(); m.Start(); m.Stop(); Abstraktion og indkapsling arbejder tæt sammen: indkapsling **skjuler data**, abstraktion **skjuler kompleksitet**. ---- ===== 3. Nedarvning (Inheritance) ===== **Nedarvning** betyder, at en klasse (kaldet **sub-klasse**, **barne-klasse**, eller **child-class**) kan **arve** felter og metoder fra en anden klasse (kaldet **superklasse**, **forældreklasse** eller **base-class** i c-sharp).\\ Dette reducerer kodeduplikering og giver en naturlig hierarkisk struktur. **Eksempel:** Både en bil og en motorcykel er køretøjer. De deler nogle egenskaber (hastighed, farve), men har også deres egne. // Superklasse class Køretøj { public float Speed { get; protected set; } public string Farve { get; set; } public void Accelerer() { Speed += 10; Console.WriteLine("Accelererer! Hastighed: " + Speed); } } // Subklasse – arver fra Køretøj class Bil : Køretøj { public int AntalDøre { get; set; } public void Honk() { Console.WriteLine("Tuut tuut!"); } } // Subklasse – arver fra Køretøj class Motorcykel : Køretøj { public bool Sidevogn { get; set; } public void Wheelie() { Console.WriteLine("Wheelie!"); } } Bil b = new Bil(); b.Farve = "rød"; // arvet fra Køretøj b.Accelerer(); // arvet fra Køretøj b.Honk(); // Bils egen metode Motorcykel mc = new Motorcykel(); mc.Accelerer(); // arvet fra Køretøj mc.Wheelie(); // Motorcykels egen metode **Vigtige begreber ved nedarvning:** * '':'' bruges i C# til at angive, at en klasse arver fra en anden (svarende til ''extends'' i Java/Processing) * Subklassen kan **tilføje** nye felter og metoder * Subklassen kan **overskrive** (override) nedarvede metoder med nyt indhold – metoden skal markeres ''virtual'' i superklassen og ''override'' i subklassen ---- ===== 4. Polymorfi (Polymorphism) ===== **Polymorfi** betyder "mange former". Det handler om, at det **samme metodenavn** kan opføre sig **forskelligt** afhængigt af, hvilken klasse der er tale om. Dette er særligt nyttigt når man har en samling af objekter fra relaterede klasser – man kan kalde den samme metode på dem alle, og hver gør sin egen ting. **Eksempel med former:** class Shape { // virtual betyder at subklasser må overskrive metoden public virtual void Draw() { Console.WriteLine("Tegner en form"); } } class Circle : Shape { public override void Draw() // overskriver Shape's Draw() { Console.WriteLine("Tegner en cirkel ○"); } } class Rectangle : Shape { public override void Draw() // overskriver Shape's Draw() { Console.WriteLine("Tegner et rektangel □"); } } Shape[] former = new Shape[3]; former[0] = new Circle(); former[1] = new Rectangle(); former[2] = new Circle(); // Kalder Draw() på alle – hver form reagerer forskelligt! foreach (Shape s in former) { s.Draw(); } Alle former i arrayet er af typen ''Shape'', men de **opfører sig forskelligt** når ''Draw()'' kaldes. Det er polymorfi. **Method overloading** er en anden form for polymorfi – samme metodenavn, men forskellige parametre: class Printer { public void Print(string tekst) { Console.WriteLine(tekst); } public void Print(int tal) { Console.WriteLine("Tal: " + tal); } public void Print(string tekst, int gange) { for (int i = 0; i < gange; i++) { Console.WriteLine(tekst); } } } ---- ===== Constructor ===== En **constructor** er en speciel metode, der automatisk kaldes, når et objekt oprettes med ''new''.\\ Den bruges til at give objektet sine startværdier. **Kendetegn for en constructor:** * Den hedder **det samme som klassen** * Den har **ingen returtype** (ikke engang ''void'') * Den kaldes automatisk ved ''new'' class Ball { private float x, y; private float radius; private string farve; // Constructor public Ball(float x, float y, float r, string farve) { this.x = x; this.y = y; this.radius = r; this.farve = farve; } public void Display() { Console.WriteLine($"Bold på ({x}, {y}) med radius {radius} i farven {farve}"); } } Ball b1 = new Ball(100, 200, 30, "rød"); Ball b2 = new Ball(300, 150, 50, "blå"); b1.Display(); b2.Display(); ''this.x'' refererer til **klassens felt**, mens ''x'' (uden this) er **parameteren** til constructoren. ---- ===== Dot Notation ===== Når vi har lavet et objekt, tilgår vi dets felter og metoder med **punktum-notation** (dot notation): objektNavn.MetodeNavn(); objektNavn.PropertyNavn; **Eksempel:** Ball b = new Ball(100, 100, 30, "hvid"); b.Display(); // kald en metode Console.WriteLine(b.FuelLevel); // læs en property ---- ===== Samlet eksempel i C# ===== Her er et eksempel der bruger alle fire principper: // Superklasse (Abstraktion + Indkapsling) class Shape { protected float x, y; private string color; public Shape(float x, float y, string color) { this.x = x; this.y = y; this.color = color; } public string GetColor() { return color; } // virtual: subklasser kan overskrive (Polymorfi) public virtual void Display() { Console.Write($"[{color}] "); } public virtual void Move() { Random rnd = new Random(); x += rnd.Next(-1, 2); y += rnd.Next(-1, 2); } } // Arv: Circle arver fra Shape class Circle : Shape { private float radius; public Circle(float x, float y, float r, string color) : base(x, y, color) // kalder superklassens constructor { this.radius = r; } public override void Display() // Polymorfi { base.Display(); Console.WriteLine($"Cirkel på ({x}, {y}) med radius {radius}"); } } // Arv: Box arver fra Shape class Box : Shape { private float width, height; public Box(float x, float y, float w, float h, string color) : base(x, y, color) { this.width = w; this.height = h; } public override void Display() // Polymorfi { base.Display(); Console.WriteLine($"Boks på ({x}, {y}) med størrelse {width}x{height}"); } } // --- Hoved program --- Shape[] former = new Shape[] { new Circle(100, 200, 40, "rød"), new Box(300, 150, 80, 50, "grøn"), new Circle(450, 300, 25, "blå"), new Box(200, 300, 60, 60, "gul"), new Circle(500, 100, 50, "lilla") }; foreach (Shape s in former) { s.Move(); s.Display(); // polymorfi: hver form viser sig forskelligt } ---- ===== UML Klassediagram ===== UML (Unified Modeling Language) bruges til at tegne et diagram over klassernes struktur og relationer **inden** man koder. +------------------+ | Shape | +------------------+ | # x : float | | # y : float | | - color : string | +------------------+ | + Display() | | + Move() | | + GetColor() | +------------------+ △ | (arv) _____|______ | | +----------+ +-----------+ | Circle | | Box | +----------+ +-----------+ | -radius | | -width | +----------+ | -height | |+Display()| +-----------+ +----------+ |+Display() | +-----------+ Symboler i UML: * ''-'' = private * ''+'' = public * ''#'' = protected * ''△'' = arv (pilen peger mod superklassen) ---- ===== Øvelser ===== - **Lav en klasse "Car"** \\ Giv den position, farve og hastighed som private felter med properties. Brug constructor. Sørg for at hastigheden ikke kan sættes negativ via en setter med validering. - **Brug flere objekter** \\ Lav et array af 10 ''Ball''-objekter og udskriv dem alle med en løkke. - **Tilføj kontrol** \\ Giv ''Ball''-klassen en metode ''Reverse()'', som vender bevægelsens retning. Kald den når bolden "rammer kanten" (x > 800). - **Brug properties** \\ Giv brugeren mulighed for at ændre et objekts størrelse – men sæt grænser for min og max størrelse i property-setteren. - **Arv og polymorfi** \\ Lav en ''Animal''-superklasse med den virtuelle metode ''MakeSound()''. Lav subklasser ''Dog'', ''Cat'' og ''Bird'', der hver overrider ''MakeSound()'' forskelligt. Lav et array med blandede dyr og kald ''MakeSound()'' på dem alle. ---- ===== Test dig selv ===== - Hvad er forskellen på en **klasse** og et **objekt**? - Hvad er en **constructor**, og hvornår kaldes den? - Hvad betyder ''private'' og ''public''? - Forklar med egne ord hvad **indkapsling** er, og hvorfor det er nyttigt. - Hvad er **arv**, og hvad bruges '':'' til i C#? - Hvad er **polymorfi**? Giv et eksempel. - Hvad er forskellen på ''virtual'' og ''override''? - Hvad betyder ''this.x = x'' inde i en constructor? - Hvad er forskellen på en **metode** og en **property** i C#?