1. Giriş
Polimorfizm — obyekt yönümlü proqramlaşdırmanın (irsiyyət və enkapsulyasiya ilə yanaşı) üç əsas konsepsiyasından biridir. Söz etimoloji olaraq yunan dilində «poly» (çox) və «morph» (forma) sözlərindən gəlir. Proqramlaşdırmada bu belə deməkdir: bir interfeys — çox implementasiya.
Tərif
Polimorfizm — müxtəlif siniflərin obyektlərinin eyni mesajlara (metod çağırışlarına) fərqli reaksiya vermək qabiliyyətidir.
Yəni, əgər sizdə makeSound() metodu varsa, onu istənilən heyvan üçün çağıra bilərsiniz, amma pişik «miyau» edəcək, it «haflayacaq», inək isə «möö» edəcək. Proqramçı üçün bu sadəcə animal.makeSound() çağırışıdır, lakin əslində nə baş verəcəyi — həmin dəyişənin arxasında hansı obyektin durmasından asılıdır.
Həyatdan analogiya
Təsəvvür edin, evdə televizorun pultu var və elə həmin pultla səsucaldanları, proyektoru, hətta qəhvə aparatını idarə edə bilərsiniz. Siz «Yandır» — turnOn() düyməsinə basırsınız və hər cihaz özünəməxsus reaksiya verir. Əsas olan — hamısında «yandırma» düyməsi var, amma implementasiya fərqlidir.
Java üzrə nümunə
class Animal {
void makeSound() {
System.out.println("Hər hansı bir səs...");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Haf!");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Miyau!");
}
}
class Cow extends Animal {
@Override
void makeSound() {
System.out.println("Mööö!");
}
}
İndi bunu belə edə bilərik:
Animal animal1 = new Dog();
Animal animal2 = new Cat();
Animal animal3 = new Cow();
animal1.makeSound(); // Haf!
animal2.makeSound(); // Miyau!
animal3.makeSound(); // Mööö!
Nəzərə alın: bütün dəyişənlərin tipi Animal-dır, amma çağırışın nəticəsi obyektin faktiki tipindən asılıdır.
2. Polimorfizmin növləri
Java-da (və əksər OOP dillərində) polimorfizmin iki əsas növü fərqləndirilir:
Kompilyator (statik) polimorfizm — metodların aşırı yüklənməsi (overloading)
Bu, eyni sinifdə adı eyni, parametrləri fərqli olan bir neçə metod olduqda baş verir. Kompilyator verilən arqumentlərə əsasən hansı metodun çağırılacağını özü müəyyən edir.
Nümunə (bir az qabağa baxaq — ətraflı izah növbəti mühazirədə olacaq):
class Printer {
void print(int x) {
System.out.println("Ədəd: " + x);
}
void print(String s) {
System.out.println("Sətir: " + s);
}
}
İcra zamanı (dinamik) polimorfizm — metodların yenidən təyin edilməsi (overriding)
Bu zaman metod baza sinifdə müəyyən edilir, sonra isə törəmə siniflərdə yenidən təyin olunur. Hansı metodun çağırılacağı — proqramın icrası zamanı obyektin faktiki tipindən asılı olaraq müəyyən edilir.
Nümunə — yuxarıdakı heyvanlar nümunəsinə baxın.
3. Polimorfizm nə üçün lazımdır?
Polimorfizm — təkcə müsahibələr üçün gözəl bir söz deyil. Bu, kodunuzu çevik, genişlənə bilən və dəstəklənməsi asan edən bir alətdir.
Kodun universallığı
İcra detallarını düşünmədən baza tipli obyektlərlə işləyən kod yaza bilərsiniz. Məsələn, əgər sizdə heyvanların siyahısı varsa, onun üzərindən keçib hər biri üçün makeSound() çağıra bilərsiniz — pişiktir, itdir, bunu düşünməyinizə ehtiyac yoxdur.
Animal[] animals = { new Dog(), new Cat(), new Cow() };
for (Animal animal : animals) {
animal.makeSound(); // Hər dəfə "doğru" metod çağırılacaq
}
Asan genişləndirmə
Əgər sabah rəisinız desə: «Gəlin tutuquşu əlavə edək!», sadəcə yeni Parrot extends Animal sinfini yazıb onu massivə əlavə edirsiniz. Qalan bütün kod dəyişməz qalır. Bu — genişləndirməyə açıq, dəyişikliklərə qapalı olmaq deməkdir (SOLID-in OCP prinsipi).
Arxitekturanın sadələşdirilməsi
Müxtəlif hissələrin konkret implementasiyaları düşünmədən abstraksiyalar (baza siniflər və ya interfeyslər) vasitəsilə qarşılıqlı əlaqədə olduğu mürəkkəb sistemlər qura bilərsiniz. Bu, vaxtı, əsəbləri və qəhvəni qənaət edir.
4. Açar anlayışlar: istinad tipi və faktiki tip
Dəyişənin istinad tipi
Animal animal = new Dog(); yazdığınız zaman animal dəyişəninin istinad tipi Animal olur, yəni kompilyator «düşünür» ki, bu bir heyvandır və yalnız Animal sinfində elan olunmuş metodlara icazə verir.
Obyektin faktiki (real) tipi
Amma yaddaşda əslində Dog tipli obyekt yerləşir. Məhz o, makeSound() çağırıldıqda hansı metodun işə düşəcəyini müəyyən edir.
İllustrasiya
Animal animal = new Dog();
animal.makeSound(); // Dog.makeSound() çağırılacaq, Animal.makeSound() yox
Vacibdir! Baza tipli istinad (Animal) vasitəsilə yalnız Dog-da olan, amma baza sinifdə elan olunmamış metodları çağıra bilməyəcəksiniz.
Gec (dinamik) bağlama
Bu, icra zamanı baş verən «sehr»dir: metodu baza tipli istinad vasitəsilə çağırdığınızda JVM obyektin faktiki tipinə baxır və «doğru» implementasiyanı çağırır. Bu, polimorfizmin iş başında olmağıdır.
5. Praktik nümunə: tətbiqdə polimorfizm
Gəlin tədris tətbiqimizi inkişaf etdirməyə davam edək. Tutaq ki, sadə bir zoopark simulyasiyası yazırıq. Bizdə Animal adlı baza sinif və onun bir neçə törəməsi var. Bütün heyvanların «səs çıxarmasını» istəyirik, amma hər heyvan tipi üçün ayrıca kod yazmaq istəmirik.
Addım 1: Baza sinif və törəmələr
class Animal {
void makeSound() {
System.out.println("Hər hansı bir səs...");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Haf!");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Miyau!");
}
}
Addım 2: Heyvanlar massivi
Animal[] zoo = { new Dog(), new Cat(), new Animal() };
Addım 3: Dövr və metod çağırışı
for (Animal animal : zoo) {
animal.makeSound();
}
İcra nəticəsi:
Haf!
Miyau!
Hər hansı bir səs...
Nəzərə alın: massivdə əvvəlcədən kimin olduğunu bilmirik — proqram özü düzgün metodu çağırır.
6. Polimorfizmin sxematik təsviri
. Animal (makeSound)
/ \
Dog Cat
(makeSound) (makeSound)
Animal animal = new Dog();
animal.makeSound(); // --> Dog.makeSound()
Animal animal = new Cat();
animal.makeSound(); // --> Cat.makeSound()
7. Başqa bir nümunə: real vəzifədə polimorfizm
Tutaq ki, siz şirkət əməkdaşlarının idarə olunması üçün proqram yazırsınız. Sizdə Employee adlı baza sinif və iki törəmə var: Manager və Developer. Bütün əməkdaşlar işləyə bilər — work(), amma bunu müxtəlif cür edirlər.
class Employee {
void work() {
System.out.println("İşçi işləyir.");
}
}
class Manager extends Employee {
@Override
void work() {
System.out.println("Menecer iclas keçirir.");
}
}
class Developer extends Employee {
@Override
void work() {
System.out.println("Tərtibatçı kod yazır.");
}
}
İndi bunu belə edə bilərsiniz:
Employee[] staff = { new Manager(), new Developer(), new Employee() };
for (Employee emp : staff) {
emp.work();
}
Nəticə:
Menecer iclas keçirir.
Tərtibatçı kod yazır.
İşçi işləyir.
8. Polimorfizm nə zaman işləmir
Polimorfizm yalnız baza sinifdə elan edilmiş metodlar üçün işləyir. Əgər törəmə sinifdə unikal metod varsa, onu baza tipli istinad vasitəsilə görməyəcəksiniz.
class Dog extends Animal {
void fetchStick() {
System.out.println("İt çubuğu gətirir!");
}
}
Animal animal = new Dog();
// animal.fetchStick(); // Kompilyasiya xətası! Belə bir metod Animal vasitəsilə görünmür
Xüsusi metodu çağırmaq üçün dəyişəni lazım olan tipə çevirmək lazımdır:
if (animal instanceof Dog) {
((Dog) animal).fetchStick();
}
Amma bu artıq başqa bir hekayədir — əsas fikir: polimorfizm vasitəsilə yalnız baza sinifdə elan olunan metodlara çıxış var.
9. Polimorfizmlə işləyərkən tipik səhvlər
Səhv № 1: Baza tipli istinad vasitəsilə törəmə sinfin bütün metodlarının əlçatan olacağını gözləmək. Əslində, yalnız baza sinifdə elan olunan metodlar əlçatandır.
Səhv № 2: Metodu yenidən təyin edərkən @Override annotasiyasından istifadə etməmək. Onsuz səhv imza ilə metod yaza bilərsiniz və bu halda polimorfizm işləməyəcək (baza sinfinin metodu yenidən təyin olunmayacaq).
Səhv № 3: Törəmə sinfə məxsus xüsusi metodu tip çevirməsi etmədən çağırmağa cəhd. Kompilyator buna icazə verməyəcək, çünki istinadı göstərən obyektin kim olduğunu bilmir.
Səhv № 4: overloading (aşırı yüklənmə) ilə overriding (yenidən təyin etmə) arasında qarışıqlıq. Overloading — eyni sinifdə adı eyni, parametrləri fərqli bir neçə metodun olmasıdır. Overriding — metodun davranışını törəmə sinifdə dəyişdirməkdir.
GO TO FULL VERSION