Source

Python Tutorial (Greek) / source / object_oriented_programming.rst

Full commit

Αντικειμενοστραφής Προγραμματισμός (Object-Oriented Programming)

Note

H ενότητα αυτή είναι περισσότερο πληροφοριακή. Το "ζουμί" ξεκινάει από την επόμενη.

O Αντικειμενοστραφής Προγραμματισμός, όπως λέει και το όνομα συνδέεται άμεσα με τις αντικείμενα και κατά συνέπεια, όπως θα δούμε και στη συνέχεια, και με τις κλάσεις. Τι είναι όμως ο Αντικειμενικοστραφής Προγραμματισμός;

Κατ' ουσίαν, ο Αντικειμενικοστραφής Προγραμματισμός είναι ένας τρόπος οργάνωσης των προγραμμάτων που γράφουμε. Ο τρόπος αυτός οργάνωσης, δεν είναι φυσικά ούτε μοναδικός ούτε καν ο βέλτιστος. Άλλοι τρόποι οργάνωσης είναι ο διαδικαστικός (procedural ή imperative) και ο συναρτησιακός (functional). Συνηθίζεται, αυτοί οι τρόποι οργάνωσης ή τεχνικές οργάνωσης των προγραμμάτων να ονομάζονται προγραμματιστικά παραδείγματα (programming paradigms).

Η επιλογή του προγραμματιστικού παραδείγματος το οποίο θα χρησιμοποιήσουμε εξαρτάται από το πρόβλημα το οποίο καλούμαστε να επιλύσουμε, αλλά και από τη γλώσσα προγραμματισμού την οποία χρησιμοποιούμε. Δεν επιτρέπουν όλες οι γλώσσες προγραμματισμού τη χρήση όλων των προγραμματιστικών παραδειγμάτων. Π.χ. υπάρχουν γλώσσες που επιτρέπουν τη δημιουργία μόνο διαδικαστικών προγραμμάτων όπως η C και η Fortran. Υπάρχουν άλλες που επιτρέπουν τη δημιουργία μόνο συναρτησιακών προγραμμάτων, όπως η Lisp και οι πολλές παραλλαγές της. Ενώ υπάρχουν και γλώσσες που επιτρέπουν, με περισσότερη η λιγότερη ευκολία, να εφαρμόσεις περισσότερα από ένα προγραμματιστικά παραδείγματα. Η Python ανήκει σε αυτήν την κατηγορία καθώς είναι δυνατό να αναπτυχθεί κώδικας που χρησιμοποιεί και τα τρία προγραμματιστικά παραδείγματα.

Όπως έχει αποδειχτεί στην πράξη, η πλειοψηφία των προβλημάτων που καλείται να λύσει ένας προγραμματιστής μπορούν να λυθούν με οποιοδήποτε από τα προγραμματιστικά παραδείγματα. Απλά συχνά κάποιο από τα προγραμματιστικά παραδείγματα προσφέρεται περισσότερο για την επίλυση ενός συγκεκριμένου προβλήματος.

Hint

Το πιο χαρακτηριστικό ίσως παράδειγμα προβλήματος που προσφέρεται για λύση μέσω αντικειμενοστραφούς προγραμματισμού είναι ο σχεδιασμός GUI.

Τα πάντα είναι αντικείμενα (Everything is an object)

Μία από τις πλέον συνήθεις εκφράσεις στα κείμενα που αναφέρονται στην Python είναι ότι στην Python "τα πάντα είναι αντικείμενα" (everything is an object). Ευθύς αμέσως φυσικά, μέσα από την φράση αυτή ξεπηδούν δύο ερωτήματα:

  • Τι είναι τα αντικείμενα (objects);
  • Πως δημιουργούνται τα αντικείμενα;

Το πρώτο ερώτημα θα το απαντήσουμε στην επόμενη ενότητα. Το δεύτερο ερώτημα μπορεί όμως να απαντηθεί πολύ πολύ σύντομα. Τα αντικείμενα (objects) δημιουργούνται από τις κλάσεις (classes). Για την ακρίβεια μάλιστα, οι κλάσεις ορίζονται ως εργοστάσια αντικειμένων (object factories). Είναι δηλαδή, τα στοιχεία εκείνα της γλώσσας, τα οποία κατασκευάζουν αντικείμενα (objects). Προκειμένου βέβαια να γίνει καλύτερα κατανοητό αυτό θα πρέπει πρώτα να δούμε τι είναι τα αντικείμενα.

Αντικείμενα (object)

Προκειμένου να εξηγήσουμε τον όρο αντικείμενο (object) στην Python συχνά βοηθάει να σκεφτόμαστε τα αντικείμενα (objects) ως κάτι ανάλογο με αυτό που ονομάζουμε στη γραμματική της φυσικής γλώσσας "ουσιαστικό".

Για να γίνει πιο σαφές αυτό ας σκεφτούμε ορισμένα ουσιαστικά. Καρέκλα, τραπέζι, σκύλος, γάτα, κύκλος, ορθογώνιο, οδηγός είναι ορισμένα που πιθανά έρχονται στο μυαλό. Αν προσπαθήσουμε να δούμε το κοινό σημείο όλων των παραπάνω θα δούμε ότι πρόκειται για:

"Οντότητες" (έμψυχες ή άψυχες) οι οποίες έχουν συγκεκριμένες ιδιότητες ή/και μπορούν να εκτελούν συγκεκριμένες ενέργειες.

Ας δούμε μερικά παραδείγματα:

  • Μία καρέκλα έχει τις ιδιότητες χρώμα, ύψος, υλικό κτλ.
  • Ένα ορθογώνιο αντίστοιχα έχει τις ιδιότητες πλάτος, ύψος, εμβαδόν, περίμετρος κτλ.
  • Μία γάτα έχει τις ιδιότητες όνομα, ηλικία, φύλλο κτλ αλλά επίσης μπορεί και να εκτελεί διάφορες ενέργειες όπως π.χ. να νιαουρίσει, να τρέξει, να περπατήσει, να σκαρφαλώσει, να φάει κτλ.
  • Ένας οδηγός μπορεί να προβεί σε διάφορες ενέργειες, όπως πχ να στρίψει, να φρενάρει, να επιταχύνει, να βάλει μπροστά τη μηχανή κτλ.

Όπως γίνεται σαφές από τα παραπάνω, σε όρους φυσικής γλώσσας, οι ιδιότητες των αντικειμένων είναι άλλα ουσιαστικά, ενώ οι ενέργειες που μπορούν να εκτελέσουν είναι ρήματα.

Note

Την αντιστοίχιση αυτή μεταξύ ιδιοτήτων - ουσιαστικών και ενεργειών - ρημάτων καλό είναι να την κρατήσουμε στο νου μας καθώς θα μας χρειαστεί αργότερα όταν θα δούμε πως μπορούμε να χρησιμοποιήσουμε τις κλάσεις.

Ο παραπάνω "ορισμός" των αντικειμένων είναι φυσικά πολύ γενικός και δύσκολα θα άντεχε σε ενδελεχή εξέταση από έναν φιλόλογο. Παρόλα αυτά, είναι ένας ορισμός που μας βολεύει ιδιαίτερα όταν επιστρέφουμε στον κόσμο της Python και αυτό γιατί, τα αντικείμενα (objects) της Python είναι εκείνες οι "οντότητες" οι οποίες μας επιτρέπουν να περιγράφουμε κάθε τι που έχει ιδιότητες και μπορεί να εκτελεί ενέργειες. Με λίγα λόγια δηλαδή, μέσω των αντικειμένων μπορούμε να περιγράψουμε πρακτικά σχεδόν οτιδήποτε συναντάμε στον υλικό κόσμο.

Χρησιμοποιώντας την ορολογία της Python, οι ιδιότητες ενός αντικειμένου ονομάζονται attributes, ενώ οι ενέργειές που μπορεί να εκτελέσει ονομάζονται methods (μέθοδοι).

Κλάσεις (Classes)

Warning

Η σύνταξη των κλάσεων στην Python είναι αρκετά απλή. Παρόλα αυτά, σε πρώτη φάση θα χρησιμοποιήσουμε μια ακόμη πιο απλοποιημένη σύνταξη προκειμένου να καταλάβουμε ευκολότερα ορισμένες βασικές έννοιες του Αντικειμενικοστραφούς Προγραμματισμού.

Την κανονική σύνταξη της Python θα την δούμε στη συνέχεια.

Μια κλάση δεν είναι τίποτα άλλο παρά το ένας ορισμός. Αυτό που ορίζει είναι το ποιες ιδιότητες (attributes) και ποιες μεθόδους (methods) θα έχει ένα αντικείμενο.

Ας δούμε ένα παράδειγμα. Σε πρώτη φάση ας φτιάξουμε μια κλάση που ορίζει ένα Αντικείμενο Γάτας, (με άλλα λόγια δηλαδή μια Γάτα!):

class Cat():
    name
    age
    sex

    def eat():
        print("%s is eating" % name)

    def sleep():
        print("%s is sleeping" % name)

H πρώτη γραμμή είναι αυτή στην οποία ορίζεται το όνομα της κλάσης. Προσέξτε την παρουσία των παρενθέσεων. Τη χρήση τους θα τη δούμε στη συνέχεια. Οι γραμμές που ακολουθούν, αποτελούν το σώμα της συνάρτησης (class body). Στη συγκεκριμένη περίπτωση, ορίσαμε ότι οι γάτες που θα δημιουργηθούν από την κλάση αυτή, θα έχουν 3 ιδιότητες (όνομα, ηλικία και φύλο) και θα μπορούν να κάνουν 2 ενέργειες (θα έχουν 2 μεθόδους δηλαδή) , να τρώνε και να κοιμούνται.

Όλες οι γάτες που θα δημιουργηθούν από την κλάση αυτή θα έχουν μόνο αυτές τις ιδιότητες (attributes) και θα μπορούν να εκτελούν μόνο τις συγκεκριμένες ενέργειες.

Note

Θα μπορούσαμε φυσικά να κάνουμε την κλάση μας πολύ πιο σύνθετη. Μια γάτα εξάλλου είναι ένας πολύ σύνθετος οργανισμός... Αλλά εδώ για διδακτικούς λόγους προτιμήσαμε να κρατήσουμε τα πράγματα απλά.

Τώρα θα χρησιμοποιήσουμε την κλάση που ορίσαμε παραπάνω για να δημιουργήσουμε δύο νέες γάτες (δηλαδή δύο νέα αντικείμενα Γάτας). Τη γατα του Joe, μια θηλυκή γάτα δύο ετών που τη λένε "Kitty" και τη γάτα της Mary, μια αρσενική γάτα οκτώ ετών που τη λένε "Paul":

joes_cat = Cat(name="Kitty", age=2, sex="female")
marys_cat = Cat(name="Paul", age=8, sex="male")

H σύνταξη για τη δημιουργία των νέων αντικειμένων Γάτας είναι πολύ απλή. Απλά χρησιμοποιούμε το όνομα της κλάση και μέσα στις παρενθέσεις δίνουμε τις τιμές των ιδιοτήτων (attributes) της κλάσης. Με τον τρόπο αυτό, δίνοντας δηλαδή διαφορετικές τιμές στα attributes της κλάσης, μπορούμε να δημιουργήσουμε πολλές, διαφορετικές μεταξύ τους γάτες. Δεν υπάρχει κανένας περιορισμός στον αριθμό των νέων αντικειμένων που θα δημιουργήσουμε από μία κλάση. Μπορούμε να δημιουργήσουμε όσα νέα αντικείμενα θέλουμε.

Note

Υπενθυμίζουμε ότι προηγουμένως ορίσαμε τις κλάσεις σαν "εργοστάσιο αντικειμένων" (factory object). Ακριβώς όπως ένα εργοστάσιο που φτιάχνει καρέκλες μπορεί να κατασκευάσει καρέκλες διαφορετικού σχεδίου, διαστάσεων και χρώματος, έτσι και οι κλάσεις μπορούν να κατασκευάσουν αντικείμενα με διαφορετικές ιδιότητες.

Όλα τα νέα αντικείμενα θα έχουν ακριβώς τις ίδιες ιδιότητες (attributes) και τις ίδιες μεθόδους. Οι τιμές των ιδιοτήτων τους όμως θα είναι διαφορετικές μεταξύ τους μεταξύ τους.

Στην ορολογία του αντικειμενικοστραφούς Προγραμματισμού, όλα τα νέα αντικείμενα που δημιουργούνται από μία κλάση ονομάζονται στιγμιότυπα (instances).

Note

Όπως μία φωτογραφία ενός αθλητή που τρέχει αποτελεί την απεικόνιση μίας μόνο από όλες τις θέσεις που πέρασε κατά τη διάρκεια του αγώνα του, έτσι και μία instance αποτελεί μία μόνο αποτύπωση όλων των δυνατών συνδυασμών που μπορούν να πάρουν οι ιδιότητες μιας κλάσης.

Πρόσβαση σε ιδιότητες και μεθόδους

Προκειμένου να αποκτήσουμε πρόσβαση στις ιδιότητες και στις μεθόδους των αντικειμένων που δημιουργήσαμε, χρησιμοποιούμε το λεγόμενο dot notation. Πχ. για να εκτυπώσουμε στην οθόνη το όνομα της κάθε γάτας θα δίναμε:

print(joes_cat.name)
print(marys_cat.name)

To αποτέλεσμα της εκτέλεσης του παραπάνω κώδικα θα είναι:

Kitty
Paul

Αντίστοιχα για να πούμε στη γάτα του Joe να κοιμηθεί και στη γάτα της Mary να φάει θα το κάναμε ως εξής:

joes_cat.sleep()
marys_cat.eat()

To αποτέλεσμα της εκτέλεσης του παραπάνω κώδικα θα είναι:

Kitty is eating.
Paul is sleeping.

Περισσότερα για τις Μεθόδους

Αν προσέξουμε τον ορισμό των μεθόδων μέσα στο σώμα της κλάσης, θα δούμε ότι δε διαφέρουν από τον ορισμό των συναρτήσεων. Μία μέθοδος δεν είναι τίποτα άλλο παρά μία συνάρτηση που ανήκει σε ένα αντικείμενο. Όλα όσα ξέρουμε για τις τυπικές συναρτήσεις της Python ισχύουν και εδώ. Μπορούμε να δώσουμε έξτρα ορίσματα (arguments) σε μία μέθοδο κτλ. Πχ ας ξαναορίσουμε την κλάση της Γάτας, βάζοντας αυτή τη φορά υποχρεωτικά ορίσματα στις μεθόδους της:

class Cat():
    name
    age
    sex

    def eat(food):
        print("%s is eating %s." % (name, food))

    def sleep(time):
        print("%s is sleeping for %d minutes." % (name, time))

Warning

Το ότι οι μέθοδοι είναι ακριβώς ίδιες με τις συναρτήσεις δεν είναι απόλυτα ακριβές. Στην ψευδογλώσσα που χρησιμοποιούμε, ισχύει μεν κάτι τέτοιο αλλά στην Python οι μέθοδοι έχουν μία διαφορά από τις συναρτήσεις. Κρατήστε το στο μυαλό σας, αλλά για την ώρα δεν είναι σημαντικό.

Αυτή τη φορά λοιπόν, θα μπορούμε να πούμε στις γάτες που θα δημιουργήσουμε τι να φάνε και πόση ώρα να κοιμηθούνε. Πχ με τον ακόλουθο κώδικα, θα δημιουργήσουμε μια γάτα στην οποία θα πούμε πρώτα να φάει ποντίκια, στη συνέχεια να κοιμηθεί για 120 λεπτά και μετά να πιει γάλα:

alley_cat = Cat(name="Leo", age=4, sex="male")

alley_cat.eat("mice")
alley_cat.sleep(120)
alley_cat.eat("milk")

To αποτέλεσμα της εκτέλεσης του παραπάνω κώδικα θα είναι:

Leo is eating mice.
Leo is sleeping for 120 minutes.
Leo is eating milk.

Note

Παρά τις επίμονες προσπάθειές του, ο συγγραφέας ποτέ δεν κατάφερε να πει στη γάτα του πόση ώρα να κοιμηθεί...

Βλέπετε, στις κλάσεις που ορίζουμε εμείς, μπορούμε να αποφασίσουμε για τη συμπεριφορά των αντικειμένων που θα δημιουργηθούν. Στον πραγματικό κόσμο πάλι όχι...

Κληρονομικότητα (Inheritance)

Ίσως η πλέον κεφαλαιώδης έννοια του Αντικειμενικοστραφούς Προγραμματισμού είναι η κληρονομικότητα (inheritance).

Με τον όρο κληρονομικότητα, εννοούμε τη δυνατότητα που έχει μια κλάση να κληρονομεί όλες τις ιδιότητες και τις μεθόδους μιας άλλης κλάσης. Χρησιμοποιώντας λίγο πιο επίσημη ορολογία, λέμε ότι η κλάση που ορίζεται πρώτη είναι η βασική κλάση (base class) και η κλάση που κληρονομεί τη βάσική κλάση ονομάζεται παράγωγη (derived class). Εναλλακτικά οι βασικές κλάσεις ονομάζονται και Υπερκλάσεις ενώ οι παράγωγες κλάσεις ονομάζονται Υποκλάσεις.

Ας δούμε ένα παράδειγμα:

class BaseClass():
    attr1
    attr2

    def method1():
        print("You just called method1.")

    def method2():
        print("You just called method2.")

class DerivedClass(BaseClass):
    pass

H υπερκλάση (BaseClass) δεν έχει καμία διαφορά από τις κλάσεις που έχουμε δει ως τώρα. H υποκλάση (DerivedClass) όμως χρησιμοποιεί ελαφρά διαφορετική σύνταξη. Αντί οι παρενθέσεις της πρώτης γραμμής του ορισμού της να είναι κενές, π.χ.:

class DerivedClass():
    pass

περιέχουν το όνομα της υπερκλάσης:

class DerivedClass(BaseClass):
    pass

Αυτή η μικρή διαφορά στη σύνταξη κάνει όλη τη διαφορά! Με τον τρόπο αυτό, οι instances της DerivedClass αποκτούν όλες τις ιδιότητες και όλες τις μεθόδους που ορίστηκαν στην BaseClass! Ας δούμε ένα παράδειγμα. Θα δημιουργήσουμε δύο instances της DerivedClass και θα καλέσουμε τις μεθόδους που έχουν οριστεί στην BaseClass:

# Class Instantiation
instance1 = DerivedClass(attr1="value1", attr2="value2")
instance2 = DerivedClass(attr1="value3", attr2="value4")

instance1.method1()
instance2.method2()

Το αποτέλεσμα του παραπάνω κώδικα θα είναι:

You just called method1.
You just called method2.

Όπως βλέπουμε, αν και το class body της DerivedClass είναι κενό, παρόλα αυτά, τα στιγμιότυπά της, τα αντικείμενα δηλαδή που δημιουργούνται από την κλάση, μπορούν να καλούν κανονικά τις μεθόδους της BaseClass.

Επειδή φαντάζομαι ότι το παραπάνω δεν είναι και πολύ ικανοποιητικό, ας δούμε ένα παράδειγμα καλύτερα:

class Mamal():
    species
    sex

    def speak():
        pass

class Cat(Mamal):
    name

    def speak():
        print("Meow!")

class Dog(Mamal):
    name

    def speak():
        print("Woof!")

Σύνθεση (Composition)

Καλά όλα αυτά... Εμένα γιατί μου χρειάζονται;

Κάπου εδώ βέβαια, εύλογα δημιουργείται το ερώτημα: "Καλά όλα αυτά... Εμένα γιατί μου χρειάζονται;"

Η απάντηση σε αυτό το ερώτημα δεν είναι και τόσο απλή. Μια σύντομη αναζήτηση στο ιντερνετ σχετικά με τα πλεονεκτήματα του Αντικειμενικοστραφούς Προγραμματισμού (benefits/advantages of Object-Oriented Programming) θα επιστρέψει πλήθος σελίδων που απαντούν στο ερώτημα αυτό. Οι απαντήσεις χωρίζονται σε δύο σκέλη:

  • Στα οφέλη σε επίπεδο κώδικα
  • Στα οφέλη σε επίπεδο σύλληψης.

Τα οφέλη της πρώτης κατηγορίας είναι εύκολο να γίνουν αντιληπτά. Χάρις στην κληρονομικότητα αποφεύγουμε να επαναλάβουμε κώδικα (αρχή "Do not Repeat Yourself" - DRY). Αυτό γίνεται γιατί όλα τα αντικείμενα μας μοιράζονται τον ίδιο κώδικα. Οι κοινές μέθοδοι ορίζονται μία φορά στην υπερκλάση και όλες οι υποκλάσεις απλά την κληρονομούνε. Με τον τρόπο αυτό μειώνεται το μέγεθος του κώδικα. Λιγότερος κώδικας σημαίνει λιγότερα bugs, πιο κατανοητός κώδικας και πιο κώδικας που είναι πιο εύκολο να συντηρηθεί.

στον οποίο έχεις γίνει Στην πρώτη κατηγορία Η αλήθεια βέβαια είναι ότι η πλειοψηφία των απαντήσεων αυτών μπορείς να τις εκτιμήσεις μόνο αφού έχεις ήδη κατανοήσει και χρησιμοποιήσει τις αρχές του OOP στα προγράμματα σου.

Τα πραγματικά οφέλη δεν γίνονται κατανοητά αν δεν προγραμματίσεις ο ίδιος με όλα τα προγραμματιστικά παραδείγματα, ούτως ώστε να μπορείς να δεις τις διαφορές τους στην πράξη.

Εν πάση περιπτώσει προκειμένου να δώσουμε μία απάντηση, θα πρέπει να πούμε ότι ο OOP είναι μία προσέγγιση που μας επιτρέπει να φτάσουμε σε υψηλά επίπεδα αφαίρεσης (abstraction) μειώνοντας με αυτόν τον τρόπο την πολυπλοκότητα των προβλημάτων που καλούμαστε να λύσουμε (και όποιος κατάλαβε, κατάλαβε :P).

Ενταξει, επειδή η προηγούμενη παράγραφος είναι σκόπιμα δυσνόητη και γενικόλογη, ας προσπαθήσουμε να κάνουμε τα πράγματα λίγο πιο λιανά.