Objekt-orienteeritud programmeerimine

object-oriented programming (OOP)

Objekt

  • Objekt kirjeldab ära konkreetse loogilise kogumi
    • näiteks õues olev punane auto on üks objekt
    • selle taga olev roheline auto on teine objekt jne
  • Tavaliselt mõtleme me arvust kui ühest väärtusest (nt 7)
  • Objekt koosneb tavaliselt mitmest väärtusest
    • värv, mark, mudel, pikkus, registrimass jne

Klass

  • Klass kirjeldab ära struktuuri
    • näiteks autol on värv, pikkus jne
  • Klass (üldiselt) ei sisalda andmeid
  • Klass on andmetüüp
  • Samatüüpi andmed pärinevad kõik ühest klassist
    • punane auto on auto, roheline auto on auto jne
  • Kuigi meil on maailmas mitu autot (objekti), siis meil on üks klass auto

OOP

  • Objekt-orienteeritud programmeerimine (OOP) on programmeerimise paradigma, mis kasutab objekte

  • Python on objekti-orienteeritud programmeerimiskeel (OOP)

  • Pythonis kõik asjad on objektid

OOP tehnikad

  • Kapseldamine (encapsulation)
    • funktsionaalsus peidetakse
  • Modulaarsus (modularity)
    • programm jagatakse iseseisvateks tükkideks
  • Polümorfism (polymorphism)
    • alamklass saab meetodeid üle kirjutada
  • Pärimine (inheritance)
    • alamklass pärib omadused ja meetodid

Sõne

  • Sõne on objekt
  • Kui loote uue sõne, siis tegelikult luuakse uus objekt, mille tüüp on str.
  • Sõne "funktsioone" kutsutakse meetoditeks
    • ehk siis klassis kirjeldatud funktsioonid on meetodid

Sõne

s = "Hello"
print(type(s))  # <class 'str'>
print(id(s))  # 30773472
print(id(s.replace("H", "h")))  # 61507648

  • Loome sõne s ja küsime selle tüübi. Tüüp on str klass
  • id tagastab objekti kohta unikaalse arvu. Kui id on erinev, siis on ka objekt erinev (st mälus erinevas kohas)
  • replace teeb uue sõne, seda näeme ka id-ga
  • replace on sõne meetod ehk funktsioon, mida saab välja kutsuda objektil

List

a = [1, 2, 3]
b = [1, 2, 3]
c = b
print(id(a))   # 44058024
a.append(4)
print(id(a))   # 44058024 still the same
print(id(b))   # 44059184
print(id(c))   # 44059184 - same as b
b.pop()
print(id(b))   # 44059184 - still the same
print(id(c))   # 44059184 - and same
  • Listi muutes id ei muutu
  • c ja b viitavad samale listile

Veel objekte

print(type(1))     # <class 'int'>
print(type(True))  # <class 'bool'>
print(type(1.2))   # <class 'float'>
print(type(None))  # <class 'NoneType'>
print(type(len))   # <class 'builtin_function_or_method'>
print(type(type))  # <class 'type'>

Klass kui andmetüüp

  • Iga klass on andmetüüp
  • Näiteks on Pythonis klass str
  • Iga konkreetne sõne, näiteks "tere", on selle klassi objekt (ehk isend)
  • Ühest klassist saab luua lõpmata palju objekte
  • Objekti kohta öeldakse ka isend ja instants
    • Üldiselt mõeldakse "objekt", "isend", "instants" terminitega samu asju
    • Erinevates allikates võivad neil erinevused olla

Teeme oma klassi

class Student:
    pass

s = Student()
print(type(s))  # <class '__main__.Student'>
print(id(s))    # 12448112

t = Student()
print(type(t))  # <class '__main__.Student'>
print(id(t))    # 12423408
  • Klass Student. pass on tühi korraldus
  • Loome kaks isendit - nende id on erinev (nad on mälus erinevas kohas)

Objektide võrdlemine

  • Objektide võrdlemine == võrdlusega kontrollib vaikimisi seda, kas nad viitavad samale objektile
  • Seda, mida täpselt kontrollitakse, saab üle kirjutada
    • Näiteks sõne puhul kontrollitakse seda, kas sisu (st sümbolid) on samad jne
s1 = Student()
s2 = Student()
s3 = s1

print(s1 == s2)   # False
print(s1 == s3)   # True
print(s2 == s3)   # False

Meetod

  • Klassis sisalduvaid funktsioone nimetatakse meetoditeks
class Student:
    """Student class."""

    def hello(self):  # method, "self" is a special parameter
        """Method (function) which just prints out "Hello!"."""
        print("Hello!")


s = Student()   # s is an object of class Student
s.hello()       # no "self" argument

self

  • Kõik objekti meetodid sisaldavad esimest parameetrit self
    • selle parameetri nimi võib ka midagi muud olla; kasutage self
  • self viitab isendile
  • Eelmises näites oli väljakutse s.hello()
    • kui hello() meetod käima pannakse, antakse sellele s kaasa
  • Meetodi jaoks vajalike väärtuste jaoks lisatakse need peale self parameetrit

self ja parameetrid

class Student:
    def greet_friend(self, friend_name):
        print(f"Hello, {friend_name}")

s = Student()
s.greet_friend("Kaia")
  • Meetodi kirjelduses esimesel kohal on self, teisel kohal friend_name.
  • Kui kutsume välja greet_friend meetodit, siis esimesena kaasa antud argument läheb teise parameetrisse jne.

Konstruktor

  • Objekti loomisel pannakse käima eriline meetod ehk konstruktor
  • Meetod kirjeldatakse: __init__(self)
  • See meetod pannakse käima üks kord objekti loomisel
  • Eelnevas näites s = Student() kutsub välja konstruktori
  • Konstruktori kirjeldamine ei ole kohustuslik
  • Konstruktor peab tagastama None (eraldi return lauset ei kirjutata).

Konstruktor

  • Kirjeldatakse nagu tavaline meetod
  • Eraldi pole vaja välja kutsuda
class Student:
    def __init__(self):
        print("Initializing student..")

s = Student()  # Initializing student..
  • Student() kutsub Student klassi konstruktori välja.

Konstruktor, objekti muutujad

  • self viitab loodavale/loodud objektile
  • Konstruktorisse saab kaasa anda argumente (nagu tavaline funktsioon)
  • Esimene parameeter on alati self
  • Objekti muutujad on seotud ühe konkreetse objektiga (isendiga)
  • Objekti muutujaid väärtustatakse: self.name = ...
  • Tavaliselt luuakse konstruktoris vajalikud väljad ära
  • Objekti muutujaid saab teistes objekti meetodites kasutada

Konstruktor, objekti muutujad

class Student:
    def __init__(self, name, title):
        self.name = name
        self.title = title

ago = Student("Ago", "Sir")
print(ago.name)

leela = Student("Leela", "Captain")
print(leela.title)
  • Konstruktori parameeter self viitab loodavale objektile.
  • Parameetrid name ja title salvestatakse objekti muutujateks: self.name ja self.title

Objekti muutujad

class Shop:
    def __init__(self, name, age, products_file=None):
        self.products = []
        self.name = name
        self.established = 2020 - age
        if products_file is not None:
            # open the file and read products from it
            pass

    def inventory(self):
        print(f"Inventory for {self.name} (est. {self.established}:")
        for p in self.products:
            print("product: ..")

Klass (class)

  • Defineerib andmetüübi
  • Šabloon, mida saab hiljem kasutada, et luua konkreetseid objekte (isendeid)
class Point2D:
    """Point in (x, y) coordinate space."""
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def print_point(self):
        print(f"({self.x:.2f}, {self.y:.2f})")

Objekt (object)

  • Konkreetne isend, instants (instance)
  • Luuakse klassi kirjeldusest
  • Klassist võib luua lõpmata palju objekte
  • Samast klassist loodud objektid on sarnase struktuuriga (neil on samad meetodid ja muutujad)
  • Aga igal objektil on oma olek (muutujate väärtused)
p1 = Point2D(1.234, 0.23456)
p2 = Point2D(-1, 3)

p1.print_point()   # (1.23, 0.23)
p2.print_point()   # (-1.00, 3.00)

Klass ja objekt

class Point2D:
    """Point in (x, y) coordinate space)."""
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def print_point(self):
        print(f"({self.x:.2f}, {self.y:.2f})")


p1 = Point2D(1.234, 0.23456)
p2 = Point2D(-1, 3)

p1.print_point()   # (1.23, 0.23)
p2.print_point()   # (-1.00, 3.00)

Objektide võrdlemine

p3 = Point2D(3, 3)
p4 = Point2D(3, 3)
p5 = p4   # both refer to the same object in memory

print(p3 == p4)   # False, although same values, objects are different
print(p4 == p5)   # True

p3.x = 10
p3.print_point()   # (10.00, 3.00)

p4.x = 11
p4.print_point()   # (11.00, 3.00)
p5.print_point()   # (11.00, 3.00), p5 points to p4

Erilised meetodid

  • Python võimaldab klassis ära kirjeldada objekti käitumist erinevates olukordades
  • Näiteks __eq__ meetodit kasutatakse selleks, et võrrelda objekte == võrdlusega
  • __str__ meetodit kasutatakse, et saada objektist sõne kuju
  • Lisaks näiteks __add__(self, other) objektide liitmiseks jne
  • __lt__(self, other) väikem-kui võrdluseks
  • __len__(self) kui rakendatakse len(self)

Objektide võrdlus

  • Punkti puhul oleks mõistlik realiseerida objektide võrdlus väärtuste järgi
  • Kui kahel punktil on mõlemad koordinaadid samad, siis on ka objektid võrdsed.
class Point2D:
    # constructor and print_point() same as previously
    def __eq__(self, other):
        if isinstance(other, Point2D):
            return self.x == other.x and self.y == other.y
        return False
  • __eq__ meetod käivitatakse võrdlemisel

Objektide võrdlus

p6 = Point2D(1, 2)
p7 = Point2D(1, 2)

print(p6 == p7)   # True
print(p6 is p7)   # False

p8 = p6
print(p6 is p8)   # True

  • Võrdleme nüüd kahte objekti == võrdlusega. Tulemus on tõene, kuna käivitub __eq__ meetod, mis võrdleb sisu.
  • is võrdlus kontrollib, kas viidatakse samale objektile

Objekt sõnena

  • __str__(self) meetod võimaldab kirjeldada, mida objekti puhul sõnena tagastatakse
  • Näiteks printimisel kasutatakse seda
  • Samuti str(obj) puhul.
  • Meetod tagastab sõne
  • Vaikimisi print(p1) kuvab midagi sellist <__main__.Point2D object at 0x0050D5D0>.

Objekt sõnena

class Point2D:
    # ...
    def __str__(self):
        return f"({self.x:.2f}, {self.y:.2f})"
        
p1 = Point2D(1, 2)
print(p1)           # (1.00, 2.00)

Objekt sõnena

  • Pythonis on eriline meetod __repr__, mille eesmärk on tagastada üheselt mõistetav sõne.
    • Tihti tagastatakse sõne, millega saab objekti luua eval() funktsiooni abil
  • Kui __str__ pole defineeritud, kutsutakse __repr__ välja.
  • __str__ meetodi eesmärk on pigem olla informatiivne
  • Seega alati on mõistlik kirjeldada __repr__
class Point2D:
    def __repr__(self):
        return f"Point2D({self.x}, {self.y})"

Klassi muutujad

  • Klassi muutuja kirjeldatakse klassi sees väljaspool meetodeid
  • Klassi muutujal on üks väärtus läbi terve programmi
  • Sõltumata sellest, mitu objekti klassist luuakse, klassi muutujal on üks ühine väärtus
  • Üldiselt ei ole vaja kasutada

Klassi muutuja näide


class Doorbell:
    click_count = 0

    def __init__(self):
        self.click_count = 0

    def ring(self):
        print("Ringing..")
        self.click_count += 1
        Doorbell.click_count += 1
d1 = Doorbell()
d2 = Doorbell()

for _ in range(10): d1.ring()
for _ in range(4): d2.ring()
print(d1.click_count)         # 10
print(d2.click_count)         # 4
print(Doorbell.click_count)   # 14

Objekti väärtuste pärimine/muutmine

  • Kapseldamise eesmärk on peita objekti olek maailma eest ning lubada selle muutmine vaid läbi teatud meetodite.
  • Objekti väärtuste muutmiseks kasutatakse tavaliselt getter ja setter meetodeid
  • See annab võimaluse kontrollida, mida ja kuidas tagastatakse/muudetakse
class Student:
    def __init__(self, name):
        self.name = name

    def set_name(self, name):
        self.name = name

    def get_name(self):
        return self.name

@[1-8](`name` muutuja salvestatakse objekti muutujaks) @[1-8](`age` muutuja kasutatakse `self.established` väärtuse arvutamiseks. Funktsiooni lõpus kaotab `age` kehtivuse.) @[1-8](`products_file` on valikuline parameeter. Kui see on määratud, siis loetakse failist tooted.) @[1-8](`self.products` luuakse siin tühja järjendina. Seal hakatakse hoidma kaupade nimekirja.) @[1,9-13](`self` kaudu loodud muutujad on siin kättesaadavad: `name`, `established` ja `products`.)

@[1-8](Klassi kirjeldus) @[3](`__init__` on eriline meetod, mis käivitatakse objekti loomisel.) @[3](`self` on esimene parameeter ja viitab objektile.) @[4,5,8](`self.x` ja `self.y` on objekti (isendi) muutujad.) @[11-12](Loome kaks objekti `p1` ja `p2` erinevate väärtustega.) @[11-15](Mõlemal objektil on oma olek. Ühe oleku muutumine ei mõjuta teist.)

@[1-3](Loome 2 isendit `p3` ja `p4`. `p5 = p4` tähistab seda, et `p5` viitab samale isendile kui `p4` ) @[1-5](Kuigi `p3` ja `p4` väärtused on samad, siis `==` võrdlusel kontrollitakse vaikimisi, kas kaks isendit on samad (viitavad samale objektile)) @[1-6](Kuna `p5` ja `p4` viitavad samale isendile, siis siin on tulemus tõene.) @[1-9](Muudame `p3.x` väärtust, vaid sellel isendil muutub `x` väärtus.) @[1-14](Muudame `p4.x` väärtust, `x` väärtus muutub nii `p4` kui `p5` jaoks. Miks?)

@[1-7](Konstruktor ja `print_point` meetodid on näitest välja jäätud) @[4](Kirjeldame `__eq__` meetodi, mis käivitatakse objektide võrdlemisel) @[4-7](`isinstance` kontrollib, kas `other` on üldse `Point2D` tüüpi. Kui ei ole, siis ei saa punkt ka `other` objektiga võrdne olla) @[4-7](Kui `other` on `Point2D` tüüpi, siis võrreldakse `self` ja `other` objekti `x` ja `y` koordinaate.)