ΕΝΟΤΗΤΑ 24 - Inheritance

Στο σημερινό δωρεάν μάθημα Python, θα μιλήσουμε για την κληρονομικότητα (inheritance) που μπορεί να υπάρξει ανάμεσα στις κλάσεις και πως αυτή μπορεί να μας βοηθήσει στο να έχουμε ένα πιο οργανωμένο κώδικα. Ας δούμε όμως την έννοια της κληρονομικότητας μέσα από ένα παράδειγμα.
Στις προηγούμενες ενότητες, είχαμε ασχοληθεί με το απλό σενάριο να περιγράψουμε προγραμματιστικά την έννοια του εργαζόμενου. Αφού δημιουργήσαμε την κλάση με την οποία περιγράψαμε τον εργαζόμενο, προσθέσαμε μεταβλητές και μεθόδους για να μπορέσουμε να εκτελέσουμε κάποιες πράξεις με το συγκεκριμένο αντικείμενο.
Τώρα θέλουμε να μεγαλώσουμε την εφαρμογή μας και, εκτός από την γενική έννοια του εργαζόμενου, θέλουμε να καλύψουμε περιπτώσεις όπως τον μισθωτό και τον εξωτερικό συνεργάτη. Με όσα γνωρίζουμε μέχρι τώρα, λογικά θα πρέπει να δημιουργήσουμε δύο ακόμα κλάσεις με ονόματα SalaryEmployee και ExternalPartner αντίστοιχα. Ας δούμε και τις τρεις κλάσεις μαζί σε μια εφαρμογή. Για λόγους ευκολίας, θα χρησιμοποιήσουμε μια πιο απλή μορφή της κλάσης Employee.
App.py
class Employee(): def __init__(self, id, name, salary, department): self.name = name self.id = id self.salary = salary self.department = department def __str__(self): return f'id={self.id}, name={self.name}, salary={self.salary}, department={self.department}' class SalaryEmployee(): def __init__(self, id, name, salary, department): self.name = name self.id = id self.salary = salary self.department = department def __str__(self): return f'id={self.id}, name={self.name}, salary={self.salary}, department={self.department}' class ExternalPartner(): def __init__(self, id, name, salary, department): self.name = name self.id = id self.salary = salary self.department = department def __str__(self): return f'id={self.id}, name={self.name}, salary={self.salary}, department={self.department}' employee = Employee(100, 'Michail', 1000, 'IT') print(employee) salaryemployee = SalaryEmployee(200, 'George', 1200, 'Finance') print(salaryemployee) externalpartner = ExternalPartner(300, 'Maria', 1500, 'Marketing') print(externalpartner)
Output
id=100, name=Michail, salary=1000, department=IT id=200, name=George, salary=1200, department=Finance id=300, name=Maria, salary=1500, department=Marketing
Αν και το πρόγραμμα μας δουλεύει, με μια γρήγορη ματιά παρατηρούμε ότι επαναλαμβάνουμε πολλαπλές φορές τον ίδιο κώδικα, όπως πχ. name και id. Το ιδανικό θα ήταν να κληρονομούσαμε όλα τα γενικά στοιχεία από μια κλάση, και εμείς απλά να προσθέταμε τα έξτρα στοιχεία που διαφοροποιούν την μια κλάση από μια άλλη. Στο δικό μας απλό παράδειγμα αυτός ο διαχωρισμός μπορεί να βασιστεί στον υπολογισμό του salary. Ο μισθωτός θα πληρώνεται ένα σταθερό ποσόν κάθε μήνα, ενώ ο εξωτερικός συνεργάτης θα πληρώνεται με βάση τις ώρες τις οποίες εργάστηκε σε ένα project. Πριν όμως φτάσουμε στο σημείο που θα υπολογίζουμε τον κάθε μισθό ξεχωριστά, πως μπορεί η κλάση SalaryEmployee να κληρονομήσει τα στοιχεία της Employee?
Ο τρόπος με τον οποίο ενεργοποιούμε την κληρονομικότητα ανάμεσα σε δύο κλάσεις είναι να προσθέσουμε μέσα στην παρένθεση της δεύτερης κλάσης το όνομα της κλάσης από την οποία επιθυμεί να κληρονομήσει.
App.py
class Employee(): def __init__(self, id, name, department): self.name = name self.id = id self.department = department def __str__(self): return f'id={self.id}, name={self.name}, salary={self.salary}, department={self.department}' class SalaryEmployee(Employee): def __init__(self, monthlysalary): self.monthlysalary = monthlysalary def __str__(self): return f'id={self.id}, name={self.name}, salary={self.monthlysalary}, department={self.department}' class ExternalPartner(Employee): def __init__(self, hourlysalary): self.hourlysalary = hourlysalary def __str__(self): return f'id={self.id}, name={self.name}, salary={self.hourlysalary}, department={self.department}' salaryEmployee = SalaryEmployee(1000) salaryEmployee.id = 100 salaryEmployee.department = 'Marketing' salaryEmployee.name = 'Michail' print(salaryEmployee)
Output
id=100, name=Michail, salary=1000, department=Marketing
Στο παραπάνω παράδειγμα, η κλάση SalaryEmployee κληρονόμησε όλα τα χαρακτηριστικά της κλάσης Employee και απλά πρόσθεσε την έξτρα μεταβλητή monthlysalary που χρειαζόταν. Με αυτό τον τρόπο αποφύγαμε να επαναλάβουμε τον κώδικα της Employee για ακόμα μια φορά. Με την ίδια ακριβώς λογική θα μπορούσαμε να δημιουργήσουμε και ένα αντικείμενο από την κλάση ExternalPartner.
Η κλάση Employee ονομάζεται superclass, ενώ η SalaryEmployee και η ExternalPartner ονομάζονται subclasses. Φυσικά εκτός από attributes, οι subclasses κληρονομούν και τις μεθόδους από τις superclasses.
Ενώ ήδη καταφέραμε να μειώσουμε σημαντικά τον κώδικα μας, ο τρόπος που δημιουργήσαμε το αντικείμενο ίσως να μην μας ικανοποιεί και τόσο. Μετά την δημιουργία του αντικειμένου, χρειάστηκε να δώσουμε τιμές σε όλες τις μεταβλητές μια προς μια. Το καλύτερο, θα ήταν να μπορούσαμε να δώσουμε τις αρχικές τιμές που θα περιέχει το αντικείμενο κατά την διάρκεια της δημιουργίας του. Ακριβώς όπως ήδη γνωρίζουμε από τις προηγούμενες ενότητες. Αλλά πως όμως αυτό είναι εφικτό όταν δημιουργούμε ένα αντικείμενο με τις μεταβλητές του να έχουν κληρονομηθεί από άλλη κλάση?
Εδώ θα χρειαστούμε την βοήθεια της super() μεθόδου. Με την χρήση της super( ) μπορούμε να δημιουργήσουμε αντικείμενο στην subclass και να καλέσουμε τον constructor της superclass για να μας κάνει initialize το αντικείμενο με κάποιες τιμές. Ας δούμε πρώτα πως θα αλλάζει ο κώδικας μας όταν χρησιμοποιούμε την super( ), και μετά θα εξηγήσουμε λίγο περισσότερο την λειτουργία της.
App.py
class Employee(): def __init__(self, id, name, department): self.name = name self.id = id self.department = department def __str__(self): return f'id={self.id}, name={self.name}, salary={self.salary}, department={self.department}' class SalaryEmployee(Employee): def __init__(self, id, name, department, monthlysalary): self.monthlysalary = monthlysalary super().__init__(id, name, department) def __str__(self): return f'id={self.id}, name={self.name}, salary={self.monthlysalary}, department={self.department}' class ExternalPartner(Employee): def __init__(self, id, name, department, hourlysalary): self.hourlysalary = hourlysalary super().__init__(id, name, department) def __str__(self): return f'id={self.id}, name={self.name}, salary={self.hourlysalary}, department={self.department}' salaryEmployee = SalaryEmployee(100, 'Michail', 'IT', 1000) print(salaryEmployee)
Output
id=100, name=Michail, salary=1000, department=IT
Ας εξηγήσουμε λοιπόν τι καταφέραμε με τον παραπάνω κώδικα. Η κλάση SalaryEmployee απαιτεί να έχει διαθέσιμες τις τιμές για τις μεταβλητές id, name, department και monthlysalary πριν από την δημιουργία του αντικειμένου γιατί πολύ απλά θα τις αναθέσει σε αυτό όταν το δημιουργήσει. Όμως μέσα στην κλάση SalaryEmployee, εκτός από την monthlysalary, δεν έχουμε ορίσει καμία άλλη μεταβλητή. Όταν λάβουμε λοιπόν τις τιμές για τις τρεις μεταβλητές, με την χρήση της μεθόδου super(), καλούμε τον constructor της κλάσης Employee. Αυτός αναλαμβάνει να θέσει τις τιμές για τις μεταβλητές που γνωρίζει στο αντικείμενο. Λόγω της κληρονομικότητας λαμβάνουμε λοιπόν αυτό το initialization έτοιμο από την κλάση Employee. Σαν τελευταίο βήμα, προσθέτουμε την monthlysalary για να δημιουργήσουμε την τελική μορφή του αντικειμένου.
ΕΙΣΗΓΗΤΗΣ
Μιχάλης Κασάπογλου
Ο Μιχάλης Κασάπογλου, είναι ένας από τους πιο έμπειρους τεχνικούς εκπαιδευτές στον χώρο του προγραμματισμού με πάνω από 20 χρόνια εμπειρία. Έχει εργαστεί σαν IT Operations Manager, Senior Programmer, και Training Team Leader ενώ κατέχει και αρκετές πιστοποιήσεις που καλύπτουν ένα μεγάλο φάσμα τεχνολογιών στο προγραμματισμό, σε βάσεις δεδομένων και cloud. Στον ελεύθερο του χρόνο διατηρεί ένα τεχνολογικό blog στο οποίο θα βρείτε αρκετά δωρεάν μαθήματα προγραμματισμού για αρχάριους.