1. Giriş
Gəlin zooparkımıza qayıdaq. Bizdə Animal (Heyvan) adlı əsas class və onun törəmələri var: Dog (İt), Cat (Pişik), Fish (Balıq).
Əvvəlki dərslərdə Animal class-ına virtual metod MakeSound() əlavə etmişdik:
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
public virtual void MakeSound() // Virtual metod
{
Console.WriteLine("Hansısa heyvan səs çıxarır."); // Susmaya görə reallaşdırma
}
public void Sleep() // Adi metod
{
Console.WriteLine($"{Name} yatır.");
}
}
public class Dog : Animal
{
public override void MakeSound() // İt üçün səsi override edirik
{
Console.WriteLine("Hav-hav!");
}
}
public class Cat : Animal
{
public override void MakeSound() // Pişik üçün səsi override edirik
{
Console.WriteLine("Miyau!");
}
}
Bu əla işləyir! Dog və ya Cat yaradanda və MakeSound() çağıranda onların unikal səslərini eşidirik. Bəs sadəcə Animal yaratsaq nə olar?
Animal genericAnimal = new Animal();
genericAnimal.Name = "Naməlum varlıq";
genericAnimal.MakeSound(); // Çap edəcək: "Hansısa heyvan səs çıxarır."
Məntiqlidir. Amma bəzən elə olur ki, əsas class-ın susmaya görə reallaşdırması heç bir məna vermir. Bəs əgər Animal konkret heyvan yox, sadəcə konsepsiyadırsa? "Heyvan" özü konkret səs çıxarmır, bunu artıq konkret heyvan növləri edir. Və ya təsəvvür et ki, Shape (Fiqur) class-ımız var və onun CalculateArea() (SahəniHesabla) metodu var. Sadəcə Fiqur hansı sahəni hesablaya bilər? Heç birini! Sahə Dairədə, Kvadratda var, amma abstrakt "Fiqur"da yoxdur.
Belə hallarda, əsas class susmaya görə məntiqli reallaşdırma verə bilmirsə (və ya verməməlidir), amma bütün törəmə class-ları bu metodu reallaşdırmağa məcbur edirsə, köməyə abstrakt metodlar və abstrakt class-lar gəlir.
2. Abstrakt class-lar: Layihə hələ ev deyil
Təsəvvür elə ki, sən memarsan və tipik evin layihəsini çəkirsən. Amma sadəcə ev yox, "Konseptual Ev". Onun ümumi cizgiləri var: divarlar, dam, fundament. Amma hələ bilmirik, bu birmərtəbəli kottec olacaq, yoxsa çoxmərtəbəli göydələn. Layihənin bəzi hissələri konkret olacaq (məsələn, birinci mərtəbənin tavan hündürlüyü), bəziləri isə hələ sadəcə işarədir (məsələn, "mərtəbə sayı", hansı ki, sonra müəyyən olunacaq).
C# dünyasında belə "Konseptual Ev" abstrakt class adlanır.
Abstrakt class – abstract açar sözü ilə işarələnmiş class-dır.
public abstract class Animal // İndi Animal - abstrakt class-dır
{
// ...
}
Abstrakt class-ların əsas xüsusiyyəti:
- Onları birbaşa yaratmaq olmur. Sən new Animal() yaza bilməzsən. Niyə? Çünki Animal artıq müəyyən olunmamış bir şeydir, konsepsiyadır. "Konseptual Ev" tikə bilməzsən, ancaq konkret kottec və ya göydələn tikə bilərsən.
Əgər new Animal() yazsan, kompilyator dərhal səni saxlayacaq:
Bu çox vacib məhdudiyyətdir!Cannot create an instance of the abstract type or interface 'Animal' (Abstrakt tip və ya interfeys 'Animal'-dan nümunə yaratmaq mümkün deyil) - Onlar abstrakt üzvlər saxlaya bilər. Bax, bu ən maraqlısıdır!
3. Abstrakt metodlar: reallaşdırmasız müqavilə
Əgər abstrakt class "Konseptual Ev"dirsə, abstrakt metod onun layihəsində "bunu et" kimi qeyd olunan, amma "necə"si yazılmayan hissədir. Məsələn, "Fundament tikmək" – ev üçün vacibdir, amma fundamentin ölçüləri və materialları evin tipindən asılıdır.
Abstrakt metod – bu metoddur ki:
- abstract açar sözü ilə işarələnib.
- Bədəni yoxdur (yəni, {} kod bloku yoxdur). Sadəcə nöqtəli vergül ; ilə bitir.
- Yalnız abstrakt class daxilində elan oluna bilər.
Gəlin MakeSound() metodumuzu abstrakt edək:
public abstract class Animal // Animal-ı abstrakt etdik
{
public string Name { get; set; }
public int Age { get; set; }
public abstract void MakeSound(); // Budur, abstrakt metod! Bədən yoxdur!
public void Sleep() // Bu metod adi, "konkret" qaldı
{
Console.WriteLine($"{Name} yatır.");
}
}
Bax, MakeSound() necə dəyişdi! Artıq süslü mötərizələr və susmaya görə reallaşdırma yoxdur. Sadəcə deyir: "Hər bir heyvan mütləq səs çıxarmağı bacarmalıdır. Necə – qoy məndən miras alanlar qərar versin."
Vacib qayda: Əgər class-ın abstrakt class-dan miras alırsa və özü abstrakt deyilsə, o mütləq əsas class-ın bütün abstrakt metodlarını (override ilə) reallaşdırmalıdır. Bu seçim deyil, tələbdir, müqavilədir! C# kompilyatoru bu məsələdə çox sərtdir. Unutsan, dərhal xəbər verəcək:
public class Dog : Animal // Adi, abstrakt olmayan class
{
// KOMPİLYASİYA SƏHVİ!
// 'Dog' does not implement inherited abstract member 'Animal.MakeSound()'
// ('Dog' class-ı miras aldığı abstrakt üzv 'Animal.MakeSound()' metodunu reallaşdırmır)
// Kompilyator bizdən MakeSound() və override gözləyir!
}
Səhvdən qurtulmaq üçün Dog və Cat mütləq MakeSound() metodunu override etməlidir:
public class Dog : Animal
{
public override void MakeSound() // Mütləq override edirik!
{
Console.WriteLine("Hav-hav!");
}
}
public class Cat : Animal
{
public override void MakeSound() // Burda da!
{
Console.WriteLine("Miyau!");
}
}
virtual, abstract və override müqayisəsi
| Xüsusiyyət | virtual metod | abstract metod | override açar sözü |
|---|---|---|---|
| Yerləşmə | Adi və ya abstrakt class-da | Yalnız abstrakt class-da | Törəmə (uşaq) class-da |
| Metodun bədəni | Bədəni var (susmaya görə reallaşdırma) | Bədəni yoxdur (; ilə bitir) | Bədəni var (yeni reallaşdırma) |
| Məqsəd | Susmaya görə reallaşdırma verir, amma törəmə class-lara dəyişmək icazəsi verir | Müqavilə elan edir: törəmə class-lar mütləq öz reallaşdırmasını verməlidir | Əsas class-ın virtual və ya abstract metoduna spesifik reallaşdırma verir |
| Əsas class-dan nümunə yaratmaq? | Bəli (əgər əsas class abstrakt deyilsə) | Xeyr (əgər əsas class abstraktdırsa) | N/A (metoda aiddir, class-a yox) |
| Törəmə class üçün öhdəlik | İstəyə bağlı override (override) | Mütləq override (override), əgər törəmə class özü abstrakt deyil | N/A |
4. Abstrakt metodlarla polimorfizm iş başında
İndi ən maraqlı yer başlayır! Artıq bilirik ki, polimorfizm bizə müxtəlif törəmə class-ların obyektləri ilə əsas class-a istinad edərək işləməyə imkan verir. Və bu, əsas class abstrakt olsa belə, əla işləyir!
Düzdür, Animal obyektini birbaşa yarada bilmirik (new Animal() səhv verəcək), amma Animal tipindən istinad kimi törəmə class obyektlərini saxlaya bilərik. Bu çox güclüdür!
Zooparkımıza davam edək. Təsəvvür elə ki, fermamız var və orada müxtəlif heyvanlar yaşayır. İstəyirik ki, hər heyvan öz səsini çıxarsın.
using System;
// Abstrakt Animal class-ı
public abstract class Animal
{
public string Name;
public int Age;
public Animal(string name, int age) { Name = name; Age = age; }
public abstract void MakeSound();
public void Sleep() { Console.WriteLine($"{Name} yatır."); }
}
public class Dog : Animal
{
public Dog(string name, int age) : base(name, age) { }
public override void MakeSound() { Console.WriteLine("Hav-hav!"); }
}
public class Cat : Animal
{
public Cat(string name, int age) : base(name, age) { }
public override void MakeSound() { Console.WriteLine("Miyau!"); }
}
public class Fish : Animal
{
public Fish(string name, int age) : base(name, age) { }
public override void MakeSound() { Console.WriteLine("Bul-bul"); }
}
class Program
{
static void Main()
{
Animal[] animals = {
new Dog("Şarik", 3),
new Cat("Murzık", 5),
new Fish("Nemo", 1)
};
foreach (Animal animal in animals)
{
Console.WriteLine($"\nSalam, mən {animal.Name}, mənim {animal.Age} yaşım var.");
animal.MakeSound();
animal.Sleep();
}
}
}
Bu kodda nə baş verir?
- Animal abstract class kimi elan olunub. Bu, kompilyatora deyir: "Bu class şablondur, özü yaradıla bilməz, amma ondan miras almaq olar".
- public abstract void MakeSound(); Animal daxilində elan olunub. Bu bildirir: "Hər hansı Animal-dan miras alan class (və özü abstrakt olmayan) mütləq MakeSound() metodunu reallaşdırmalıdır". Bu bizim müqaviləmizdir!
- Dog, Cat və Fish bu müqaviləyə əməl edir, MakeSound() metodunu özünəməxsus reallaşdırır. Əgər bunu unutsaq, kompilyator kodumuzu buraxmazdı.
- Main metodunda Animal[] massiv yaradırıq. Animal abstrakt olsa da, massiv istinad kimi törəmə class obyektlərini (Dog, Cat, Fish) saxlaya bilər, çünki onlar Animal-dır!
- foreach dövründə animal.MakeSound() çağıranda, polimorfizm sayəsində C# "başa düşür" ki, hansı MakeSound() çağırılmalıdır: Dog.MakeSound(), Cat.MakeSound() və ya Fish.MakeSound(). Çağırılan metod obyektin faktiki tipinə uyğun olur, istinad tipinə yox. Polimorfizmin gözəlliyi budur!
- animal.Sleep() isə konkret əsas class Animal-dan gələn reallaşdırmanı çağırır, çünki bu metod nə virtual, nə də abstract deyil və törəmə class-larda override olunmayıb.
5. Bunu real həyatda niyə istifadə edirik?
"Düzdü, zoopark, heyvanlar... Bəs mən real bank və ya mağaza proqramı yazanda bu nəyə lazımdır?" — deyə bilərsən. Və bu əla sualdır! Abstrakt class-lar və metodlar çevik və genişlənə bilən sistemlər dizayn etmək üçün super güclü alətdir.
Müqavilənin reallaşdırılmasına məcbur etmək: Əsas üstünlük budur. Təsəvvür elə, ödəniş sistemləri üçün framework yazırsan. Səndə əsas abstract class PaymentProcessor (Ödəniş emalçısı) var. Və sən dəqiq bilirsən ki, istənilən ödəniş emalçısı ProcessPayment() (ÖdənişiEmalEt), RefundPayment() (ÖdənişiGeriQaytar) və CheckStatus() (StatusuYoxla) bacarmalıdır. Amma PayPal, bank kartı və ya Bitcoin üçün bu proseslər tam fərqlidir.
Bu metodları abstract kimi PaymentProcessor-da elan edirsən.
public abstract class PaymentProcessor
{
public abstract bool ProcessPayment(decimal amount, string currency, string cardNumber);
public abstract bool RefundPayment(string transactionId);
public abstract string CheckStatus(string transactionId);
// ... başqa metodlar da ola bilər, məsələn, loglama üçün konkret metod
public void LogTransaction(string message)
{
Console.WriteLine($"[LOG]: {message}");
}
}
public class PayPalProcessor : PaymentProcessor
{
public override bool ProcessPayment(decimal amount, string currency, string cardNumber)
{
// Burada PayPal API ilə işləmək üçün çətin məntiq var
Console.WriteLine($"PayPal: {amount} {currency} emal olunur...");
return true;
}
public override bool RefundPayment(string transactionId) { /* ... */ return true; }
public override string CheckStatus(string transactionId) { /* ... */ return "Tamamlandı"; }
}
public class CreditCardProcessor : PaymentProcessor
{
public override bool ProcessPayment(decimal amount, string currency, string cardNumber)
{
// Burada bank-acquirer ilə işləmək üçün məntiq var
Console.WriteLine($"CreditCard: {amount} {currency} kartdan {cardNumber.Substring(0,4)}XXXX...");
return true;
}
public override bool RefundPayment(string transactionId) { /* ... */ return true; }
public override string CheckStatus(string transactionId) { /* ... */ return "Emal olunur"; }
}
İndi hər hansı developer yeni ödəniş emalçısı (məsələn, BitcoinProcessor) yaratmaq istəsə, mütləq bu üç metodu reallaşdırmalıdır. RefundPayment()-i təsadüfən unutmaq mümkün deyil, çünki kompilyator buna imkan verməyəcək! Bu, sistemində ardıcıllığı təmin edir.
Çeviklik və genişlənə bilmək: Sən PaymentProcessor ilə işləyən kod yaza bilərsən, hansı konkret reallaşdırmanın istifadə olunacağını bilmədən. Məsələn, online mağazada sadəcə currentProcessor.ProcessPayment() çağırırsan, sistem isə istifadəçinin seçdiyi ödəniş üsuluna görə uyğun emalçını seçir. Sabah yeni ödəniş üsulu çıxsa – sadəcə yeni class yaradırsan, PaymentProcessor-dan miras alırsan, abstrakt metodları reallaşdırırsan və əsas kodu dəyişməyə ehtiyac olmur!
Boş reallaşdırmalardan qaçmaq: Əgər virtual metodlardan istifadə etsəydik, susmaya görə boş reallaşdırma verməli olardıq, bu isə yanıldıcı ola bilərdi. abstract isə açıq deyir: "Burada reallaşdırma yoxdur və ola da bilməz – miras alanlara bax!"
Kod arxitekturasını yaxşılaşdırmaq: Abstrakt class-lar ümumi məntiqi və spesifik məntiqi aydın ayırmağa kömək edir. Ümumi (məsələn, LogTransaction PaymentProcessor-da) əsas class-da olur, spesifik (məsələn, ProcessPayment) isə törəmə class-larda. Bu kodu daha oxunaqlı, dəstəklənən və test olunan edir. Framework və kitabxana dizaynerlərinə "genişlənmə nöqtələri" təyin etməyə imkan verir, hansı ki, istifadəçilər mütləq doldurmalıdır.
6. Yayğın səhvlər və incəliklər
Səhv №1: Abstrakt class-dan nümunə yaratmağa cəhd.
Bu həmişə kompilyasiya səhvidir. Abstrakt class – konsepsiyadır, konkret obyekt deyil. Onu istinad kimi istifadə edə bilərsən, amma new Animal() yaza bilməzsən.
Səhv №2: Abstrakt metodu override etməyi unutmaq.
Əgər törəmə class abstrakt deyilsə, əsas class-ın bütün abstract metodlarını reallaşdırmalıdır. Əks halda – kompilyasiya səhvi. Yeganə çıxış yolu törəmə class-ı da abstrakt etməkdir, amma adətən bunu istəmirsən.
Səhv №3: abstract və virtual arasında qarışıqlıq.
abstract metodu override etməyə məcbur edir, bədəni yoxdur. virtual susmaya görə reallaşdırma verir və override oluna bilər. abstract metodu bədənlə, virtual metodu bədəsiz elan etmək olmaz – bu sintaksis səhvidir.
Səhv №4: new əvəzinə override istifadə etməmək.
Əgər override istifadə etmirsənsə, sadəcə eyni adlı metod yaradırsan, əsas class metodunu gizlədirsən. Bu, polimorf çağırışlarda gözlənilməz nəticə verəcək: əsas class-dan olan metod çağırılacaq.
public class Base
{
public void DoSomething() { Console.WriteLine("Base"); }
}
public class Derived : Base
{
public new void DoSomething() { Console.WriteLine("Derived"); }
}
Base obj = new Derived();
obj.DoSomething(); // Çap edəcək: Base
GO TO FULL VERSION