OOP konstruktor, Klassid ja Objektid

Kuigi Python toetab mitut programmeerimise paradigmat, on neist kõige enam rõhutatud just objekt-orienteeritud programmeerimise (OOP) paradigmat. Võib öelda, et OOP-i kasutamine tähendab programmikoodi teatud kindlal viisil organiseerimist. Selle lähenemise juures leiavad kasutamist mõisted nagu klass ja objekt, teemasse enam süvenedes ka kapseldamine, polümorfism, pärimine, modulaarsus, abstraktsioon. Viimaseid nimetatakse OOP tehnikateks.

OOP-i juures on tavaline rääkida klasside kirjeldamisest ja nende kirjelduste alusel objektide loomisest ning manipuleerimisest. Objektidega manipuleerimine leiab aset klassi kirjelduses antud meetodite (põhimõtteliselt funktsioonide) alusel. Klassi kirjelduse alusel luuakse objekte, millistel on ühesugused omadused ja tegevused (meetodid).

Vaata alljärgnevast videost, mis on objekt-orienteeritud programmeerimine, mis on klassid ja objektid ning vaata näiteid erinevatest OOP tehnikatest.

Oletame, et tahame koostada programmi, mis simuleerib tavalise koera käitumist. Mida oskab tavaline koer? Eeldame, et iga tavaline koer oskab haukuda, veereda ja tervitada oma omanikku. Samal ajal, tavaline koer ei oska rääkida (üsna loogiline, eks). Selleks, et koostada sellist programmi, defineerime klassi Dog (mille alusel saab hiljem programmis just samade, klassis kirjeldatud, oskustega koeri luua):

# Defining the Dog class
class Dog:
    # Code

On teada, et iga tavaline koer oskab haukuda, veereda ja tervitada oma omanikku:

class Dog:

    def bark(self):
        print("Bark!")

    def roll(self):
        print("*rolling*")

    def greet(self):
        print("Hey, master")

    def speak(self):
        print("I cannot!")

Klassi Dog kirjelduses on neli funktsiooni, mida OOP lähenemise puhul nimetatakse klassi meetoditeks. Pöörame tähelepanu, et iga meetod kasutab parameetrit nimega self, millest räägime natuke hiljem. Meie klass kirjeldab tavalise koera käitumist - on meetodid, mis paiknevad klassi sees ning kirjeldavad iga tavalise koera oskusi. See ongi abstraktsioon. S.t. klass (meil Dog) on kui piparkoogivorm ning objektid kui antud vormiga tehtud piparkoogid. Nüüd, kui meil on klassis tavalise koera käitumine kirjeldatud, saab luua objekti - klassi isendi (instance). Loome kaks koera: Clyde ja Jenkins.

class Dog:

    def bark(self):
        print("Woof!")

    def roll(self):
        print("*rolling*")

    def greet(self):
        print("Greetings, master")

    def speak(self):
        print("I cannot!")

# Creating the Dog class instance and saving it to the variable <clyde>
clyde = Dog()
clyde.bark()   # --> Woof!
clyde.roll()   # --> *rolling*
clyde.greet()  # --> Greetings, master
clyde.speak()  # --> I cannot!

# Creating another Dog instance
jenkins = Dog()
jenkins.bark()  # --> Woof!
jenkins.roll()  # --> *rolling*
# .. And other methods
# .. Infinite objects can be created this way, all implementing the same methods defined in our class

Tuleb välja, et vaatamata sellele, et clyde ja jenkins on erinevad koerad, oskavad mõlemad teha asju, mida meie definitsiooni järgi oskab iga tavaline koer. Nii, nagu reaalses elus umbes ongi - näiteks iga koer oskab haukuda sõltumata tema liigist.

Konstruktor

Klassil võib olla ka konstruktor, mis kujutab endast erilist, fikseeritud nimega, meetodit klassi sees. See fikseeritud nimi on järgmine: __init__().

class Dog:
    # Class constructor
    def __init__(self):
        # Code here

Konstruktor ja selle sees olev kood käivitatakse vaid isendi (objekti) loomisel. Näiteks tahame, et klassi Dog isendi loomisel oleks igale klassi objektile antud oma nimi. Teeme nii, et meie konstruktor võtaks sisse teise argumendi, name, ja salvestaks selle uue muutuja self.name sisse.

class Dog:

    # Class constructor
    def __init__(self, name):
        self.name = name

    def bark(self):
        print("Woof!")

    def roll(self):
        print("*rolling*")

    def greet(self):
        print("Greetings, master")

    def speak(self):
        print("I cannot!")

    # Returns the current dog's name
    def get_name(self):
        return self.name

    # Returns the reference to the current dog instance
    def get_this_dog(self):
        return self

# Creating the Dog class instance and giving it name
dog1 = Dog("Clyde")
dog1.bark()  # --> Woof!
dog1.roll()  # --> *rolling*
dog1.greet() # --> Greetings, master
dog1.speak() # --> I cannot!
print(dog1.get_name())  # --> Clyde
print(dog1.get_this_dog())  # --> (Example) <__main__.Dog object at 0x0119D710>
print(dog1)  # --> (Example) <__main__.Dog object at 0x0119D710>

# Creating some more Dog instances
dog2 = Dog("Jenkins")
dog2.bark() # --> Woof!
dog2.roll()  # --> *rolling*
dog2.greet() # --> Greetings, master
dog2.speak() # --> I cannot!
print(dog2.get_name())  # --> Jenkins
print(dog2.get_this_dog())  # --> (Example) <__main__.Dog object at 0x0143D710>
print(dog2)  # --> (Example) <__main__.Dog object at 0x0143D710>

dog3 = Dog("Robbie")
print(dog3.get_name())  # --> Robbie
print(dog3.get_this_dog())  # --> (Example) <__main__.Dog object at 0x01CED710>

Seega, vaatamata sellele, et iga koer oskab haukuda, veereda ja tervitada, on kõik koerad erinevad objektid, nagu reaalelus ongi. Muutuja self iseloomustab antud juhul just seda konkreetset koera, kelle objekt on loodud, self on viide sellele objektile. Seda on näha, kui printida välja meie Dog klassil olev meetod get_this_dog või kui lihtsalt üritada välja printida objekti ennast, nagu on üleval tehtud. Nagu näha, on erinevatel objektidel erinev viide, mis tähendabki, et näiteks Jenkins ja Clyde ei ole üks ja sama koer, vaid kaks erinevat. Kui meie loome muutuja, pannes self juurde prefiksina, nagu tegime self.name korral, siis saame nn isendimuutuja, mis on nähtav igale klassi meetodile:

class Dog:

    # Class constructor
    def __init__(self, name):
        # Instance variable
        self.name = name

    def bark(self):
        print(self.name + " says: Woof!")

    def roll(self):
        print("*rolling*")

    def greet(self):
        print(self.name + " greets you!")

    def speak(self):
        print("I cannot")

    # Returns the current dog's name
    def get_name(self):
        return self.name

    # Returns the current dog instance
    def get_this_dog(self):
        return self

dog1 = Dog("Clyde")
dog1.bark()  # --> Clyde says: Woof!
dog1.greet()  # --> Clyde greets you!
print(dog1.get_name())  # --> Clyde
print(dog1)  # --> Reference

dog2 = Dog("Jenkins")
dog2.bark() # --> Jenkins says: Woof!
dog2.greet() # --> Jenkins greets you!
print(dog2.get_name())  # --> Jenkins

Kuid peab siiski silmas pidama, et konstruktor ei pea ilmtingimata olema nii kerge ja lühike. On teada, et koerale võib õpetaja erinevaid trikke, oletame nüüd, et meie koera objektil on trikkid mida koer oskab teha kujul list sõnedest, kus iga sõne on trikk nt "jump", "lay", "catch ball", "flip". Kuidas seda lisada koera objektile, arvestades, et koerad on ju erinevad? Samuti kuidas lisada trikki koerale või just eemaldada triki, kui koer enam ei oska seda teha :)?

Seega, lisame uue muutuja koerale, mis on algselt tühi list, sest eks koer alguses ju ühtegi trikki ei oska teha.

class Dog:

    # Class constructor
    def __init__(self, name):
        # Instance variable
        self.name = name

        self.tricks = [] # initially the dog knows no tricks

    ... rest of code

Nüüd peame lisama paar funktsiooni, mis lisaks uue trikki koerale, kui antud koer õpis uue triki ära. Ja samuti võiks igaks juhuks teha ka funktsiooni, mis eemaldab triki, kui koer seda enam ei oska. Samuti lisame ikka kontrolli, et trikid ei korduks.

class Dog:

    # Class constructor
    def __init__(self, name):
        # Instance variable
        self.name = name

        self.tricks = [] # initially the dog knows no tricks

    # Note that first param here is the self,
    # it is used to access instance variables
    # Add trick
    def add_trick(self, trick):
        if trick not in self.tricks:
            self.tricks.append(trick)

    # Remove trick
    def remove_trick(self, trick):
        if trick in self.tricks:
            self.tricks.remove(trick) # remove by value

    ... rest of code

Nüüd, tänu sellele, et me lisasime uue listi muutujana ja paar uut funktsiooni, et selle listiga opereerida, saame panna kõik kokku ja luua paar koera ning õpetada nendele paar trikki ning uurida, mis trikke nad oskavad. Aga enne seda võiks teha funktsiooni, mis tagastab meile trikid loetaval kujul

class Dog:

    ... rest of code

    # Display tricks
    def display_tricks(self):
        print(f"{self.name} knows these tricks:")
        for trick in self.tricks:
            print(trick) # print every trick on a new line
        print("\n")

    ... rest of code

Kõik koos:

class Dog:

    # Class constructor
    def __init__(self, name):
        # Instance variable
        self.name = name

        self.tricks = [] # initially the dog knows no tricks

    # Add trick
    def add_trick(self, trick):
        if trick not in self.tricks:
            self.tricks.append(trick)

    # Remove trick
    def remove_trick(self, trick):
        if trick in self.tricks:
            self.tricks.remove(trick) # remove by value

    # Display tricks
    def display_tricks(self):
        print(f"{self.name} knows these tricks:")
        for trick in self.tricks:
            print(trick) # print every trick on a new line
        print("\n")

    def bark(self):
        print(self.name + " says: Woof!")

    def roll(self):
        print("*rolling*")

    def greet(self):
        print(self.name + " greets you!")

    def speak(self):
        print("I cannot")

    # Returns the current dog's name
    def get_name(self):
        return self.name

    # Returns the current dog instance
    def get_this_dog(self):
        return self

dog1 = Dog("Clyde")
dog1.bark()  # --> Clyde says: Woof!
dog1.greet()  # --> Clyde greets you!
print(dog1.get_name())  # --> Clyde
print(dog1)  # --> Reference

dog1.add_trick("flip")
dog1.add_trick("dance")
dog1.display_tricks()
# Clyde knows these tricks:
# flip
# dance

dog1.add_trick("flip") # no change
dog1.display_tricks()
# Clyde knows these tricks:
# flip
# dance

dog2 = Dog("Jenkins")
dog2.bark() # --> Jenkins says: Woof!
dog2.greet() # --> Jenkins greets you!
print(dog2.get_name())  # --> Jenkins

dog2.add_trick("jump")
dog2.add_trick("crawl")
dog2.add_trick("sing")

dog2.remove_trick("sing")
dog2.display_tricks()
# Jenkins knows these tricks:
# jump
# crawl

Nüüd asume koera trikide võistlusel. Iga koer, oskab kindlad trikid ja samuti on erineval koeraomanikul erinev programm võistluseks. Oletame nüüd kaks olukorda: 1) Kõikidele koeraomanikele jagatakse info, kus on pythoni plaanis dict, kus võti on koera nimi ning väärtus on trikid, mida koer võiks teha antud võistluse voorus. 2) Igal koeraomanikul on oma personaalne programm oma koerale. Nüüd lisame tingimuse, et see võib olla 1) või 2) variant, ning mõlemat peab arvestama. Peame muutma meie konstruktori ja tegema seda mõnevõrra targemaks.

class Dog:

    # Class constructor
    def __init__(self, name, tricks, global_program, individual_program):
        # Instance variable
        self.name = name

        self.tricks = tricks # assume that every dog knows some tricks

        self.tricks_for_program = []

        # add only those tricks to program tricks that the dog actually knows
        # add tricks to program that are for this given dog
        if self.name in global_program:
            tricks_for_this_dog = global_program[self.name]
            for trick in tricks_for_this_dog:
                if trick in self.tricks and trick not in self.tricks_for_program:
                    self.tricks_for_program.append(trick)

        # add tricks from personal program
        for trick in individual_program:
            if trick in self.tricks and trick not in self.tricks_for_program:
                self.tricks_for_program.append(trick)

    ...rest of code

Ülalolevalt näitelt saab aru, et tegelikult võib klassi konstruktoris teha palju erinevaid tegusid. Konstruktor ei ole ilmtingimata selleks, et klassisisestele muutujatele anda väärtusi, konstruktoris võib välja kutsuda nii funktsioone kui ka näiteks luua veel objekte jpm.