====== 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#?