Τι είναι ο Πολυμορφισμός;

Ο πολυμορφισμός προέρχεται από δύο ελληνικές λέξεις: πολύ (πολλές) και μορφή (σχήμα), δηλαδή “πολλές μορφές”. Στην C#, σημαίνει ότι ένα αντικείμενο μπορεί να έχει πολλές μορφές ή να συμπεριφέρεται με πολλούς διαφορετικούς τρόπους.

Ουσιαστικά, ο πολυμορφισμός επιτρέπει σε διάφορες υποκλάσεις να υλοποιούν τη δική τους συμπεριφορά για μεθόδους που κληρονομούν από μια βασική κλάση. Έτσι, μπορείς να χρησιμοποιείς ένα αντικείμενο ως γενικό τύπο (π.χ., Animal), αλλά αυτό να συμπεριφέρεται ανάλογα με την συγκεκριμένη υποκλάση στην οποία ανήκει (π.χ., Dog ή Cat).


Δύο τύποι πολυμορφισμού στην C#

  1. Στατικός Πολυμορφισμός (Static Polymorphism): Γίνεται με υπερφόρτωση μεθόδων. Η ίδια μέθοδος μπορεί να έχει διαφορετικές παραμέτρους και να κάνει διαφορετικά πράγματα, ανάλογα με το ποια έκδοση καλείται.
  2. Δυναμικός Πολυμορφισμός (Dynamic Polymorphism): Γίνεται με εικονικές μεθόδους (virtual) και προκαθορισμένες μεθόδους (override). Μια μέθοδος μπορεί να αλλάζει συμπεριφορά ανάλογα με την υποκλάση που την υλοποιεί.

1. Στατικός Πολυμορφισμός (Static Polymorphism)

Ο στατικός πολυμορφισμός επιτυγχάνεται με την υπερφόρτωση μεθόδων. Αυτό σημαίνει ότι η ίδια μέθοδος μπορεί να έχει διαφορετικές εκδοχές (με διαφορετικές παραμέτρους), και η σωστή έκδοση καλείται ανάλογα με τις παραμέτρους που της δίνεις.

Παράδειγμα Στατικού Πολυμορφισμού:

class Calculator
{
// Υπερφόρτωση της μεθόδου Add για δύο ακέραιους αριθμούς
public int Add(int a, int b)
{
return a + b;
}

// Υπερφόρτωση της μεθόδου Add για τρεις ακέραιους αριθμούς
public int Add(int a, int b, int c)
{
return a + b + c;
}

// Υπερφόρτωση της μεθόδου Add για δεκαδικούς αριθμούς
public double Add(double a, double b)
{
return a + b;
}
}

class Program
{
static void Main()
{
Calculator calc = new Calculator();

Console.WriteLine(calc.Add(2, 3)); // Καλεί την πρώτη έκδοση (2 αριθμοί)
Console.WriteLine(calc.Add(2, 3, 4)); // Καλεί τη δεύτερη έκδοση (3 αριθμοί)
Console.WriteLine(calc.Add(2.5, 3.5)); // Καλεί την τρίτη έκδοση (δεκαδικοί αριθμοί)
}
}

Αναλυτική Εξήγηση:

Στο παραπάνω παράδειγμα:

  1. Η κλάση Calculator έχει τρεις εκδόσεις της μεθόδου Add:
    • Μία που προσθέτει δύο ακέραιους αριθμούς.
    • Μία που προσθέτει τρεις ακέραιους αριθμούς.
    • Μία που προσθέτει δύο δεκαδικούς αριθμούς.
  2. Η C# καταλαβαίνει ποια έκδοση της μεθόδου πρέπει να καλέσει, ανάλογα με τον αριθμό και τον τύπο των παραμέτρων που περνάς.

Αυτός είναι ο στατικός πολυμορφισμός, επειδή η απόφαση για το ποια έκδοση της μεθόδου θα κληθεί γίνεται κατά την μεταγλώττιση (compile time).

Αποτέλεσμα:





2. Δυναμικός Πολυμορφισμός (Dynamic Polymorphism)

Ο δυναμικός πολυμορφισμός επιτρέπει στις υποκλάσεις να παρέχουν τις δικές τους εκδόσεις των μεθόδων της βασικής κλάσης. Αυτό επιτυγχάνεται με τη χρήση εικονικών μεθόδων (virtual) και προκαθορισμένων μεθόδων (override). Η απόφαση για το ποια μέθοδος θα κληθεί γίνεται κατά την εκτέλεση του προγράμματος (runtime).

Παράδειγμα Δυναμικού Πολυμορφισμού:

Ας φτιάξουμε ένα παράδειγμα με ζώα και διαφορετικούς ήχους που κάνουν.

// Βασική κλάση Animal
class Animal
{
public string Name { get; set; }

// Εικονική μέθοδος που μπορεί να υπερκαλυφθεί από τις υποκλάσεις
public virtual void MakeSound()
{
Console.WriteLine($"{Name} κάνει έναν γενικό ήχο.");
}
}

// Υποκλάση Dog που υπερκαλύπτει τη μέθοδο MakeSound
class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} γαβγίζει!");
}
}

// Υποκλάση Cat που υπερκαλύπτει τη μέθοδο MakeSound
class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine($"{Name} νιαουρίζει!");
}
}

class Program
{
static void Main()
{
// Χρησιμοποιούμε την κλάση Animal αλλά με διαφορετικές υποκλάσεις
Animal myDog = new Dog { Name = "Rex" };
Animal myCat = new Cat { Name = "Milo" };

// Αν και ο τύπος είναι Animal, η μέθοδος που καλείται είναι από την αντίστοιχη υποκλάση
myDog.MakeSound(); // Καλεί τη μέθοδο MakeSound της κλάσης Dog
myCat.MakeSound(); // Καλεί τη μέθοδο MakeSound της κλάσης Cat
}
}

Αναλυτική Εξήγηση:

  1. Η βασική κλάση Animal: Έχει μια εικονική μέθοδο MakeSound() που μπορεί να υπερκαλυφθεί από τις υποκλάσεις. Εδώ, η MakeSound() εκτυπώνει ότι το ζώο κάνει έναν γενικό ήχο.
  2. Υποκλάση Dog: Υπερκαλύπτει τη μέθοδο MakeSound() και εκτυπώνει ότι ο σκύλος γαβγίζει.
  3. Υποκλάση Cat: Υπερκαλύπτει τη μέθοδο MakeSound() και εκτυπώνει ότι η γάτα νιαουρίζει.
  4. Δυναμικός Πολυμορφισμός:
    • Δημιουργούμε αντικείμενα των τύπων Dog και Cat, αλλά τα αποθηκεύουμε σε μεταβλητές τύπου Animal.
    • Όταν καλούμε τη μέθοδο MakeSound(), το πρόγραμμα καταλαβαίνει κατά την εκτέλεση ποια έκδοση της μεθόδου να καλέσει, ανάλογα με την υποκλάση (Dog ή Cat).

Αποτέλεσμα:

Rex γαβγίζει!
Milo νιαουρίζει!

Πότε να χρησιμοποιείς τον Δυναμικό Πολυμορφισμό;

Ο δυναμικός πολυμορφισμός είναι χρήσιμος όταν έχεις μια κοινή βασική κλάση αλλά θέλεις οι υποκλάσεις να έχουν διαφορετική συμπεριφορά για κάποιες μεθόδους. Αυτό σου επιτρέπει να χειρίζεσαι αντικείμενα γενικά (π.χ., Animal), αλλά να εκτελούν τη σωστή συμπεριφορά ανάλογα με την υποκλάση τους (π.χ., Dog, Cat).

Παράδειγμα πραγματικής χρήσης:

Σκέψου ένα παιχνίδι με χαρακτήρες. Έχεις έναν βασικό χαρακτήρα (π.χ., Character), και κάθε τύπος χαρακτήρα (π.χ., Warrior, Mage) έχει διαφορετικές ικανότητες. Χρησιμοποιώντας τον δυναμικό πολυμορφισμό, μπορείς να φτιάξεις έναν γενικό κώδικα που δουλεύει με όλους τους χαρακτήρες, αλλά κάθε χαρακτήρας εκτελεί τη δική του έκδοση των ικανοτήτων του.

abstract class Character
{
public string Name { get; set; }
public abstract void Attack(); // Αφηρημένη μέθοδος για επίθεση
}

class Warrior : Character
{
public override void Attack()
{
Console.WriteLine($”{Name} κάνει επίθεση με σπαθί!”);
}
}

class Mage : Character
{
public override void Attack()
{
Console.WriteLine($”{Name} ρίχνει μαγικό ξόρκι!”);
}
}

class Program
{
static void Main()
{
Character warrior = new Warrior { Name = “Arthur” };
Character mage = new Mage { Name = “Merlin” };

    warrior.Attack();  // Καλεί τη μέθοδο Attack της υποκλάσης Warrior
    mage.Attack();     // Καλεί τη μέθοδο Attack της υποκλάσης Mage
}

}

αποτέλεσμα:

Arthur κάνει επίθεση με σπαθί!
Merlin ρίχνει μαγικό ξόρκι!

Συμπεράσματα για τον Πολυμορφισμό:

  1. Στατικός Πολυμορφισμός (Static Polymorphism): Επιτυγχάνεται με την υπερφόρτωση μεθόδων. Η απόφαση για ποια μέθοδο θα κληθεί γίνεται κατά τη μεταγλώττιση (compile time).
  2. Δυναμικός Πολυμορφισμός (Dynamic Polymorphism): Επιτυγχάνεται με εικονικές και προκαθορισμένες μεθόδους. Η απόφαση για ποια μέθοδο θα κληθεί γίνεται κατά την εκτέλεση (runtime).

Πλεονεκτήματα του Πολυμορφισμού:

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

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

1. Η σημασία της χρήσης του virtual και override

  • Η λέξη-κλειδί virtual χρησιμοποιείται στη βασική κλάση για να δηλώσει ότι η μέθοδος μπορεί να υπερκαλυφθεί από τις υποκλάσεις.
  • Η λέξη-κλειδί override χρησιμοποιείται στην υποκλάση για να δηλώσεις ότι υπερκαλύπτεις τη μέθοδο της βασικής κλάσης με μια δική σου υλοποίηση.

Χωρίς το virtual στη βασική κλάση, δεν μπορείς να υπερκαλύψεις τη μέθοδο, και χωρίς το override στην υποκλάση, δεν μπορείς να δηλώσεις ότι αλλάζεις τη συμπεριφορά της μεθόδου της βασικής κλάσης.

Παράδειγμα:

class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Το ζώο κάνει έναν γενικό ήχο.");
    }
}

class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Ο σκύλος γαβγίζει!");
    }
}

2. Κλήση της βασικής έκδοσης της μεθόδου με το base

Μπορείς να καλέσεις την αρχική έκδοση της μεθόδου από τη βασική κλάση χρησιμοποιώντας το base μέσα από την υπερκαλυμμένη μέθοδο της υποκλάσης. Αυτό είναι χρήσιμο αν θέλεις να προσθέσεις λειτουργίες στη μέθοδο, αντί να την αντικαταστήσεις πλήρως.

Παράδειγμα με χρήση του base:

csharp