Download as pdf or txt
Download as pdf or txt
You are on page 1of 12

Inicializálás

z Régebbi nyelvekben a nem biztonságos


programozás fő okai:
– Inicializálás hiánya

Inicializálás és takarítás z
– Eltakarítás hiánya
C++ óta: Konstruktor
– Garantált inicializálás objektum létrejöttekor
– Helyette lehetne initialize(), de ezt mindig
kézzel kellene meghívni
– Neve = az osztály neve
class Rock {
– Java-ban nincs Rock() {
destruktor! }
System.out.println("Creating Rock");

Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák


2002. 03. 18. 68 2002. 03. 18. 69
© Ferenc Rudolf © Ferenc Rudolf

Konstruktor Constructor
Operáció-kiterjesztés Overloading
z Mint minden más függvénynek, a z Magasabb szintű nyelvekben
konstruktornak is lehetnek paraméterei: neveket használunk
– Megadják, hogyan inicializáljunk z Természetes nyelvben is lehet több értelme a
Tree t = new Tree(12);
szavaknak:
– Szövegkörnyezetből derül ki az értelme
– Csak úgy lehet létrehozni, hogy megadjuk a
z Programozásban ezt nevezzük overloading-nak vagy
magasságot is
kiterjesztés-nek (túlterhelés ☺)
z Konstruktornak nincs visszatérési értéke – (Overriding = felüldefiniálás, ld. később)
– Objektumra való referenciát kapunk z Régebben pl. C-ben minden név egyedi volt (nincs
printf int-re meg float-ra külön-külön)

Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák


2002. 03. 18. 70 2002. 03. 18. 71
© Ferenc Rudolf © Ferenc Rudolf
Kiterjesztés (folyt.) Kiterjesztés (folyt.)
z C++, Java-ban szükségessé vált, pl.: z Pl. class Tree {
int height;
– Konstruktornak csak egy neve lehet, mégis Tree() { height = 0; }
különböző konstruálásokat szeretnénk, hogyan? }
Tree(int i) { height = i; }

z Metódusok kiterjesztése (nem csak


z Argumentum nélkül hívható konstruktor = default
konstruktor): constructor:
– Ugyanaz a neve de más a paraméter-lista (akár üres – Alapértelmezett beállításokkal rendelkező objektum
is lehet) létrehozására
– Ugyanaz a feladat, miért lenne több különböző z Ha osztályunkban nem definiálunk egy konstruktort
nevű függvényünk? sem, akkor a fordító készít egy default-ot:
– Ha már van valamilyen (akár default akár nem) akkor nem
készít
Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák
2002. 03. 18. 72 2002. 03. 18. 73
© Ferenc Rudolf © Ferenc Rudolf

Megkülönböztetés this
z Hogyan különböztetjük meg, hogy melyik kiterjesztett z Egy függvény kódja csak egy példányban van a
metódus hívódjon? memóriában
z A paraméterlistáknak egyedieknek kell lenniük Class Banana { void f(int i) {/*…*/} }
z Hívás helyén az aktuális argumentumok típusai Banana a = new Banana(), b = new Banana();
a.f(1);
határozzák meg (ld. korai kötés) b.f(2);

z Konvertálható primitív típusoknál bonyolultabb a z Honnan tudja az f(), hogy melyik objektumhoz lett
helyzet: hívva?
– Ha nincs pontos egyezés, akkor az adat konvertálódik
(de csak nagyobb típusokra!) z Egy „titkos” implicit első paramétert generál a fordító
z Függvény visszatérési értéke nem használható a
megkülönböztetésre (lehetetlen is lenne):
– Pl. f(); hívásnál a vissz. érték nincs is használva
Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák
2002. 03. 18. 74 2002. 03. 18. 75
© Ferenc Rudolf © Ferenc Rudolf
this (folyt.) Kitakarítás Cleanup
z Mire jó? Amikor valamire explicite fel akarjuk z Nem csak a korrekt inicializálás fontos,
használni, pl.: hanem az erőforrások helyes kezelése is
– Ha a metódus formális paraméterneve megegyezik z Java: garbage collector (ez csak memóriával
valamelyik mező nevével: foglalkozik)
z Nem minden takarítást tud elvégezni:
this.x = x; // az első az attribútum
– new nélküli memóriafoglalás (pl. natív metódus által)
– Visszaadni egy metódussal: – Egyéb esetben nincs rá szükség mert minden Object
leszármazott el lesz takarítva
class Leaf {
int i = 0;
Leaf increment() { i++; return this; }
z Segítség: finalize()
}
/* ... */ x.increment().increment(); – Takarítás előtt hívódik
– Nem destruktor!
Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák
2002. 03. 18. 76 2002. 03. 18. 77
© Ferenc Rudolf © Ferenc Rudolf

Szemétgyűjtés Szemétgyűjtés (folyt.)


z Fontos: a szemétgyűjtés végrehajtása nem z Kézzel is lehet indítványozni a futtatását:
szavatolt! System.gc();
System.runFinalization();

– Pl. még sok szabad memória van és elkerülhető a z Működés: több algoritmus szerint, de ez mind
fölösleges lassítása a programnak
rejtve marad:
– Kiszámíthatatlan, hogy mikor fog hívódni
– Pl. referenciaszámlálás
z Tehát, ez nem azonos a destruktor szerepével:
– Fontos, hogy mindig végrehajtandó feladatot ne
rakjunk a finalize()-ba (pl. fájl lezárás)

Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák


2002. 03. 18. 78 2002. 03. 18. 79
© Ferenc Rudolf © Ferenc Rudolf
Mezők inicializálása Mezők inicializálása (folyt.)
z Lokális változókat kötelező explicite z Példa
inicializálni class A {

z Adattagok inicializálására a következő módok


char c; // default
int i = 1; // definíció helyén
vannak: float f = init(); // függvénnyel
B a = new B(); // nem primitív
– Nincs explicite inicializálva – a primitívek 0 C c1;
C c2;
default értéket kapnak, a nem primitívek pedig // instance initialization clause:

null értéket (nincs memória foglalva)


{
c1 = new C(1);

– Definíció helyén
c2 = new C(2);
}
– Instance initialization clause A() { i = 2; } // default konstruktorral
// előbb 1, utána 2
– Konstruktorral: az előző kettő után hívódik csak }
A(int i) { this.i = i; }

Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák


2002. 03. 18. 80 2002. 03. 18. 81
© Ferenc Rudolf © Ferenc Rudolf

Tömbök Array Tömbök (folyt.)


z Tömb: névvel ellátott (egyetlen azonosítóval kezelt) z Tömb objektumok mérete: a1.length;
azonos típusú elemek (primitív vagy nem) sorozata: – csak olvasható, pl.
– Java-ban a tömb is egy objektum for (int i = 0; i < a1.length; i++)
– Deklarálás: int[] a1; vagy int a1[]; System.out.println(a1[i]);

– Méretet nem lehet megadni (akkor foglalódik le amikor z Biztonságos, mert túlindexelésnél nincs elszállás,
inicializáljuk) hanem kivétel lesz dobva
– Deklaráláskor csak egy referenciánk van a tömbre
z Ezek az extra funkcionalitások lassíthatják a
z Inicializálás (helyfoglalás) programot
– Inicializáló kifejezéssel: int[] a1 = { 1, 3, 4 };
z Inicializálás futás közben (méret nem ismert előre):
– Tömb másolással: int[] a2 = a1;
int i;
– Lefoglalt terület elemei 0-ra lesznek inicializálva i = /* ??? */;
int[] a1 = new int[i];

Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák


2002. 03. 18. 82 2002. 03. 18. 83
© Ferenc Rudolf © Ferenc Rudolf
Tömbök (folyt.)
z Nem primitív típusok tömbjei
– Csak referenciák tárolódnak
– Ezért az elemeket new-val kell lefoglalni
– Lehet bonyolult is az inicializálás:
Integer[] a1 = new Integer[3];
Implementáció elrejtése
a1[0] = new Integer(500);
Integer[] a2 = new Integer[] {
new Integer(1),
new Integer(2),
}; // new Integer[] elhagyható

z Többdimenziós tömbök:
– Hasonló az egydim.-hoz int[][] a2d = { {1,2,3}, {4,5,6} };

– Egyéb kombinációk: int[][][] a3d = new int[2][3][5];

Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák


2002. 03. 18. 84 2002. 03. 18. 85
© Ferenc Rudolf © Ferenc Rudolf

Elrejtés Elrejtés (folyt.)


z OO Tervezés: Különválasztani a változó z Elérések (növekvő szigorúság szerint):
dolgokat a nem változóktól – public, protected, friendly (nincs rá kulcsszó),
z Osztálykönyvtárak esetén különösen fontos: private
– Kliens kódja változatlan maradhat ha a könyvtár z Könyvtár (library) egységeinek elérését hogyan
változik is szabályozhatjuk?
– Kívülről elérhető részek ne változzanak, de
– Csomagok: package
– szabadon változhat a rejtett rész
– Elérés vezérlésének teljes értelme a csomagok
z Az ilyen fajta elrejtést szolgálják az elérést használatával jön elő
vezérlők (access specifiers):
– import-tal érünk el
– Láthatóság import java.util.*;
egy csomagot
Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák
2002. 03. 18. 86 2002. 03. 18. 87
© Ferenc Rudolf © Ferenc Rudolf
Csomagok Packages Java forráskódok
z import használata után java.util.ArrayList z Egy-egy Java program írásakor fordítási egységeket
helyett elég ArrayList-et írni készítünk (compilation-, translation unit) → .class fájl
z Lehet csak egy osztályra is kiadni: z Minden fordítási egységben lehet egy publikus osztály
import java.util.ArrayList; (public class ...) amelynek a neve meg kell
z Miért van rá szükség? egyezzen a fájl nevével (.java nélkül)
– nevek ütközésének elkerülésére z Lehetnek a fájlban egyéb (rejtett) osztályok is
z Ha nincs megadva (mint az eddigi példákban), akkor z .java fájl fordításával .class fájlok jönnek létre (egy-
minden új osztály egy “default package” csomagba egy minden egyes osztályhoz)
kerül z Java-ban nincs linker (.class fájlok önállóak)
z Ha nagy rendszert írunk, célszerű csomagokat – Lehet őket tömöríteni .jar fájlba (~ zip)
használni – .class fájlokat az interpreter értelmezi és hajtja őket végre
egyenként
Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák
2002. 03. 18. 88 2002. 03. 18. 89
© Ferenc Rudolf © Ferenc Rudolf

Csomagok megadása Csomagok nevei


z A csomag (package) nem más mint .class fájlok z Egyedi csomagnevek:
halmaza: – A teljes elérési útvonal bele van kódolva
– Melyik csomagba tartoznak? Forrásfájl első nem-komment – Konvenció szerint az internet domain név is benne van
sorába be kell írni: package mypackage; fordított sorrendben (com.sun.java.*)
– Ez azt jelenti, hogy a benne levő publikus osztály a – Nem kötelező a használata, de javasolt
megadott csomaghoz fog tartozni z Hogy a Java interpreter megtalálja, be kell állítani a
z Használata: mint már láttuk: CLASSPATH környezeti változót, aminek
mypackage.MyClass m; import mypackage.*; tartalmaznia kell minden olyan elérési útvonalat, ahol
MyClass m;
a Java csomagok elhelyezkednek a lokális rendszeren
z Az egy csomagba tartozó osztályok egy lokális – .jar fájlok esetében az is hozzá van adva (nem csak a
könyvtárba kerülnek könyvtára)
Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák
2002. 03. 18. 90 2002. 03. 18. 91
© Ferenc Rudolf © Ferenc Rudolf
Csomagok nevei (folyt.) Elérés vezérlése Access specifiers
 Név ütközések:  Osztály tagjai elé oda kell írni az elérés vezérlés
– Ha két import valami.* van és mindkettőben van kulcsszavát (csak arra az egy tagra lesz
azonos nevű osztály amit használni szeretnénk érvényes):
(amíg nem használunk ilyent addig nincs baj) – public, protected, private

– Explicite ki kell írni a csomag nevét az osztály elé – Ha nincs kiírva, akkor alapértelmezett a “friendly”
 Friendly (vagy “package access”):
– Csomagon belül minden osztály eléri (public)
– Csomagon kívül nem elérhető (private)
– Szorosan kapcsolódó osztályokat lehet így
csoportosítani (egymást használják)
Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák
2002. 03. 18. 92 2002. 03. 18. 93
© Ferenc Rudolf © Ferenc Rudolf

Elérés vezérlése (folyt.) Elérés vezérlése (folyt.)


 Ha nem friendly-t használunk:  public
– Minden osztálynak saját magának kell megadnia mi – Mindenki elérheti
az ami elérhető benne és kinek – Az osztály interfésze
– Ami nem kell másnak az mindig legyen private  private
– Az interfész rész legyen public – Senki nem érheti el, kivéve az adott osztály saját
– Leszármazottaknak legyen protected metódusait
 Megjegyzés: konstruktornak is megadhatjuk az – Főleg rejtett implementációra és adattagokra
elérését: alkalmazzák
– Direkt létrehozását lehet így korlátozni

Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák


2002. 03. 18. 94 2002. 03. 18. 95
© Ferenc Rudolf © Ferenc Rudolf
Elérés vezérlése (folyt.) Osztály elérés vezérlése
z protected z Könyvtáron (csomagon) belül az egyes osztályok
– Az adott osztály és annak származtatott osztályai elérését is lehet szabályozni
elérik, a többieknek nem elérhető  public osztály: kliens elérheti az osztályt
class Cookie { public class Widget { ...
protected void bite() {}
} z .java fájlonként csak egy publikus osztály lehet (de
class Chocolate extends Cookie { nem kötelező), az amelyik a fájl nevét viseli (kis-nagy
}
public void eat() { bite(); /* ... */}
betű is számít!)
/* ... */
z Publikuson kívül lehet még friendly, ha nincs semmi
Chocolate x = new Chocolate(); kiírva (private, protected csak inner class lehet):
x.bite(); // nem elérhető
x.eat(); // ok
– csomagon kívül nem hozható létre belőle objektum

Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák


2002. 03. 18. 96 2002. 03. 18. 97
© Ferenc Rudolf © Ferenc Rudolf

Újrafelhasználhatóság Reuse
 OOP programozás (és a Java nyelv) egyik
legvonzóbb tulajdonsága a kód hatékony
újrafelhasználásának lehetősége
Osztályok újrafelhasználása  De: újrafelhasználás ≠ egyszerű másolás
 Régebbi nyelvekben próbálkozás a
procedurális programozás
 Ennél jobb az osztály újrafelhasználása:
– Ne kelljen minden osztályt újból megírni, hanem
építőkockaként felhasználni a meglévőket
Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák
2002. 03. 18. 98 2002. 03. 18. 99
© Ferenc Rudolf © Ferenc Rudolf
Újrafelhasználhatóság (folyt.) Kompozíció Composition
A lényeg: mindezt úgy csinálni, hogy a  Kompozíció: összetétel, Aggregation
meglévő kódot (osztályokat) ne módosítsuk aggregáció, rész-egész kapcsolat
 Kompozíció: új osztályban meglévő  Egy osztály egy mezője (adattagja) másik
osztályokból készítünk objektumot osztály típusú (vagy primitív) az egy
 Öröklődés: kompozíció (összetett osztály):
– Új osztály létrehozása egy meglévő „altípusaként” – S inicializálva lesz null-ra
– Új funkcionalitás hozzáadásával – i 0-ra class A {
private String s;

 Ezek a módszerek az alap-építőelemi az int i;


}

OOP-nek
Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák
2002. 03. 18. 100 2002. 03. 18. 101
© Ferenc Rudolf © Ferenc Rudolf

Öröklődés Inheritance
Öröklődés (folyt.)
class Cleanser { /* ... */
z Minden OOP nyelvnek szerves része public void scrub() {} Detergent d;
public void apply() {}
z Java-ban minden új osztály implicite örököltetve van } d.apply();
d.scrub();
az Object-ből (direkt, vagy indirekt módon) public class Detergent extends Cleanser { d.foam();
// metódus módosítása (felüldefiniálás):
z Új osztály származtatása meglévőből: „olyan mint a public void scrub() { super.scrub(); /* ... */ }
// új metódus hozzáadása:
régi”: public void foam() {}
}
– Bővíti azt (extends kulcsszó)
z Az ősnek van néhány interfész metódusa (public)
– A származtatott az ős minden adatát és metódusát
„megkapja” – örökli az őstől z A származtatott ezeket mind tartalmazza, tehát az interfész újra
fel van használva
z Az öröklődés: z scrub() felüldefiniálja az ősét (specializálja a viselkedését):
– Származtatott (al-, gyermek-osztály) szempontjából – Overriding
specializálás – Őst is hívhatja super segítségével (egyébként rekurzív lenne)
– Ős szempontjából (alap-, szülő-osztály) általánosítás z foam() új metódus, bővítése az ősnek
Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák
2002. 03. 18. 102 2002. 03. 18. 103
© Ferenc Rudolf © Ferenc Rudolf
Ős osztály inicializálása Ős inicializálása (folyt.)
z Öröklődés nem csak az osztály interfészét „másolja”
 Ha nem default az ős konstruktor, hanem
z A származtatottból létrejövő objektum vannak argumentumai, akkor kézzel kell
rész-objektuma lesz az ősnek!
meghívni a super segítségével:
– Ős objektum ugyanolyan marad + körül van véve a
class Game {
származtatott új elemeivel ős Game(int i) { /* ... */ }

z Az őst is inicializálni kell: származtatott


}

– A származtatott konstruktorában hívni az ős konstruktorát class BoardGame extends Game {


BoardGame(int i) { super(i); /* ... */ }
(hiszen ő tudja hogyan kell) }

– Java-ban a default ős-konstruktor automatikusan public class Chess extends BoardGame {


meghívódik (mindig a legősibb előbb és így lefele) Chess() { super(64); /* ... */ }
}

Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák


2002. 03. 18. 104 2002. 03. 18. 105
© Ferenc Rudolf © Ferenc Rudolf

Kompozíció és öröklődés Kompozíció vagy öröklődés?


z Mindkettőnél az új osztály rész-objektumokat tárol
 Sűrűn használatos a kettő együtt z Mi a különbség és mikor melyiket használjuk?
 Ős osztály inicializálására a fordító kényszerít, z Kompozíció: egy meglévő osztály funkcionalitását
de az adattag-objektumokra nekünk kell felhasználjuk, de az interfészét nem (a kliens csak az új
figyelni! osztály interfészét látja):
– Ezért a beágyazott objektum általában private típusú
 Kombinált használat esetén „rendes” takarítás – Lehet public is (biztonságos, mert úgyis csak az interfésze
szükséges: elérhető) ha a kliensnek fontos tudnia hogy miből állt össze az
objektum
– Java-ban nincs destruktor, ehelyett van a garbage
z Öröklődés: egy meglévő osztály speciális változatát
collector (de ez kiszámíthatatlan) készítjük el (specializálunk) - nem mindig ez a jó!
z Pl. melyik mi: Autó + Kerekek vagy Jármű + Autó
Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák
2002. 03. 18. 106 2002. 03. 18. 107
© Ferenc Rudolf © Ferenc Rudolf
Kompozíció vagy öröklődés? (folyt.) Ősre konvertálás Upcasting
z Sokszor hajlamosak vagyunk arra, hogy mindenhol az z Mivel a származtatott az ős osztály egy
öröklődést használjuk, mert az OOP tulajdonság! „új típusa”, logikus hogy mindenhol ahol az ős használható,
z Nem mindig ez a jó ott a származtatott is használható
z Ez azt jelenti, hogy ahol ős típusú objektumot vár a program,
z Kezdetben induljunk ki mindig a kompozícióból, majd
ott a származtatott egy implicit konverzión megy át az ősbe.
ha kiderül hogy az új osztály mégis egy speciális típusa
a másiknak, akkor származtassunk z Ezt már korábban is láttuk. Példa:
class Instrument {
z Származtatásnak elsődlegesen akkor van }
public void play () {}

létjogosultsága, ha ősre való konvertálás is szükséges class Tuner {


static void tune(Instrument i) { /* ... */ i.play(); }

lesz (lásd később) }


class Piano extends Instrument {
public void play() {}
z Túl sok származtatás és mély osztályhierarchiák }

nehezen karbantartható kódot eredményezhetnek! /* ... */


Piano p = new Piano();
Tuner.tune(p); // upcast

Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák


2002. 03. 18. 108 2002. 03. 18. 109
© Ferenc Rudolf © Ferenc Rudolf

A final kulcsszó Végső adatok Final data


 Több jelentése van, de a lényeg: az a mi el van z Konstans adat létrehozása hasznos lehet:
– Ha már fordításkor eldönthető (konstans propagálás)
látva vele az nem változhat (végső)
– Futás közben inicializálva, de onnantól kezdve konstans
 Miért akarjuk korlátozni? z Konstans primitív adat létrehozására egyszerűen ki
– Tervezési megfontolásból kell írni a definíció elé (lehet static is, ebben az
– Hatékonyság miatt esetben a címe is fix)
z Nem primitív típusra használva nem az objektum lesz
 Három helyen használható: konstans hanem a referencia:
– Adat – Inicializálás után másra már nem mutathat, de maga az
objektum megváltozhat
– Metódus
– Java-ban az objektumok nem tehetők konstanssá, csak a
– Osztály viselkedésükkel lehet azt „szimulálni”
Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák
2002. 03. 18. 110 2002. 03. 18. 111
© Ferenc Rudolf © Ferenc Rudolf
Végső adatok (folyt.) Végső metódusok
 Üres végsők (blank final) z Két dologra használjuk:
– A konstruktorban inicializálni kell – Meggátoljuk a származtatott osztályokat abban hogy
felüldefiniálják és megváltoztassák a viselkedését
 Metódus paramétere is lehet final (csak
– Hatékonyság végett: a fordító számára megmondjuk, hogy a
olvasható) metódus hívását inline hívásokká alakíthatja: hívás helyett a
 Pl.: hívott függvény kódja „be lesz másolva”
class FinalData {
final int i = 9; z Minden private metódus implicite final lesz:
static final int VAL = 99; // csak egy van belőle
final Value v2 = new Value(); – Nem lehet felüldefiniálni mert privát
final int j = (int)(Math.random()*20); // futás közben!
final int k; // blank – Ha mégis megpróbáljuk, akkor új metódust kapunk
FinalData(int i) { k = i; }
void f(final int i) { i++; } // hiba: i nem változhat
}

Rendszerfejlesztés előadás fóliák Rendszerfejlesztés előadás fóliák


2002. 03. 18. 112 2002. 03. 18. 113
© Ferenc Rudolf © Ferenc Rudolf

Végső osztályok Final classes


 Osztályra is mondhatjuk hogy legyen végső:
final class ...
 Azt jelenti, hogy belőle nem lehet új
osztályokat származtatni
 Biztonsági vagy hatékonysági megfontolásból
 Minden metódus is implicite végső lesz (nem
lehet származtatni ezért felüldefiniálni sem):
– Ugyanazok a hatékonysági megfontolások
érvényesek lesznek mintha ki lenne írva
Rendszerfejlesztés előadás fóliák
2002. 03. 18. 114
© Ferenc Rudolf

You might also like