CodeGym /Kurslar /C# SELF /Polimorfizm anlayışı və onun OOP-də rolu

Polimorfizm anlayışı və onun OOP-də rolu

C# SELF
Səviyyə , Dərs
Mövcuddur

1. Giriş

Təsəvvür elə: evdə televizorun, musiqi mərkəzinin, kondisionerin və smart lampanın var. Hərəsinin öz pultu var. Televizoru yandırmaq üçün televizorun pultunu götürürsən və "Yandır" düyməsini basırsan. Musiqi mərkəzini yandırmaq üçün onun pultunu götürürsən və yenə də "Yandır" düyməsini basırsan. Təsəvvür elədin? Xaos, düzdü?

Bəs necə olardı, əgər sənin universal pultun olsaydı? Onu götürürsən, "Yandır" düyməsini basırsan və o, bir növ sehrli şəkildə başa düşür ki, hansı cihazı yandırmaq lazımdır və ona düzgün komanda göndərir. Və özü pult bilmir, dəqiq necə televizor yandırılır və ya dəqiq necə musiqi mərkəzi yandırılır. Sadəcə bilir ki, bütün bu cihazlarda ümumi "yandırma funksiyası" var.

Bax bu, proqramlaşdırmada polimorfizm analoqudur!

Polimorfizm

"Polimorfizm" sözü yunan dilindən gəlir: poly (çox) və morph (forma). Sözün düz mənası "çox formalar" deməkdir. OOP kontekstində bu, obyektin müxtəlif formalar ala bilməsi və ya daha dəqiq desək, eyni metodun fərqli obyektlərdə çağırıldıqda fərqli davranması deməkdir.

C#-da polimorfizm əsasən inheritance və virtualoverride metodlarının istifadəsi ilə əldə olunur.

Polimorfizmin əsas ideyası yuxarıya çevrilmə (upcasting) anlayışıdır. Bu nədir?

Gəlin, AnimalDog, Cat iyerarxiyamıza qayıdaq. Bilirik ki, it heyvandır. Pişik heyvandır. Bu "is-a" münasibətidir — inheritance-in əsas prinsipi.


public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Hər hansı bir səs...");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Hav-hav!");
    }
}

public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Miyau!");
    }
}

"is-a" münasibəti sayəsində biz törəmə sinifin obyektini əsas sinifin dəyişəninə təyin edə bilirik. Buna yuxarıya çevrilmə (upcasting) deyilir, çünki biz obyekti daha ümumi, əsas tipə "qaldırırıq". C# compiler bunu avtomatik edir:


// Bizdə konkret bir it var
Dog myDog = new Dog();

// Dog obyektini Animal tipli dəyişənə təyin edə bilərik!
// Bu, yuxarıya çevrilmədir (upcasting).
// Sağda - konkret Dog, solda - daha ümumi Animal.
Animal generalAnimal = myDog;

// İndi isə MakeSound() çağırmaq olar!
generalAnimal.MakeSound(); // Çap edir "Hav-hav!" (Yox "Hər hansı bir səs...", məhz "Hav-hav!")               

Burda nə baş verdi?

  1. Animal generalAnimal = myDog; yazanda yeni heyvan yaratmadıq. Sadəcə myDog obyektini (əslində Dog olan) "Animal" adlanan "qutunun" içinə qoyduq.
  2. Kompilyasiya mərhələsində (kod byte-code-a çevriləndə) generalAnimal dəyişəni Animal tipində olur. Ona görə compiler "bilir" ki, yalnız Animal sinifində olan metod və property-ləri çağırmaq olar.
  3. Ən maraqlısı: runtime zamanı (proqram işləyəndə) generalAnimal.MakeSound() çağıranda .NET runtime dəyişənin tipinə yox, obyektin əsl tipinə baxır (Dog)! Və MakeSound() metodu virtual olaraq Animal-da və override olaraq Dog-da işarələndiyi üçün məhz Dog-un implementasiyası çağırılır.

Bu magiya, yəni metodun davranışı obyektin əsl tipi runtime zamanı ilə müəyyənləşəndə, dəyişənin tipi ilə yox, buna dinamik dispatch və ya runtime polimorfizmi deyilir.

Təsəvvür elə bir qutu var: üstündə "Meyvə" yazılıb (bizim Animal dəyişən tipi). İçinə alma qoyursan (bizim Dog obyektimiz). "Meyvə, səs çıxart!" deyəndə (MakeSound() çağırışı), qutu bilir ki, içində alma var və "xırç" səsi çıxarır, ümumi "meyvə" səsi yox.

2. Polimorfizmin işdə nümayişi

Polimorfizmi ən yaxşı başa düşmək üçün onu işdə görmək lazımdır, xüsusən də bir yox, bir neçə obyekt olanda.

Gəlin, "Mənim kiçik zooparkım" tətbiqimizi genişləndirək. Təsəvvür elə, baytarlıq klinikasında işləyirsən və müxtəlif heyvanlar müayinə üçün gətirilir. İstəyirik ki, hər müayinə öz qaydasına görə getsin, amma müayinə çağırışı üçün kod universal olsun.

Əvvəlcə əsas Animal sinifinə və törəmə DogCat siniflərinə yeni virtual Examine() metodu əlavə edək:


public class Animal
{
    public string Name { get; set; }
    public Animal(string name) { Name = name; }
    public virtual void MakeSound() { Console.WriteLine($"{Name} hər hansı bir səs çıxarır..."); }
    public virtual void Examine()
    {
        Console.WriteLine($"{Name} müayinəsi:");
        Console.WriteLine("  - Nəfəs yoxlanışı.");
        Console.WriteLine("  - Temperatur ölçülməsi.");
    }
}

public class Dog : Animal
{
    public Dog(string name) : base(name) { }
    public override void MakeSound() { Console.WriteLine($"{Name} deyir: Hav!"); }
    public override void Examine()
    {
        base.Examine();
        Console.WriteLine("  - Dişlərin yoxlanması.");
        Console.WriteLine("  - Quduzluğa qarşı peyvənd.");
    }
}

public class Cat : Animal
{
    public Cat(string name) : base(name) { }
    public override void MakeSound() { Console.WriteLine($"{Name} deyir: Miyau!"); }
    public override void Examine()
    {
        base.Examine();
        Console.WriteLine("  - Dırnaqların yoxlanması.");
        Console.WriteLine("  - Qurd dərmanı.");
    }
}

İndi bizdə it və pişik üçün xüsusi Examine() metodları var. base.Examine(); çağırışına fikir ver: bu, əvvəlcə əsas Animal sinifindən ümumi müayinə addımlarını icra etməyə, sonra isə hər heyvana spesifik addımlar əlavə etməyə imkan verir. Çox rahatdır!

İndi baytarlıq klinikamızı təsəvvür edək. Müayinə üçün gələn heyvanların siyahısı var:


class Program
{
    static void Main()
    {
        Animal[] animals = {
            new Dog("Reks"),
            new Cat("Murka"),
            new Animal("Dovshan")
        };

        Console.WriteLine("--- Heyvanların müayinəsi ---");
        foreach (Animal animal in animals)
        {
            animal.Examine();
            Console.WriteLine();
        }

        Console.WriteLine("--- Səslər saatı ---");
        foreach (Animal animal in animals)
            animal.MakeSound();
    }
}

Nəticə:


--- Heyvanların müayinəsi ---
Reks müayinəsi:
  - Nəfəs yoxlanışı.
  - Temperatur ölçülməsi.
  - Dişlərin yoxlanması.
  - Quduzluğa qarşı peyvənd.

Murka müayinəsi:
  - Nəfəs yoxlanışı.
  - Temperatur ölçülməsi.
  - Dırnaqların yoxlanması.
  - Qurd dərmanı.

Dovshan müayinəsi:
  - Nəfəs yoxlanışı.
  - Temperatur ölçülməsi.

--- Səslər saatı ---
Reks deyir: Hav!
Murka deyir: Miyau!
Dovshan hər hansı bir səs çıxarır...

Bax budur, polimorfizmin gücü! Eyni foreach (Animal animal in animals) dövrünü yazdıq və hər iterasiyada eyni animal.Examine() (və ya animal.MakeSound()) metodunu çağırdıq. Amma hər dəfə düzgün, spesifik implementasiya işə düşdü — it, pişik və ya ümumi heyvan üçün. Kod sadə, təmiz və universal qalır, implementasiya detalları isə hər sinifin içində gizlənib.

Sabah bizə hamster (Hamster) gətirsələr, sadəcə Hamster : Animal sinifini yaradıb Examine() metodunu override edirik və o, heç bir dəyişiklik etmədən ümumi müayinə sistemimizdə işləyəcək! Bax, polimorfizmin əsl gücü budur.

3. Polimorfizmin OOP-də rolu

Polimorfizm təkcə gözəl söz və ya kod fəndi deyil. Bu, proqramlarınızı çevik, genişlənə bilən və asan dəstəklənən edən fundamental prinsiptir. Gəlin baxaq, o, hansı rolu oynayır:

Çeviklik və genişlənə bilmək (Open/Closed Principle)

Bəlkə də ən əsas üstünlük budur. Polimorfizm imkan verir ki, kodun genişlənməyə açıq, dəyişməyə qapalı olsun. Bu nə deməkdir?

  • Genişlənməyə açıq: Yeni heyvan tipləri əlavə edə bilərsən (məsələn, Bird, Fish, Hamster), sadəcə yeni sinif yaradıb Animal-dan inherit edib lazım olan metodları override edirsən.
  • Dəyişməyə qapalı: Animal[] ilə işləyən mövcud kodu dəyişməyə ehtiyac yoxdur. foreach dövrü, hansı ki, animal.Examine() çağırır, tamamilə dəyişməz qalacaq, neçə yeni heyvan növü əlavə etsən də.

Təsəvvür elə, polimorfizm olmasaydı, nə qədər if-else if və ya switch operatoru yazmalı və daim dəyişməli olardın! Əsl başağrısı olardı.

Kodun sadələşməsi

Polimorfizm müxtəlif, amma əlaqəli obyektlər kolleksiyası ilə işləyən kodu xeyli sadələşdirir. Hər obyektin tipini yoxlayıb uyğun metodu çağırmaq əvəzinə, sadəcə əsas sinifin ümumi metodunu çağırırsan və sistem özü hansı implementasiyanı çağıracağını müəyyən edir.

Bu, müxtəlif qapılar üçün bir "Aç" düyməsinin olması kimidir: adi, sürüşən, fırlanan. Hər dəfə dartmaq, itələmək və ya çevirmək lazım deyil — sadəcə "aç", qapı özü necə lazımdırsa elə açılır.

Abstraksiya

Polimorfizm abstraksiya ilə sıx bağlıdır, bu da OOP-nin başqa bir sütunudur və növbəti dərslərdə ətraflı danışacağıq. O, sənə obyektin "nə edir" (məsələn, "səs çıxarır", "müayinə olunur") üzərində fokuslanmağa imkan verir, "necə edir" yox. Sən "heyvan" abstrakt ideyası ilə işləyirsən, konkret "it" və ya "pişik" yox. Bu, yüksək səviyyəli, təmiz kod yazmağa imkan verir, hansı ki, aşağı səviyyəli implementasiya detallarından asılı deyil.

Kodun təkrar istifadəsi

Inheritance özü kodun təkrar istifadəsini təmin edir, əsas sinifin property və metodlarını yenidən istifadə etməyə imkan verir, polimorfizm isə bunu daha da gücləndirir. O, universal alqoritmlər və data strukturlar yaratmağa imkan verir (məsələn, Animal[]), hansı ki, əsas sinifdən inherit edən istənilən obyektlə işləyə bilər, hər törəmə tip üçün logikanı təkrarlamadan.

Əslində, polimorfizm kodunu daha "pro" və dəyişikliklərə hazır edir, bu isə real development-də çox vacibdir, çünki tələblər daim dəyişir.

4. Faydalı nüanslar

Polimorfizm sxemi


classDiagram
    class Animal {
        +MakeSound()
    }
    class Dog {
        +MakeSound()
    }
    class Cat {
        +MakeSound()
    }
    class Parrot {
        +MakeSound()
    }

    Animal <|-- Dog
    Animal <|-- Cat
    Animal <|-- Parrot
Polimorfizm üçün sinif iyerarxiyası sxemi

MakeSound() metodunu Animal tipli istinadla çağıranda — dəyişəndə hansı obyekt varsa, onun sinifinin metodu çağırılacaq.

Polimorfizmin massiv və kolleksiyalarla istifadəsi

Çox rast gəlinən tapşırıq — müxtəlif tiplərdən olan obyekt kolleksiyasını dövr edib hamısında eyni logikanı işə salmaqdır.


// Bizim əsas məşq tətbiqində:
Animal[] zoo = { new Dog("Sharik"), new Cat("Barsik"), new Parrot("Kesha") };

foreach (Animal animal in zoo)
{
    animal.MakeSound();
}

Real layihələrdə belə, məsələn, event processing, ekranda çəkmə (hər fiqur özünə görə), ödəniş processing (fərqli kart və servis tipləri) həyata keçirilir.

Polimorfizm necə işləyir

Obyektin tipi Dəyişənin tipi Hansı metod çağırılır?
Dog
Animal
Dog.MakeSound()
Cat
Animal
Cat.MakeSound()
Parrot
Animal
Parrot.MakeSound()
Animal
Animal
Animal.MakeSound()

Əsas odur ki, metod virtual və ya abstract əsas sinifdə, override isə törəmədə olmalıdır!

5. Polimorfizmlə işləyərkən tipik səhvlər

Real həyatda tələbələrdə tez-tez belə səhvlər olur:

  • Əsas sinifdə metodu virtual etmirsən — və polimorfizm əvəzinə həmişə bir variant işləyir.
  • Qarışdırırsan: dəyişən üçün hansı sinif lazımdır? Həmişə yadda saxla: dəyişənin tipi var (məsələn, Animal), ora qoyduğun obyekt isə instance-dır (məsələn, new Dog()).
  • Əsas sinifdə olmayan yeni üzvləri əsas tipli dəyişənlə istifadə etməyə çalışırsan. Məsələn:

Animal pet = new Dog();
pet.Bark(); // Səhv! Animal-da Bark() yoxdur

Bununla necə davranmalı? Əgər çox lazımdırsa, type casting istifadə et, amma kodunu elə qur ki, əsas sinifdə olan metodlarla işləyəsən.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION