Η κληρονομικότητα (inheritance) και ο πολυμορφισμός (polymorphism) είναι δύο βασικές έννοιες στη γλώσσα C# που επιτρέπουν την οργάνωση και την αποδοτική ανάπτυξη των προγραμμάτων.
Η κληρονομικότητα είναι η δυνατότητα μιας κλάσης να κληρονομεί τα χαρακτηριστικά (μεταβλητές και μεθόδους) μιας άλλης κλάσης. Μια κλάση που κληρονομείται ονομάζεται υποκλάση, ενώ η κλάση που δίνει την κληρονομικότητα ονομάζεται γονική κλάση. Οι υποκλάσεις μπορούν να κληρονομήσουν τα χαρακτηριστικά της γονικής κλάσης και να προσθέσουν νέα χαρακτηριστικά ή να τροποποιήσουν τη συμπεριφορά των υπάρχοντων. Αυτό επιτρέπει την αναδιάρθρωση του κώδικα, την αποφυγή της επανάληψης και την αποδοτική αναπαραγωγή των κλάσεων.
Ο πολυμορφισμός αφορά τη δυνατότητα ενός αντικειμένου να λειτουργεί ως πολλαπλοί τύποι. Συγκεκριμένα, ένα αντικείμενο μπορεί να αντιμετωπίζεται ως αντικείμενο μιας γονικής κλάσης ή ως αντικε
ίμενο μιας υποκλάσης της. Αυτό επιτρέπει τη δημιουργία γενικών μεθόδων που μπορούν να εργαστούν με αντικείμενα που ανήκουν σε διάφορους τύπους. Ο πολυμορφισμός συνδέεται στενά με την κληρονομικότητα καθώς οι υποκλάσεις μπορούν να υλοποιούν διαφορετικές συμπεριφορές για τις ίδιες μεθόδους που έχουν κληρονομηθεί από τη γονική κλάση.
Η κληρονομικότητα και ο πολυμορφισμός αποτελούν σημαντικές αρχές της αντικειμενοστραφούς προγραμματισμού (OOP) που επιτρέπουν την οργάνωση και την ευελιξία του κώδικα, καθιστώντας την ανάπτυξη και τη συντήρηση προγραμμάτων πιο αποτελεσματική.
Στη γλώσσα προγραμματισμού C#, μπορούμε να χρησιμοποιήσουμε την κληρονομικότητα για τη μετάδοση πεδίων και μεθόδων από μια κλάση σε μια άλλη. Η κληρονομικότητα βασίζεται στην έννοια της “παράγωγης κλάσης” και της “βασικής κλάσης”:
- Παράγωγη κλάση (child): Αυτή είναι η κλάση που κληρονομεί την λειτουργικότητα από μια άλλη κλάση. Η παράγωγη κλάση μπορεί να επωφεληθεί από τα πεδία και τις μεθόδους της βασικής κλάσης.
- Βασική κλάση (parent): Αυτή είναι η κλάση που παρέχει τη λειτουργικότητά της στις παράγωγες κλάσεις. Η βασική κλάση ορίζει τα πεδία και τις μεθόδους που είναι κοινές για τις παράγωγες κλάσεις.
Για να πραγματοποιήσετε κληρονομικότητα σε μια κλάση, χρησιμοποιείτε το σύμβολο “:” (άνω κάτω τελεία). Η δήλωση κληρονομικότητας ακολουθείται από το όνομα της βασικής κλάσης από την οποία κληρονομείτε.
Παράδειγμα:
class ChildClass : ParentClass { // Πρόσθετη λειτουργικότητα για την παράγωγη κλάση }
Στο παραπάνω παράδειγμα, η κλάση ChildClass
κληρονομεί τη λειτουργικότητα από την κλάση ParentClass
. Μπορείτε να προσθέσετε πρόσθετα πεδία και μέθοδοι στην ChildClass
, επωφελούμενοι από την κληρονομημένη λειτουργικότητα της ParentClass
.
Ένα παράδειγμα κληρονομικότητας μεταξύ δύο κλάσεων μπορεί να είναι το εξής:
using System; class Vehicle { protected string brand; // Προστατευμένο πεδίο brand public void Honk() { Console.WriteLine("Beep beep!"); // Εκτύπωση μηνύματος στην οθόνη } } class Car : Vehicle // Κληρονομικότητα, η κλάση Car κληρονομεί την κλάση Vehicle { private string modelName; // Ιδιωτικό πεδίο modelName static void Main(string[] args) { Car myCar = new Car(); // Δημιουργία ενός αντικειμένου της κλάσης Car με τη χρήση του κατασκευαστή myCar.brand = "Ford"; // Ανάθεση τιμής στο πεδίο brand του αντικειμένου myCar myCar.modelName = "Mustang"; // Ανάθεση τιμής στο πεδίο modelName του αντικειμένου myCar Console.WriteLine($"Brand: {myCar.brand}, Model: {myCar.modelName}"); // Εκτύπωση των τιμών των πεδίων brand και modelName του αντικειμένου myCar myCar.Honk(); // Κλήση της μεθόδου Honk() του αντικειμένου myCar } }
Ο παραπάνω κώδικας σε C# ορίζει δύο κλάσεις, την κλάση Vehicle
και την κλάση Car
, και περιέχει μια μέθοδο Main
ως σημείο εκκίνησης του προγράμματος.
Η κλάση Vehicle
διαθέτει ένα προστατευμένο πεδίο με την ονομασία brand
και μια μέθοδο Honk()
που εκτυπώνει το μήνυμα “Beep beep!”.
Η κλάση Car
κληρονομεί την κλάση Vehicle
και διαθέτει ένα ιδιωτικό πεδίο modelName
.
Η μέθοδος Main
είναι η μέθοδος που εκτελείται κατά την έναρξη του προγράμματος. Σε αυτήν την μέθοδο, δημιουργείται ένα αντικείμενο της κλάσης Car
με τη χρήση του κατασκευαστή. Στη συνέχεια, ορίζονται τιμές στα πεδία brand
και modelName
του αντικειμένου και εκτυπώνονται αυτές οι τιμές μέσω της μεθόδου Console.WriteLine()
. Τέλος, καλείται η μέθοδος Honk()
του αντικειμένου για να εκτελεστεί η ενέργεια του “κόρναρίσματος”.
Ουσιαστικά, ο κώδικας δημιουργεί ένα αντικείμενο αυτοκινήτου, ορίζει τη μάρκα και το μοντέλο του αυτοκινήτου, και εκτυπώνει αυτές τις πληροφορίες στην οθόνη. Στη συνέχεια, το αυτοκίνητο “κορνάρει”.
[adinserter block=”2″]
Εάν δεν θέλετε άλλες κλάσεις να κληρονομούν από μια κλάση, χρησιμοποιήστε τη λέξη-κλειδί sealed:
sealed class MyClass { // Κώδικας κλάσης }
Στο παραπάνω παράδειγμα, η κλάση MyClass
ορίζεται ως sealed, που σημαίνει ότι δεν επιτρέπεται η κληρονομικότητα από άλλες κλάσεις. Οποιαδήποτε προσπάθεια να κληρονομηθεί η MyClass
θα προκαλέσει ένα σφάλμα στον μεταγλωττιστή.
Η χρήση της λέξης-κλειδί sealed προσδιορίζει έναν τελικό τύπο, που δεν μπορεί να λειτουργήσει ως βασική κλάση για άλλες κλάσεις. Αυτό μπορεί να είναι χρήσιμο όταν θέλετε να περιορίσετε την κληρονομικότητα σε μια κλάση και να διασφαλίσετε ότι ο τύπος διατηρεί την αρχική του λειτουργία χωρίς περαιτέρω επεκτάσεις ή παραμορφώσεις.
Ο πολυμορφισμός (polymorphism) αναφέρεται στην “πολλαπλότητα των μορφών” και συμβαίνει όταν έχουμε πολλές κλάσεις που σχετίζονται μεταξύ τους μέσω κληρονομικότητας.
Όπως αναφέραμε και στο προηγούμενο κεφάλαιο, η κληρονομικότητα μας επιτρέπει να κληρονομήσουμε πεδία και μεθόδους από μια άλλη κλάση. Ο πολυμορφισμός χρησιμοποιεί αυτές τις μεθόδους για να εκτελέσει διάφορες εργασίες. Αυτό μας επιτρέπει να εκτελούμε μια ενέργεια με διαφορετικούς τρόπους.
Ένα παράδειγμα πολυμορφισμού είναι η υπερσυνάρτηση (overriding) μεθόδων. Όταν μια κλάση κληρονομεί μια μέθοδο από μια βασική κλάση, μπορεί να υπερκαλύψει (override) αυτήν τη μέθοδο για να παρέχει μια νέα υλοποίηση. Αυτό μας επιτρέπει να χρησιμοποιούμε τη μέθοδο με διαφορετικό τρόπο στην παράγωγη κλάση.
Για παράδειγμα, ας υποθέσουμε μια βασική κλάση με το όνομα Animal που έχει μια μέθοδο με το όνομα animalSound(). Οι παράγωγες κλάσεις των Animals θα μπορούσαν να είναι Pigs, Cats, Dogs, Birds και θα είχαν επίσης τη δική τους υλοποίηση για τον ήχο του ζώου (το γουρούνι κρούει, η γάτα νιαουρίζει, κλπ.):
using System; class Animal { // Ορισμός της εικονικής μεθόδου animalSound() public virtual void animalSound() { Console.WriteLine("The animal makes a sound"); } } class Pig : Animal { // Υλοποίηση της μεθόδου animalSound() για το γουρούνι public override void animalSound() { Console.WriteLine("The pig says: Oink oink!"); } } class Cat : Animal { // Υλοποίηση της μεθόδου animalSound() για τη γάτα public override void animalSound() { Console.WriteLine("The cat says: Meow!"); } } class Dog : Animal { // Υλοποίηση της μεθόδου animalSound() για το σκυλί public override void animalSound() { Console.WriteLine("The dog says: Woof woof!"); } } class Bird : Animal { // Υλοποίηση της μεθόδου animalSound() για το πουλί public override void animalSound() { Console.WriteLine("The bird says: Chirp chirp!"); } }
[adinserter block=”3″]
Ο παραπάνω κώδικας σε C# ορίζει μια ιεραρχία κλάσεων που αντιπροσωπεύει ζώα και κάθε ζώο έχει μια μέθοδο που περιγράφει τον ήχο που κάνει. Ακολουθούν τα βασικά στοιχεία του κώδικα:
- Η κλάση
Animal
είναι η βασική κλάση που ορίζει τη μέθοδοanimalSound()
ως εικονική (virtual). Αυτό σημαίνει ότι οι υποκλάσεις μπορούν να αντικαταστήσουν τη μέθοδο αυτή για να παρέχουν διαφορετική υλοποίηση. - Οι κλάσεις
Pig
,Cat
,Dog
καιBird
είναι υποκλάσεις της κλάσηςAnimal
και αντικαθιστούν τη μέθοδοanimalSound()
με δικές τους υλοποιήσεις. Κάθε υποκλάση περιέχει τη μέθοδοanimalSound()
που εκτυπώνει έναν διαφορετικό ήχο για κάθε ζώο.
Όταν δημιουργείται ένα αντικείμενο από μια από τις υποκλάσεις (π.χ., Pig pig = new Pig();
), μπορεί να κληθεί η μέθοδος animalSound()
για να εκτυπωθεί ο αντίστοιχος ήχος που κάνει το ζώο. Για παράδειγμα:
Pig pig = new Pig(); Cat cat = new Cat(); Dog dog = new Dog(); Bird bird = new Bird(); pig.animalSound(); // Θα εκτυπωθεί: "The pig says: Oink oink!" cat.animalSound(); // Θα εκτυπωθεί: "The cat says: Meow!" dog.animalSound(); // Θα εκτυπ ωθεί: "The dog says: Woof woof!" bird.animalSound(); // Θα εκτυπωθεί: "The bird says: Chirp chirp!"
Έτσι, ο κώδικας προσδιορίζει τη συμπεριφορά και τον ήχο των διάφορων ζώων μέσω της κληρονομικότητας και της υλοποίησης εικονικών μεθόδων.
Τώρα μπορούμε να δημιουργήσουμε αντικείμενα Pig και Dog και να καλέσουμε τη μέθοδο animalSound() σε κάθε ένα από αυτά:
Pig myPig = new Pig(); Dog myDog = new Dog(); myPig.animalSound(); // Εκτυπώνει "The pig says: Oink oink!" myDog.animalSound(); // Εκτυπώνει "The dog says: Woof woof!"
Σε αυτό το παράδειγμα, δημιουργούμε ένα αντικείμενο Pig με το όνομα myPig και ένα αντικείμενο Dog με το όνομα myDog. Στη συνέχεια, καλούμε τη μέθοδο animalSound() σε κάθε ένα από αυτά τα αντικείμενα. Η μέθοδος που εκτελείται είναι η υλοποίηση της αντίστοιχης κλάσης (Pig ή Dog), παράγοντας τον αντίστοιχο ήχο για κάθε ζώο.