1. Java‑da irsi almanın məhdudiyyətləri
Yalnız siniflərin tək irsi alınması. Java‑da bir sinif yalnız bir başqa sinifdən irsi ala bilər. Buna tək irsi alma deyilir. Məsələn, belə — olar:
class Animal { }
class Dog extends Animal { }
Amma belə — olmaz:
class Animal { }
class Robot { }
// XƏTA! Java siniflərin çoxlu irs almasını dəstəkləmir
class RoboDog extends Animal, Robot { }
Belə sinif elan etməyə çalışsanız, kompilyator belə deyəcək: "class RoboDog cannot extend multiple classes". Niyə? Çünki çoxlu irsi alma qeyri‑müəyyənliklərə gətirib çıxarır: əgər hər iki valideyn eyni siqnaturlu metodu saxlayırsa, hansını istifadə etməli? Bu məşhur «romb problemi»dir (diamond problem).
İnterfeyslər Java‑da istənilən sayda reallaşdırıla bilər, amma biz hələ onları keçməmişik. Sonra danışacağıq.
Konstruktorlar irsi alınmır. Hətta valideyn sinifdə rahat konstruktorunuz olsa belə, bu konstruktor avtomatik olaraq törəmə sinifdə yaranmayacaq. Valideynin konstruktorunu törəmə sinifin konstruktorunda açıq şəkildə super(...) vasitəsilə çağırmaq lazımdır.
Private üzvlər irsi alınmır. Valideynin bütün private sahələri və metodları törəmə sinifdə əlçatan deyil. Onlar obyektin «içində» mövcuddur, amma onlara birbaşa müraciət etmək mümkün deyil.
2. Kövrək iyerarxiya problemləri
Siniflər arasında sıx bağlılıq. Sinif iyerarxiyası yaratdığınız zaman törəmə siniflər valideyn sinfə möhkəm bağlanır. Baza sinfi dəyişsəniz, bu, onun bütün törəmələrinə təsir edə (hətta onları poza) bilər. Təsəvvür edin, sizdə Animal sinfi var və ondan Dog, Cat, Bird və daha onlarla sinif miras alır. Əgər siz Animal strukturunu dəyişsəniz (məsələn, konstruktora yeni məcburi parametr əlavə etsəniz), bütün törəmə sinifləri nəzərdən keçirib onların kodunu yeniləməli olacaqsınız. Bu, xüsusən böyük layihələrdə ağrılıdır.
«Qırılan» irsi alma problemi. Bəzən törəmə sinif təsadüfən baza sinfinin güvəndiyi davranışı dəyişdirə bilər. Məsələn, valideyn sinif başqa bir metodun içində öz metodunu çağırır, amma törəmə sinif həmin metodu yenidən təyin edib onun məntiqini dəyişir. Nəticədə valideyn sinif gözlənildiyi kimi işləmir.
class Animal {
void makeSound() {
System.out.println("Some sound");
}
void sleep() {
System.out.println("Animal is going to sleep...");
makeSound(); // Valideyn sinif öz metodunu çağırır
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Woof!");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.sleep();
}
}
Proqram nə çıxaracaq?
Animal is going to sleep...
Woof!
Valideyn sinif düşünürdü ki, makeSound() — onun öz reallaşdırmasıdır, amma əslində çağırılacaq versiya törəmə sinifdəndir! Əgər törəmə sinif metodu başqa məntiq ilə yenidən təyin edirsə, bu, gözlənilməz xətalara gətirə bilər.
3. Kövrək baza sinfi problemi (fragile base class problem)
Bu, böyük layihələrdə real problemdir. Baza sinfi dəyişsəniz (məsələn, sahə əlavə etsəniz, metodun reallaşdırmasını dəyişsəniz), bütün törəmə siniflərin davranışını pozmaq riskini daşıyırsınız. Bəzən bu dərhal üzə çıxmır və belə səhvi tapmaq saatlar, hətta günlər apara bilər.
İllüstrasiya: tutalım, sizdə Shape sinfi və onun draw() metodu var. Siz Shape sinfinə drawShadow() adlı yeni metod əlavə edirsiniz və o, draw() metodunu çağırır. Amma törəmələrdən biri (Circle) draw() metodunu yenidən təyin edib və indi drawShadow() metodu Circle üçün gözlənilməz davranışa səbəb ola bilər.
4. Sıx bağlılıq və refaktorinq çətinlikləri
Siniflər irsi alma ilə bağlandıqda, bir sinfin dəyişməsi asılılıqlar zəncirinin hamısına təsir edə bilər. Bu, kodu daha az çevik edir, refaktorinqi və genişləndirməni çətinləşdirir. Bəzən yeni funksionallıq əlavə etmək üçün bütöv iyerarxiyaları yenidən yazmaq lazım gəlir.
Real nümunə
class Vehicle { /* ... */ }
class Car extends Vehicle { /* ... */ }
class Bicycle extends Vehicle { /* ... */ }
class Bus extends Vehicle { /* ... */ }
Birdən tələbi gəlir: «Gəlin elektrik skuter əlavə edək!». Amma elektrik skuter — həm nəqliyyat vasitəsidir, həm də qadcet. Necə olsun? Əgər iyerarxiyanı genişləndirməyə başlasanız ki, bütün yeni mahiyyətləri sığdırasınız, o, tez bir zamanda idarəolunmaz hala gələr.
5. Məntiqi əlaqə olmadan kodun yenidən istifadə edilməsi problemi
Çox vaxt yeni başlayan (və təkcə onlar yox) proqramçılar siniflər arasında «is-a» (is-a) münasibəti olmasa belə, kodu yenidən istifadə etmək üçün irsi almanı tətbiq edirlər. Bu isə yanlış arxitekturaya gətirir.
Yanlış irsi alma nümunəsi
class DatabaseUtils {
void connect() { /* ... */ }
void disconnect() { /* ... */ }
}
class User extends DatabaseUtils { // İstifadəçi verilənlər bazası utility-si deyil!
String name;
}
Daha doğru yol kompozisiyadır: DatabaseUtils ayrıca sinif olsun və lazım olan yerlərdə onun metodları çağırılsın, ondan irs almaq yox.
6. İrsi almaya alternativlər
Kompozisiya (has-a)
Obyekt başqa bir obyekti «saxlayırsa», kompozisiyadan istifadə edin. Məsələn, Car sinfinin Engine sahəsi ola bilər:
class Engine { /* ... */ }
class Car {
private Engine engine;
// ...
}
Deleqasiya
Sinfi genişləndirmək əvəzinə, tapşırığın icrasını başqa bir obyektə deleqasiya edin. Bu, çevikliyi qoruyur və komponentlər arasındakı bağlılığı azaldır.
İnterfeyslər
Java‑da sinif istənilən sayda interfeysi reallaşdıra bilər. Bu, sərt iyerarxiya olmadan davranışları çevik şəkildə birləşdirməyə imkan verir. İnterfeyslərə sonra qayıdacağıq.
İrsi almanı nə zaman istifadə etmək olar?
İrsi almanı yalnız siniflər arasında aydın «is-a» (is-a) münasibəti olduqda istifadə edin:
- Pişik heyvandır (Cat extends Animal)
- Dairə fiqurdur (Circle extends Shape)
- Admin istifadəçidir (Admin extends User)
Yalnız kodu yenidən istifadə etmək üçün irsi almanı işlətməyin — bunun üçün kompozisiya və deleqasiya var.
7. Bir neçə praktik nümunə
Nümunə: həddən artıq mürəkkəb iyerarxiya
class Animal { }
class Mammal extends Animal { }
class Cat extends Mammal { }
class PersianCat extends Cat { }
class SuperPersianCat extends PersianCat { }
İyerarxiyanız üç səviyyədən dərinləşirsə — düşünün: bəlkə dayanmağın vaxtıdır? Çox dərin iyerarxiyalar kodun anlaşılmasını və dəstəyini çətinləşdirir.
Nümunə: çox «yastı» iyerarxiya
class Animal { }
class Cat extends Animal { }
class Dog extends Animal { }
class Bird extends Animal { }
class Fish extends Animal { }
class Spider extends Animal { }
class Platypus extends Animal { }
class Dragon extends Animal { }
Onlarla alt sinifiniz varsa və hər biri yalnız bir metodla fərqlənirsə, yəqin ki, interfeyslərdən və ya kompozisiyadan istifadə etməyə dəyər.
8. İrsi almanın istifadəsində tipik səhvlər
Səhv № 1: «is-a» münasibəti olmadan irsi alma.
Əgər törəmə sinif əslində valideynin növü deyilsə, arxitektura qeyri‑təbii olur və tez nəzarətdən çıxır. Məsələn, User sinfi, «rahat görünsə» belə, DatabaseUtils sinfindən irsi almamalıdır.
Səhv № 2: Müqaviləni dəyişdirərək metodları yenidən təyin etmək.
Əgər siz metodu yenidən təyin edib onun məntiqini elə dəyişirsinizsə ki, artıq valideynin gözləntilərinə cavab vermir, bu, gözlənilməz xətalara səbəb olacaq. Məsələn, baza sinfi draw() metodunun fiquru çəkəcəyini gözləyir, amma törəmə sinifdə o, qəfil təhlükəli yan təsirlər etməyə başlayırsa — bu, fəlakətdir.
Səhv № 3: Həddindən artıq dərin və ya həddindən artıq yastı iyerarxiyalar.
Çox dərin iyerarxiya kodun anlaşılmasını çətinləşdirir; çox yastı iyerarxiya isə təkrarlanmalara gətirir.
Səhv № 4: Dilin məhdudiyyətlərini dolanmaq cəhdləri.
Siniflərin çoxlu irs almasını «hacks» ilə (kopyala‑yapışdır, «utility» super‑siniflər) reallaşdırmağa çalışırlar ki, bu da xaosa gətirib çıxarır.
Səhv № 5: Kodu yenidən istifadə üçün irsi almanın kor‑koranə tətbiqi.
Çox vaxt siniflər arasında gözlənilməz əlaqələrə səbəb olur, testləşdirmə və dəstəyi çətinləşdirir. Kompozisiya və deleqasiyadan istifadə edin.
GO TO FULL VERSION