Indholdsfortegnelse

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:


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:

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:


Øvelser

  1. 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.
  2. Brug flere objekter
    Lav et array af 10 Ball-objekter og udskriv dem alle med en løkke.
  3. Tilføj kontrol
    Giv Ball-klassen en metode Reverse(), som vender bevægelsens retning. Kald den når bolden „rammer kanten“ (x > 800).
  4. 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.
  5. 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

  1. Hvad er forskellen på en klasse og et objekt?
  2. Hvad er en constructor, og hvornår kaldes den?
  3. Hvad betyder private og public?
  4. Forklar med egne ord hvad indkapsling er, og hvorfor det er nyttigt.
  5. Hvad er arv, og hvad bruges : til i C#?
  6. Hvad er polymorfi? Giv et eksempel.
  7. Hvad er forskellen på virtual og override?
  8. Hvad betyder this.x = x inde i en constructor?
  9. Hvad er forskellen på en metode og en property i C#?