Kihagyás

11. gyakorlat

A gyakorlat anyaga

Ezen a gyakorlaton megismerkedünk a JavaScript nyelven történő objektumorientált programozással.

Osztályok, objektumok

A korábban megismert objektumorientált paradigma alapvető elemei az osztályok. Ezek tulajdonképpen azonos tulajdonságokkal és viselkedéssel rendelkező objektumok általánosításai, formai leírásai.

Érdekes módon a JavaScriptben nem mindig voltak osztályok. Az osztály, mint beépített nyelvi elem csupán a 2015-ös ECMAScript6 (ES6) szabványban jelent meg. Arról, hogy az osztályok bevezetése előtt milyen megoldások voltak JavaScriptben az objektumok általános formai leírásának elkészítésre, az előadáson lesz szó.

Osztályok létrehozása

A fentebb említett ECMAScript6 szabvány bevezette a class kulcsszót, aminek segítségével osztályokat hozhatunk létre JavaScriptben.

Példa: Egy Jarmu nevű osztály létrehozása

1
2
3
class Jarmu {
    // ide jön majd az osztály törzse...
}
Adattagok, metódusok

Egy osztálynak lehetnek adattagjai, illetve metódusai (tagfüggvényei). Ezekre JavaScriptben a . (pont) operátorral tudunk hivatkozni.

Az osztály metódusait a függvényekhez hasonló módon hozhatjuk létre, azzal a fontos különbséggel, hogy a függvények definiálásakor használt function kulcsszót elhagyjuk.

Példa: Egy paraméter nélküli info() és egy egyparaméteres utastFelvesz() metódus

1
2
3
4
5
6
7
8
9
class Jarmu {
    info() {
        console.log("Ez egy jármű");
    }

    utastFelvesz(utasNeve) {
        console.log("Az új utasunk: " + utasNeve);
    }
}

Hasonlóképpen, mint ahogy azt Pythonban tanultuk, JavaScriptben sem deklaráljuk az adattagokat külön az osztályban. Az adattagok létrehozása és inicializálása itt is a konstruktorban történik.

Konstruktor

JavaScriptben a constructor() névre hallgató speciális metódus lesz az osztály konstruktora. Ez természetesen akkor kerül meghívásra, amikor az osztályt példányosítjuk. A konstruktort használjuk az osztály adattagjainak létrehozására és inicializálására.

Amennyiben az osztályunkba nem írunk konstruktort, akkor az interpreter automatikusan gyártani fog egy paraméter nélküli konstruktort (default konstruktor), ami az osztály példányosításakor fog meghívódni.

JavaScriptben a this kulcsszóval hivatkozhatunk az aktuális objektumra. Ha egy osztályon belül hivatkozni szeretnénk egy adattagra vagy egy metódusra, akkor a kérdéses adattag vagy metódus neve elé mindig kötelezően ki kell írni a this kulcsszót!

Példa: Egy két paraméteres konstruktor, amely paraméterei alapján inicializáljuk a marka és sebesseg adattagokat

1
2
3
4
5
6
class Jarmu {
    constructor(marka, sebesseg) {
        this.marka = marka;
        this.sebesseg = sebesseg;
    }
}

JavaScriptben továbbra sincs function overload. Ha azt szeretnénk elérni, hogy egy metódust többféle, eltérő paraméterezéssel is tudjunk használni, akkor használjuk a default függvényparamétereket.

Példa: Írjuk át a Jármű osztály konstruktorát úgy, hogy a sebesség paraméter értékét ne legyen kötelező megadni, alapértéke legyen 0!

1
2
3
4
5
6
class Jarmu {
    constructor(marka, sebesseg=0) {
        this.marka = marka;
        this.sebesseg = sebesseg;
    }
}

Példa: Egészítsük ki az osztályunkat a következőkkel:

  • A konstruktorban hozzunk létre egy utasok adattagot, amit egy üres tömbbel inicializáljunk!
  • Írjuk meg az utastFelvesz() metódust, amely egy utas nevét (szöveges adat) várja paraméterül! A metódus szúrja be a paraméterül kapott utas nevét az utasok tömb végére!
  • Készítsük el az info() metódust, ami kiírja a jármű márkáját, sebességét és az utasok számát!
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Jarmu {
    constructor(marka, sebesseg=0) {
        this.marka = marka;
        this.sebesseg = sebesseg;
        this.utasok = [];               // új adattag, üres tömb kezdőértékkel
    }

    utastFelvesz(utasNeve) {
        if (typeof utasNeve === "string") { // típusellenőrzés
            this.utasok.push(utasNeve);         // új utas beszúrása a tömbbe
        }
    }

    info() {
        console.log(`${this.marka} márkájú jármű, melynek sebessége ${this.sebesseg} km/h, és ${this.utasok.length} utast szállít.`);
    }
}

Objektumok létrehozása

A példányosítás során az osztályból egy objektumpéldányt hozunk létre.

Az osztály példányosítása, avagy egy új objektum létrehozása JavaScriptben a new kulcsszó segítségével történik. A példányosításkor meghívjuk az osztály konstruktorát, és átadjuk neki a megfelelő paramétereket (ha vannak).

Példa: A Jármű osztály példányosítása, metódusok meghívása

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Jarmu {
    constructor(marka, sebesseg=0) {
        this.marka = marka;
        this.sebesseg = sebesseg;
        this.utasok = [];               // új adattag, üres tömb kezdőértékkel
    }

    utastFelvesz(utasNeve) {
        if (typeof utasNeve === "string") { // típusellenőrzés
            this.utasok.push(utasNeve);         // új utas beszúrása a tömbbe
        }
    }

    info() {
        console.log(`${this.marka} márkájú jármű, melynek sebessége ${this.sebesseg} km/h, és ${this.utasok.length} utast szállít.`);
    }
}

// példányosítás + metódusok kipróbálása

const jarmu1 = new Jarmu("Škoda", 90);
const jarmu2 = new Jarmu("Ikarus");     // az sebesség a default 0-s értéket veszi fel

jarmu1.utastFelvesz("Pista bácsi");
jarmu1.info();
jarmu2.info();

Kimenet

Škoda márkájú jármű, melynek sebessége 90 km/h, és 1 utast szállít. Ikarus márkájú jármű, melynek sebessége 0 km/h, és 0 utast szállít.

Láthatóságok, getterek, setterek

A Pythonhoz hasonló módon JavaScriptben sem tudjuk szabályozni módosítószavakkal az adattagok láthatóságát (vesd össze: Java). Alapértelmezett módon minden adattag publikus, azaz mindenhonnan elérhető.

Konvenció alapján az adattag neve előtti egyszeres alulvonás jelzi azt, hogy az adattag nem publikus használatra van szánva ("private adattag"). Viszont ettől az adattag kívülről továbbra is elérhető lesz!

Példa: Jelezzük, hogy a márka és sebesség adattagokat nem publikus használatra szánjuk!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Jarmu {
    constructor(marka, sebesseg=0) {
        this._marka = marka;
        this._sebesseg = sebesseg;
        this.utasok = [];               // új adattag, üres tömb kezdőértékkel
    }

    utastFelvesz(utasNeve) {
        if (typeof utasNeve === "string") { // típusellenőrzés
            this.utasok.push(utasNeve);         // új utas beszúrása a tömbbe
        }
    }

    info() {
        console.log(`${this._marka} márkájú jármű, melynek sebessége ${this._sebesseg} km/h, és ${this.utasok.length} utast szállít.`);
    }
}

Ha a nem publikus használatra szánt adattagok szabályozott elérését és módosítását szeretnénk elérni JavaScriptben, akkor készíthetünk ezekhez az adattagokhoz gettereket, illetve settereket.

JavaScriptben a gettert, valamint a settert property-ként valósíthatjuk meg. A get property-t a get, míg a set property-t a set kulcsszóval hozhatjuk létre. A property-k használatával szabályozott módon kérhetjük le és állíthatjuk be adattagok értékét úgy, hogy kívülről azt a látszatot keltjük, mintha új, publikus adattagokkal dolgoznánk.

Példa: Készítsünk gettert és settert az _sebesseg adattaghoz!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Jarmu {
    constructor(marka, sebesseg=0) {
        this._marka = marka;
        this._sebesseg = sebesseg;
        this.utasok = [];
    }

    get sebesseg() {
        return this._sebesseg;
    }

    set sebesseg(ujErtek) {
        // a setterben mindenféle ellenőrzéseket végezhetünk... 

        if (typeof ujErtek === "number" && ujErtek >= 0) {
            this._sebesseg = ujErtek;
        } else {
            console.log("A sebesség értéke csak nemnegatív szám lehet!");
        }
    }

    // ...
}

const jarmu = new Jarmu("Lada", 50);
jarmu.sebesseg = 60;            // setter hívás
jarmu.sebesseg = -100;          // setter hívás (hibás adat)
console.log(jarmu.sebesseg);    // getter hívás

Kimenet

A sebesség értéke csak nemnegatív szám lehet! 60

Figyelem

Fontos, hogy a property és az adattag neve mindig eltérő legyen, különben végtelen rekurzióba futunk! A példában a property neve sebesseg (alulvonás nélkül), az adattag neve ettől eltérő módon _sebesseg (alulvonással).

Feladat: A fenti mintájára készítsünk get és set property-t a _marka adattaghoz is!

Öröklődés

A korábbi tanulmányainkból ismerős lehet számunkra az öröklődés fogalma. Ez egy osztályok közötti kapcsolat, amely egy úgynevezett ősosztály (szülőosztály) és egy gyermekosztály (leszármazott osztály) között valósul meg. A gyermekosztály tulajdonképpen az ősosztályának egy speciális változata lesz (pl. a Kutya osztály az Allat osztály gyermeke, hiszen minden kutya egyben állat is).

Az öröklődés során a gyermekosztály megörökli az ősosztályának összes adattagját és metódusát. A gyermekosztályban létrehozhatunk az örökölt adattagokon kívül még egyéb adattagokat, metódusokat, illetve lehetőségünk van az örökölt metódusok működésének felüldefiniálására is (overriding).

JavaScriptben az öröklődést a következő szintaxissal adhatjuk meg:

1
2
3
class GyermekOsztaly extends OsOsztaly {
    // gyermekosztály törzse...
}

Fontos megjegyezni, hogy JavaScriptben csak egyszeres öröklődés van, tehát egy osztálynak nem lehet kettő vagy több ősosztálya.

A gyermekosztályból hivatkozhatunk az ősosztályra, annak adattagjaira és metódusaira a super kulcsszóval. Ennek segítségével meghívhatjuk az ősosztály konstruktorát az adott osztályon belül. Ha a gyermekosztályban nem hozunk létre új adattagot, akkor az ősosztály konstruktorának meghívása elhagyható.

Példa: Hozzunk létre egy Auto osztályt, ami az imént elkészített Jarmu osztályból származik!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// ősosztály (őt már korábban megírtuk)

class Jarmu {
    constructor(marka, sebesseg=0) {
        this._marka = marka;
        this._sebesseg = sebesseg;
        this.utasok = [];
    }

    get marka() {
        return this._marka;
    }

    set marka(ertek) {
        this._marka = ertek;
    }

    get sebesseg() {
        return this._sebesseg;
    }

    set sebesseg(ertek) {
        if (typeof ertek === "number" && ertek >= 0)
            this._sebesseg = ertek;
        else
            console.log("A sebesség értéke csak nemnegatív szám lehet!");
    }

    utastFelvesz(utasNeve) {
        if (typeof utasNeve === "string")
            this.utasok.push(utasNeve);
    }

    info() {
        console.log(`${this._marka} márkájú jármű, melynek sebessége ${this._sebesseg} km/h, és ${this.utasok.length} utast szállít.`);
    }
}

// gyermekosztály

class Auto extends Jarmu {      // az autó egy speciális jármű lesz
    constructor(marka, sebesseg=0, onvezeto=false) {
        super(marka, sebesseg);         // ősosztály konstruktorának meghívása
        this.onvezeto = onvezeto;       // egy új adattag, ami az ősben nem szerepelt
    }

    info() {                    // overriding: az ősből örökölt info() metódus felüldefiniálása
        console.log(`${this.marka} autó, (sebesség: ${this.sebesseg} km/h), utasok száma: ${this.utasok.length}, önvezető: ${this.onvezeto ? "igen" : "nem"}`);
    }

    dudal() {                   // új metódus definiálása
        console.log("TÜTŰŰŰ!");
    }
}

// példányosítás

const auto1 = new Auto("Trabant", 40, false);
const auto2 = new Auto("Tesla", 130, true);

auto2.utastFelvesz("Elon Musk");    // ez a metódus az ősosztályból jön
auto2.utastFelvesz("Bill Gates");

auto1.dudal();
auto1.info();
auto2.info();

Kimenet

TÜTŰŰŰ! Trabant autó, (sebesség: 40 km/h), utasok száma: 0, önvezető: nem Tesla autó, (sebesség: 130 km/h), utasok száma: 2, önvezető: igen

Típusellenőrzés

Amint azt korábban láthattuk, a typeof operátorral le tudjuk kérni, hogy egy adott objektum adott típusú-e.

1
2
console.log(typeof 42 === "number");        // true
console.log(typeof 42 === "string");        // false

Fontos megjegyezni, hogy a typeof csak beépített típusokra működik! Ha egy saját osztályból példányosított objektumról szeretnénk eldönteni, hogy adott típusú-e, akkor az instanceof operátor használatos. Az obj instanceof ClassName visszaadja, hogy az obj objektum ClassName osztály példánya-e.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Jarmu {
    constructor(marka, sebesseg=0) {
        this._marka = marka;
        this._sebesseg = sebesseg;
        this.utasok = [];
    }
}

class Auto extends Jarmu {
    constructor(marka, sebesseg=0, onvezeto=false) {
        super(marka, sebesseg);
        this.onvezeto = onvezeto;
    }
}

const jarmu = new Jarmu("Apache helikopter", 100);
const auto = new Auto("Volkswagen", 70, false);

console.log(jarmu instanceof Jarmu);        // true
console.log(auto instanceof Jarmu);         // true
console.log(jarmu instanceof Auto);         // false

Megjegyzés

Tömbök esetén is az instanceof kulcsszót használhatjuk (tomb1 instanceof Array), vagy az Array.isArray() beépített függvényt.

Kiegészítés: A Math objektum

Ha JavaScriptben programozunk, gyakran előfordulhat, hogy matematikai függvények vagy konstansok használatára van szükségünk. Ebben nyújt nekünk segítséget a Math objektum:

  • Math.abs(n): visszaadja az n szám abszolútértékét
  • Math.round(n): a hagyományos kerekítési szabályok szerint kerekíti az n számot
  • Math.floor(n): lefelé kerekíti az n számot
  • Math.ceil(n): felfelé kerekíti az n számot
  • Math.sqrt(n): visszaadja az n szám négyzetgyökét
  • Math.min(a, b, ...): visszaadja az a, b, ... számok közül a legkisebbet
  • Math.max(a, b, ...): visszaadja az a, b, ... számok közül a legnagyobbat
  • Math.random(): visszaad egy véletlenszámot a [0, 1) intervallumból
  • Math.sin(n), Math.cos(n), Math.tan(n): visszaadják n szinuszát, koszinuszát, tangensét (alapból radiánban számolnak, szükség esetén át kell váltani n-t fokba!)
  • Math.PI: a pí értéke
  • Math.E: az Euler-féle szám értéke
  • ...

Példa: 1 és 10 közötti random egész szám generálása

1
let randomInteger = Math.floor(Math.random() * 10 + 1);

Feladatok

Az anyagrészhez tartozó gyakorló feladatsor elérhető itt.


Utolsó frissítés: 2020-11-18 08:38:30