Çok Biçimlilik (Polymorphism)
Gün 2: Nesne Yönelimli Programlama (OOP) | Virtual metodlar, upcasting, $cast ile downcasting
Bu derste çok biçimliliği (polymorphism) öğreneceğiz: Bir üst sınıf handle'ı üzerinden çağrılan metodun, çalışma zamanında nesnenin gerçek tipine göre seçilmesini sağlayan virtual metodları, upcasting'i ve $cast ile güvenli downcasting'i inceleyeceğiz.
Polimorfizm ve virtual Metodlar
Polimorfizm, "tek arayüz, çok davranış" demektir. Aynı metod çağrısının (speak()), çağrıldığı nesnenin tipine göre farklı sonuç vermesidir. Bunun anahtarı virtual anahtar kelimesidir:
- Bir metod
virtualolarak işaretlenirse, hangi sürümün çalışacağı derleme zamanında handle'ın tipine değil, çalışma zamanında nesnenin gerçek tipine göre belirlenir. Buna dinamik bağlama (dynamic dispatch) denir. virtualolmayan bir metodda ise handle'ın bildirilen tipi belirleyicidir; bu polimorfik davranışı engeller.
Bu sayede bir üst sınıf dizisinde (Animal animals[4]) farklı alt sınıf nesneleri tutup hepsi üzerinde aynı display() çağrısını yaptığınızda, her nesne kendi özelleşmiş sürümünü çalıştırır.
Upcasting ve Downcasting ($cast)
- Upcasting: Bir alt sınıf nesnesini üst sınıf handle'ına atamaktır (
animals[1] = d;). Her zaman güvenlidir ve örtük (implicit) yapılır, çünkü birDogzaten birAnimal'dır. - Downcasting: Üst sınıf handle'ından alt sınıf handle'ına dönüştürmektir. Bu tehlikelidir çünkü handle gerçekte beklenen alt tipi işaret etmeyebilir. Bu yüzden
$cast(hedef, kaynak)ile yapılır; dönüşüm başarılıysa1, değilse0döndürür ve böylece güvenli kontrol sağlanır.
Kaynak Kod
// =============================================================================
// GUN 2 - Konu 6: Cok Bicimlilik (Polymorphism) ve Sanal (Virtual) Metodlar
// =============================================================================
class Animal;
string name;
int age;
function new(string name, int age);
this.name = name;
this.age = age;
endfunction
// virtual: Alt siniflarda override edilebilir
virtual function string speak();
return "...";
endfunction
virtual function void display();
$display(" [%s] Isim: %s, Yas: %0d, Ses: %s",
get_type(), name, age, speak());
endfunction
virtual function string get_type();
return "Animal";
endfunction
endclass
class Dog extends Animal;
string breed;
function new(string name, int age, string breed = "Mixed");
super.new(name, age);
this.breed = breed;
endfunction
virtual function string speak();
return "Hav hav!";
endfunction
virtual function string get_type();
return "Dog";
endfunction
virtual function void display();
super.display();
$display(" Cins: %s", breed);
endfunction
endclass
class Cat extends Animal;
bit is_indoor;
function new(string name, int age, bit indoor = 1);
super.new(name, age);
this.is_indoor = indoor;
endfunction
virtual function string speak();
return "Miyav!";
endfunction
virtual function string get_type();
return "Cat";
endfunction
virtual function void display();
super.display();
$display(" Ev kedisi: %s", is_indoor ? "Evet" : "Hayir");
endfunction
endclass
class Parrot extends Animal;
string vocabulary [$];
function new(string name, int age);
super.new(name, age);
endfunction
function void learn_word(string word);
vocabulary.push_back(word);
endfunction
virtual function string speak();
if (vocabulary.size() > 0) begin
int idx = $urandom % vocabulary.size();
return vocabulary[idx];
end
return "Cik cik!";
endfunction
virtual function string get_type();
return "Parrot";
endfunction
endclass
module polymorphism;
initial begin
// Ust sinif dizisi ile tum hayvanlari tutma (Polimorfizm)
Animal animals [4];
Dog d;
Cat c;
Parrot p;
Animal a_ref; // Dog nesnesi, Animal referansi
Dog d_ref;
$display("=== Polimorfizm (Polymorphism) ===\n");
// --- Nesneler olustur ---
d = new("Karabas", 3, "Golden Retriever");
c = new("Tekir", 2, 0);
p = new("Papagan", 5);
p.learn_word("Merhaba!");
p.learn_word("Gunaydin!");
p.learn_word("Nasilsin?");
// --- Polimorfik dizi ---
animals[0] = new("Bilinmeyen", 0); // Base
animals[1] = d; // Dog -> Animal (upcasting)
animals[2] = c; // Cat -> Animal
animals[3] = p; // Parrot -> Animal
// --- virtual metod cagrilari ---
$display("--- Polimorfik display() ---");
foreach (animals[i]) begin
$display("\n Hayvan #%0d:", i);
animals[i].display(); // Her nesne kendi override'ini calistirir
end
// --- $cast ile downcasting ---
$display("\n--- $cast ile Downcasting ---");
a_ref = d; // Dog nesnesi, Animal referansi
// Guvenli donusum
if ($cast(d_ref, a_ref)) begin
$display(" Cast basarili! Cins: %s", d_ref.breed);
end else begin
$display(" Cast basarisiz!");
end
// Yanlis cast denemesi
a_ref = c; // Cat nesnesi
if (!$cast(d_ref, a_ref))
$display(" Cat -> Dog cast basarisiz (beklenen)");
// --- Polimorfik fonksiyon ---
$display("\n--- Polimorfik Fonksiyon ---");
begin
foreach (animals[i]) begin
make_speak(animals[i]);
end
end
$display("\n=== Polimorfizm Sonu ===");
$finish;
end
// Herhangi bir Animal alt sinifini kabul eder
function void make_speak(Animal a);
$display(" %s diyor: %s", a.name, a.speak());
endfunction
endmodule
Kodun Açıklaması
Animaltemel sınıfı:name,agealanları ve üçvirtualmetod (speak(),display(),get_type()) içerir.virtualolmaları, alt sınıfların bu metodları geçersiz kılabilmesini (override) ve polimorfik çağrıyı mümkün kılar.- Alt sınıflar:
Dog,CatveParrot,Animal'dan türer vespeak()ileget_type()metodlarını kendilerine göre yeniden tanımlar.DogveCatayrıcadisplay()'i geçersiz kılarken içindesuper.display()çağırarak temel çıktının üzerine ek bilgi yazar. Parrotve kuyruk:vocabularybir kuyruktur (string [$]).learn_word()ile kelime eklenir;speak()kuyrukta kelime varsa$urandomile rastgele birini döndürür.- Polimorfik dizi:
Animal animals[4]dizisine farklı tipte nesneler atanır (upcasting).animals[1] = d;ile birDog,Animalhandle'ına yerleştirilir. - Dinamik bağlama:
foreachdöngüsündeanimals[i].display()çağrılır. Metodvirtualolduğu için her eleman kendi gerçek tipinindisplay()vespeak()sürümünü çalıştırır; handleAnimaltipinde olmasına rağmen. $castile downcasting:a_ref = d;sonrası$cast(d_ref, a_ref)başarılı olur çünküa_refgerçekte birDog'u işaret eder. Ancaka_ref = c;(birCat) yapıldığında$cast(d_ref, a_ref)başarısız olur ve0döndürür; bu yüzden!$cast(...)koşulu beklenen şekilde çalışır.- Polimorfik fonksiyon:
make_speak(Animal a)herhangi birAnimalalt sınıfını kabul eder vea.speak()çağrısı yine gerçek tipe göre çözülür.
Önemli Noktalar
- Polimorfik davranış için metodlar
virtualolmalıdır.virtualolmazsa, handle'ın bildirilen tipine göre statik bağlama yapılır ve override edilen sürüm çağrılmaz. virtualbir metod, kalıtım zinciri boyunca türetilen sınıflarda davirtualkalır; alt sınıflarda tekrarvirtualyazmak iyi bir okunabilirlik pratiğidir.- Downcasting için her zaman
$castkullanın ve dönüş değerini kontrol edin; doğrudan atama derleme hatası verir veya güvensizdir.$castbaşarısızsa0döner ve çalışma zamanı hatasından kaçınılır. - Upcasting örtüktür ve daima güvenlidir; downcasting ise yalnızca handle gerçekten hedef tipi işaret ediyorsa başarılı olur.
super.metot()çağrısı, override edilen bir metodun üst sınıf davranışını koruyup üzerine ekleme yapmak için kullanılır (örneğinDog::displayiçindekisuper.display()).- Polimorfizm, UVM gibi doğrulama metodolojilerinin temelidir: ortak bir temel transaction/bileşen arayüzü üzerinden farklı türler tek tip kodla işlenebilir.
$castiki biçimde kullanılabilir: fonksiyon olarak (if ($cast(d_ref, a_ref))) başarısızlıkta0döndürür; task/ifade olarak ($cast(d_ref, a_ref);) başarısızlıkta çalışma zamanı hatası üretir. Tip kontrolü yapıyorsanız fonksiyon biçimini, "bu kesinlikle dönüşmeli" diyorsanız ifade biçimini kullanın.- Constructor'lar (
new)virtualolamaz ve statik metotlar davirtualolamaz; polimorfizm yalnızca nesne metotları için geçerlidir. - SystemVerilog'da pure virtual metotlar ve soyut sınıflar da vardır:
virtual class Shape; pure virtual function real area(); endclass. Soyut sınıfnewile yaratılamaz; yalnızca alt sınıflararea()'yı tanımlayarak somutlaşır. UVM'dekiuvm_driver,uvm_sequencegibi birçok temel sınıf bu kalıbı kullanır.
virtual Olmadan Ne Olur?
Aynı kodu virtual anahtar kelimelerini silerek çalıştırdığınızı düşünün:
| Çağrı | virtual ile |
virtual olmadan |
|---|---|---|
animals[1].display() (handle Animal, nesne Dog) |
Dog::display → "Hav hav!" |
Animal::display → "..." |
d.display() (handle Dog) |
Dog::display |
Dog::display |
make_speak(c) (Animal a parametresi) |
Cat::speak → "Miyav!" |
Animal::speak → "..." |
Fark yalnızca üst sınıf handle'ı üzerinden çağrı yapıldığında ortaya çıkar; ve doğrulama ortamlarında neredeyse her çağrı böyledir (kuyruklar, mailbox'lar, fonksiyon parametreleri hep temel tip kullanır).
Benzetme:
virtualmetot, kapıda "zile basın" yazan bir evdir: zile kim basarsa bassın, içerideki kişi kapıyı açar (nesnenin gerçek tipi karar verir).virtualolmayan metot ise kapıdaki tabelaya bakıp "burada Animal yazıyor, demek ki Animal gibi davranmalıyım" demektir; içeride birDogolsa bile tabela kazanır.
UVM'de Karşılığı
UVM'in tamamı bu derse dayanır. uvm_driver'ın run_phase metodu virtual olduğu için sizin yazdığınız my_driver::run_phase çalışır; uvm_sequence_item kuyrukları temel tiple tutulur ve $cast ile gerçek tipe dönüştürülür; factory override mekanizması ise "my_driver yerine my_error_driver yarat" diyebilmenizi sağlar — bu da ancak kod temel tip üzerinden virtual metot çağırıyorsa işe yarar.
Sık Yapılan Hatalar
virtualeklemeyi unutmak: En sinsi hata; derleme hatası vermez, yalnızca "yanlış" metot sessizce çalışır. Override edilmesi muhtemel her metodu baştanvirtualyapın.- Alt sınıfta imzayı değiştirmek:
virtual function string speak(int volume)yazarsanız bu bir override değil, farklı bir metottur. Argüman tipleri ve dönüş tipi üst sınıfla birebir aynı olmalıdır. $castdönüş değerini kontrol etmemek:void'($cast(d_ref, a_ref))yazıp devam ederseniz, dönüşüm başarısız olduğundad_refeski değerinde kalır ve bir sonraki satır yanlış nesneyle çalışır.- Downcast'i doğrudan atamayla denemek:
d_ref = a_ref;derleme hatasıdır; dönüşüm için$castzorunludur.
Kendinizi Deneyin
Animal::speak'tenvirtualkelimesini silin (alt sınıflarda bırakın).animals[i].display()çıktıları nasıl değişti? Yalnızca temel sınıftakivirtualkelimesinin yeterli olduğunu doğrulayın.$cast(d_ref, a_ref);satırınıifolmadan,a_refbirCatiken çalıştırın. Simülatörün hata mesajını kaydedin.virtual class Shapeadında soyut bir sınıf yazın (pure virtual function real area();),CircleveSquaretüretin ve birShape shapes[$]kuyruğunda toplam alanı hesaplayın.Parrotsınıfınadisplay()override'ı ekleyip kelime sayısını da yazdırın;super.display()çağrısını unutmayın.
Hızlı Kontrol: Animal a = new("x", 1); Dog d; $cast(d, a); ne döndürür?
0 (başarısız). a gerçekte bir Animal nesnesini gösteriyor, bir Dog değil; Animal tabandır ve Dog'a dönüştürülemez. $cast yalnızca nesnenin gerçek tipi hedef tipin kendisi veya alt sınıfı olduğunda başarılı olur.