Object Georiënteerd Programmeren: De Basisconcepten (Javascript)

Dit overzicht bespreekt de kernprincipes van OOP met een eenvoudige uitleg en een codevoorbeeld in JavaScript.

  1. Class en Object

    Eenvoudige Beschrijving: Een Class is het algemene plan of de blauwdruk. Het definieert wat voor soort attributen (eigenschappen) en methods (handelingen) een ding zal hebben. Een Object is een tastbaar exemplaar van die blauwdruk. Je maakt een object met het trefwoord new.

    class Auto { // De Blauwdruk (Class)
        constructor(kleur) {
            this.kleur = kleur; // Attribuut
        }
        rijden() { // Method
            return `De ${this.kleur}e auto rijdt.`;
        }
    }
    
    const mijnAuto = new Auto("rood"); // Het exemplaar (Object)
    console.log(mijnAuto.rijden()); // Output: De rode auto rijdt.
  2. Attribuut en Method

    Eenvoudige Beschrijving: Een Attribuut is een eigenschap of kenmerk van een object (bijvoorbeeld kleur of naam). Een Method is een actie of handeling die het object kan uitvoeren (bijvoorbeeld starten of stoppen).

    class Lamp {
        #isAan = false; // Attribuut: toestand van de lamp
    
        zetAan() { // Method: handeling om de toestand te veranderen
            this.#isAan = true;
        }
        getStatus() {
            return this.#isAan ? "Aan" : "Uit";
        }
    }
    
    const halLamp = new Lamp();
    halLamp.zetAan();
    console.log(halLamp.getStatus()); // Output: Aan

    Uitbreiding : De waarde van een attributen kan je uitlezen en wijzigen op 2 manieren : getter/setter-methods (zoals het voorbeeld hierboven) of accessor properties ( get en set sleutelwoorden).

    class Thermometer {
        // 1. Het private (interne) attribuut
        #temperatuur = 0; 
    
        // 2. Getter: Wordt uitgevoerd wanneer je 'mijnThermometer.celsius' leest.
        get celsius() {
            console.log("--> Getter uitgevoerd: waarde wordt gelezen.");
            return this.#temperatuur;
        }
    
        // 3. Setter: Wordt uitgevoerd wanneer je 'mijnThermometer.celsius = 25' schrijft.
        set celsius(nieuweTemperatuur) {
            if (nieuweTemperatuur < -273) {
                console.error("Fout: Temperatuur kan niet onder het absolute nulpunt komen.");
                return; // Stopt de toewijzing
            }
            console.log("--> Setter uitgevoerd: waarde wordt gevalideerd en gewijzigd.");
            this.#temperatuur = nieuweTemperatuur;
        }
    
        // Een berekende getter is ook mogelijk
        get fahrenheit() {
            return (this.#temperatuur * 9/5) + 32;
        }
    }
    
    const mijnThermometer = new Thermometer();
    
    // Gebruik als een normale property (roept de SETTER op)
    mijnThermometer.celsius = 20; 
    
    // Gebruik als een normale property (roept de GETTER op)
    console.log(`De temperatuur is nu ${mijnThermometer.celsius}°C.`);
    
    // De berekende property (roept de GETTER op)
    console.log(`Dat is ${mijnThermometer.fahrenheit}°F.`); 
    
    // Test de validatie (roept de SETTER op, maar deze mislukt)
    mijnThermometer.celsius = -300; 
  3. Encapsulation (Inkapseling)

    Eenvoudige Beschrijving: Encapsulation beschermt de interne data van een object door de attributen "private" te maken (# in JavaScript). Dit zorgt ervoor dat de attributen niet direct van buitenaf aangepast kunnen worden, wat foutieve waarden voorkomt. Je gebruikt methods om de data gecontroleerd te lezen of te wijzigen.

    class KlantAccount {
        #saldo = 0; // Private Attribuut (#)
    
        stort(bedrag) {
            if (bedrag > 0) { // Gecontroleerde toegang
                this.#saldo += bedrag;
            }
        }
    
        getSaldo() { // Public Method voor lezen
            return this.#saldo;
        }
    }
    
    const account = new KlantAccount();
    // account.#saldo = 1000; // Dit veroorzaakt een fout
    account.stort(200);
    console.log(account.getSaldo()); // Output: 200 
  4. Constructor

    Eenvoudige Beschrijving: De constructor is de speciale method die automatisch wordt uitgevoerd wanneer een object wordt aangemaakt (bij new). Het wordt gebruikt om de beginwaarden (attributen) van het nieuwe object in te stellen.

    class Product {
        #naam; 
    
        // Constructor initialiseert het attribuut bij het aanmaken
        constructor(productNaam) {
            this.#naam = productNaam;
            console.log(`Product ${this.#naam} is aangemaakt.`);
        }
    
        getNaam() {
            return this.#naam;
        }
    }
    
    const melk = new Product("Melk"); // De constructor wordt nu uitgevoerd
    // Output: Product Melk is aangemaakt. 
  5. Composition (Compositie)

    Eenvoudige Beschrijving: Composition is de "heeft een" relatie tussen classes. Een class gebruikt een object van een andere class als een van zijn eigen attributen. Bijvoorbeeld: een auto heeft een motor.

    class Motor {
        start() {
            return "Motor gestart.";
        }
    }
    
    class AutoMetMotor {
        constructor() {
            // Compositie: Auto 'heeft een' Motor object als attribuut
            this.motor = new Motor(); 
        }
    
        rij() {
            return "Auto rijdt. " + this.motor.start();
        }
    }
    
    const mijnWagen = new AutoMetMotor();
    console.log(mijnWagen.rij()); // Output: Auto rijdt. Motor gestart. 
  6. Inheritance (Overerving)

    Eenvoudige Beschrijving: Inheritance is de "is een" relatie. Een nieuwe class (Derived Class) neemt alle attributen en methods van een bestaande class (Base Class) over met het trefwoord extends. Dit bevordert herbruikbaarheid. De constructor van de Base Class roep je aan met super().

    class Vervoermiddel { // Base Class
        verplaats() {
            return "Het middel beweegt.";
        }
    }
    
    class Fiets extends Vervoermiddel { // Derived Class (is een Vervoermiddel)
        bel() {
            return "Ring ring!";
        }
    }
    
    const stadsfiets = new Fiets();
    console.log(stadsfiets.verplaats()); // Geërfde method
    console.log(stadsfiets.bel());      // Eigen method
    // Output: Het middel beweegt. 

    wat uitgebreider :

    // --- 1. Base Class (Vervoermiddel) ---
    class Vervoermiddel {
        // Gemeenschappelijke private attributen
        #merk;
        #maxSnelheid;
    
        constructor(merk, maxSnelheid) {
            // Initialiseert de geërfde attributen
            this.#merk = merk;
            this.#maxSnelheid = maxSnelheid;
        }
    
        verplaats() {
            // Gemeenschappelijke method
            return `Het ${this.#merk} verplaatst zich.`;
        }
    }
    
    // --- 2. Derived Class (Fiets) ---
    // Gebruikt 'extends' om de attributen en methods van Vervoermiddel over te erven
    class Fiets extends Vervoermiddel {
        #aantalVersnellingen; // Extra attribuut, uniek voor Fiets
    
        constructor(merk, maxSnelheid, aantalVersnellingen) {
            // WERKING VAN super():
            // 1. super() MOET als eerste worden aangeroepen in de constructor van de Derived Class.
            // 2. Het roept de constructor van Vervoermiddel op en geeft de parameters 'merk' en 'maxSnelheid' door.
            // 3. Dit initialiseert de geërfde attributen in de Base Class.
            super(merk, maxSnelheid); 
            
            // 4. Hierna wordt het unieke attribuut van de Fiets class geïnitialiseerd.
            this.#aantalVersnellingen = aantalVersnellingen;
        }
    
        bel() {
            // Unieke method voor Fiets
            return "Ring ring! De fiets belde.";
        }
    
        getInfo() {
            return `Dit is een ${this.constructor.name} van merk ${this.verplaats().split(' ')[1]}. Aantal versnellingen: ${this.#aantalVersnellingen}.`;
        }
    }
    
    // --- 3. Gebruik van het geërfde object ---
    const stadsfiets = new Fiets("Gazelle", 35, 7);
    
    // Gebruikt de geërfde method van de Base Class (Vervoermiddel)
    console.log(stadsfiets.verplaats()); 
    
    // Gebruikt de unieke method van de Derived Class (Fiets)
    console.log(stadsfiets.bel()); 
    
    // Toont geërfde en unieke attributen
    console.log(stadsfiets.getInfo());
    
    /*
    Output:
    Het Gazelle verplaatst zich.
    Ring ring! De fiets belde.
    Dit is een Fiets van merk Gazelle. Aantal versnellingen: 7.
    */
    	 
  7. Method Overriding (Method Overschrijven)

    Eenvoudige Beschrijving: Dit is het aanpassen van een geërfde method in de Derived Class. U geeft de method een andere of uitgebreide functionaliteit. U kunt de oorspronkelijke method van de Base Class oproepen binnen de nieuwe method met super.methodNaam().

    class Dier {
        geluid() {
            return "Maakt een basisgeluid.";
        }
    }
    
    class Kat extends Dier {
        // Overschrijven van de geluid method
        geluid() {
            // Roep de Base Class method op en voeg extra info toe
            return `${super.geluid()} Maar de Kat zegt 'Miau'.`;
        }
    }
    
    const poes = new Kat();
    console.log(poes.geluid()); 
    // Output: Maakt een basisgeluid. Maar de Kat zegt 'Miau'. 
  8. Polymorfisme

    Eenvoudige Beschrijving: Polymorfisme betekent "vele vormen". Het verwijst naar het feit dat eenzelfde method (met dezelfde naam) in verschillende classes bestaat, maar telkens een andere actie uitvoert. Dit is handig wanneer je objecten van verschillende types in een lijst behandelt.

    class Figuur {
        teken() {
            return "Een figuur wordt getekend.";
        }
    }
    
    class Cirkel extends Figuur {
        teken() {
            return "Een ronde vorm wordt getekend.";
        }
    }
    
    class Vierkant extends Figuur {
        teken() {
            return "Een vierkant met rechte hoeken wordt getekend.";
        }
    }
    
    const objecten = [new Cirkel(), new Vierkant()];
    
    // Polymorfisme: dezelfde oproep 'teken()' heeft verschillende resultaten
    for (const obj of objecten) {
        console.log(obj.teken());
    }
    // Output: Een ronde vorm wordt getekend.
    // Output: Een vierkant met rechte hoeken wordt getekend. 
  9. Static

    Eenvoudige Beschrijving: Static attributen of methods zijn Class-eigenschappen, en dus geen object-eigenschappen. Alle objecten delen dezelfde static waarde. Je roept een static method aan via de naam van de Class, niet via een object.

    class Teller {
        static #gemaakteObjecten = 0; // Static attribuut
    
        constructor() {
            // Verhoog de static waarde via de Classnaam
            Teller.#gemaakteObjecten++; 
        }
    
        static getTotaalAantalTellers() { // Static method
            return Teller.#gemaakteObjecten;
        }
    }
    
    let item1 = new Teller();
    let item2 = new Teller();
    
    // Roep static method op via Classnaam
    console.log(Teller.getTotaalAantalTellers()); // Output: 2