EDA Playground'da Dene

Kapsülleme (Encapsulation)

Gün 2: Nesne Yönelimli Programlama (OOP) | Erişim belirleyicileri: local, protected, public

Bu derste nesne yönelimli programlamanın temel ilkelerinden biri olan kapsüllemeyi (encapsulation) öğreneceğiz. Bir sınıfın iç verilerini dış dünyadan nasıl gizleyeceğimizi ve bu verilere yalnızca kontrollü metodlar (getter/setter) üzerinden erişim sağlamayı local, protected ve public belirleyicileriyle inceleyeceğiz.

Kapsülleme Nedir?

Kapsülleme, bir sınıfın verilerini ve bu veriler üzerinde çalışan metodları bir arada tutarak iç ayrıntıları dışarıdan gizleme ilkesidir. Amaç, nesnenin durumunun ancak izin verilen ve doğrulanan yollarla değiştirilebilmesini sağlamaktır. Bu, hatalı veya tutarsız durumların oluşmasını engeller.

SystemVerilog üç erişim belirleyicisi sunar:

  • public (varsayılan): Üyeye her yerden erişilebilir. Belirteç yazılmazsa üye public kabul edilir.
  • local: Üyeye yalnızca tanımlandığı sınıfın içinden erişilebilir. Alt sınıflar bile erişemez. En katı gizlilik düzeyidir.
  • protected: Üyeye tanımlandığı sınıf ve ondan türeyen alt sınıflardan erişilebilir, ancak sınıf dışından erişilemez.

Getter ve Setter Kalıbı

Gizli (local/protected) verilere dışarıdan erişim gerektiğinde, doğrudan erişim yerine public metodlar kullanılır:

  • Getter: Veriyi okumak için (get_balance() gibi).
  • Setter: Veriyi yazmak için, ama önce kontrol/doğrulama yaparak (deposit(), withdraw() gibi). Böylece geçersiz değerler engellenir.

Kaynak Kod

// =============================================================================
// GUN 2 - Konu 4: Kapsulleme (Encapsulation): local, protected, public
// =============================================================================

class BankAccount;
  // public: Disaridan erisilebilir (varsayilan)
  string owner;

  // local: Sadece bu sinif icinden erisilebilir
  local real balance;
  local int  account_no;
  local static int next_account = 1000;

  // protected: Bu sinif ve alt siniflardan erisilebilir
  protected string account_type;
  protected int    transaction_count;

  function new(string owner, real initial_balance = 0, string acc_type = "Standard");
    this.owner       = owner;
    this.balance     = initial_balance;
    this.account_type = acc_type;
    this.account_no  = next_account++;
    this.transaction_count = 0;
  endfunction

  // Public getter (okuma erisimi saglar)
  function real get_balance();
    return balance;
  endfunction

  function int get_account_no();
    return account_no;
  endfunction

  function int get_transaction_count();
    return transaction_count;
  endfunction

  // Public setter (kontrollu yazma)
  function bit deposit(real amount);
    if (amount <= 0) begin
      $display("  [HATA] Gecersiz miktar: %.2f", amount);
      return 0;
    end
    balance += amount;
    transaction_count++;
    $display("  [+] %.2f yatirildi. Yeni bakiye: %.2f", amount, balance);
    return 1;
  endfunction

  function bit withdraw(real amount);
    if (amount <= 0 || amount > balance) begin
      $display("  [HATA] Gecersiz cekim: %.2f (Bakiye: %.2f)", amount, balance);
      return 0;
    end
    balance -= amount;
    transaction_count++;
    $display("  [-] %.2f cekildi. Yeni bakiye: %.2f", amount, balance);
    return 1;
  endfunction

  function void display();
    $display("  Hesap #%0d | Sahip: %s | Tip: %s | Bakiye: %.2f | Islem: %0d",
             account_no, owner, account_type, balance, transaction_count);
  endfunction
endclass

// Alt sinif: protected'a erisebilir, local'a erisemez
class PremiumAccount extends BankAccount;
  local real credit_limit;

  function new(string owner, real initial_balance, real credit_limit = 5000);
    super.new(owner, initial_balance, "Premium");
    this.credit_limit = credit_limit;
  endfunction

  // protected uyelere erisim (OK)
  function void show_details();
    $display("  [Premium] Tip: %s, Islem sayisi: %0d, Kredi limiti: %.2f",
             account_type, transaction_count, credit_limit);
  endfunction
endclass

module kapsulleme;
  initial begin
    BankAccount    acc1;
    PremiumAccount acc2;

    $display("=== Kapsulleme (Encapsulation) ===\n");

    // --- Public erisim ---
    $display("--- Hesap Olusturma ---");
    acc1 = new("Ali", 1000.0);
    acc2 = new("Ayse", 5000.0, 10000.0);
    acc1.display();
    acc2.display();

    // --- Kontrollu erisim (getter/setter) ---
    $display("\n--- Islemler (Kontrollu Erisim) ---");
    void'(acc1.deposit(500));
    void'(acc1.withdraw(200));
    void'(acc1.withdraw(99999));   // Hata: yetersiz bakiye
    void'(acc1.deposit(-100));     // Hata: negatif miktar
    
    $display("\n  Bakiye (getter ile): %.2f", acc1.get_balance());
    $display("  Islem sayisi: %0d", acc1.get_transaction_count());

    // --- local erisim denemesi (derleme hatasi verir) ---
    // acc1.balance = 999999;       // HATA: 'balance' local
    // acc1.account_no = 0;         // HATA: 'account_no' local

    // --- protected erisim (alt siniftan) ---
    $display("\n--- Protected Erisim (Alt Sinif) ---");
    void'(acc2.deposit(2000));
    acc2.show_details();

    // --- Sonuc ---
    $display("\n--- Sonuc ---");
    acc1.display();
    acc2.display();

    $display("\n=== Kapsulleme Sonu ===");
    $finish;
  end
endmodule

Kodun Açıklaması

  • BankAccount sınıfı erişim düzeyleri: owner public'tir (dışarıdan erişilebilir). balance, account_no ve next_account local'dir (sadece sınıf içinden). account_type ve transaction_count ise protected'tir (sınıf ve alt sınıflardan).
  • local static next_account: Hem local hem static olduğu için sınıfa özel ortak bir sayaçtır. Her constructor'da account_no = next_account++ ile benzersiz, ardışık hesap numaraları üretilir.
  • Getter metodları: get_balance(), get_account_no() ve get_transaction_count(), local/protected alanların değerlerini dışarıya güvenli biçimde okuma erişimi sağlar.
  • Kontrollü setter'lar: deposit() miktarın pozitif olduğunu, withdraw() ise miktarın geçerli ve bakiyeden küçük olduğunu doğrular. Geçersiz durumda hata yazıp 0 döndürür, bakiyeyi bozmaz. Bu, kapsüllemenin asıl değeridir: balance'a yalnızca kuralları geçen değerler işlenebilir.
  • PremiumAccount alt sınıfı: BankAccount'tan türer ve super.new(...) ile üst sınıf constructor'ını çağırır. show_details() metodu account_type ve transaction_count (protected) alanlarına erişebilir; ancak üst sınıfın local balance alanına erişemez.
  • Erişim hatası örneği: Koddaki yorum satırı acc1.balance = 999999; etkin olsaydı, balance local olduğu için derleme hatası verirdi. Bu, kapsüllemenin derleyici düzeyinde nasıl zorlandığını gösterir.

Önemli Noktalar

  • Bir üye için belirteç yazılmazsa varsayılan public'tir; gizlilik istiyorsanız açıkça local veya protected yazmalısınız.
  • local ile protected arasındaki fark kalıtımdadır: protected üyeler alt sınıflardan erişilebilir, local üyeler erişilemez.
  • Veri alanlarını gizleyip erişimi getter/setter üzerinden vermek, doğrulama (validation) eklemenize ve nesnenin tutarlılığını korumanıza imkân tanır.
  • Erişim ihlalleri derleme zamanında yakalanır; bu da hataları simülasyon çalışmadan önce ortaya çıkarır.
  • Benzersiz kimlik üretimi için local static bir sayaç kalıbı (next_account++) çok kullanışlıdır; sayaç dış müdahaleye kapalı kalır.
  • local ve protected metodlara da uygulanabilir. Yalnızca sınıf içinden çağrılması gereken bir yardımcı fonksiyonu (local function void log_txn();) gizlemek, sınıfın dış arayüzünü (API) küçük ve net tutar.
  • Erişim belirleyici nesneye değil tipe bağlıdır: BankAccount içindeki bir metot, başka bir BankAccount nesnesinin local balance alanına erişebilir (other.balance). Bu, compare(other) gibi metodların yazılabilmesini sağlar.

Erişim Düzeyleri Özet Tablosu

Belirleyici Sınıfın kendi içinden Alt sınıftan (extends) Sınıf dışından
(yok) = public ✅ ✅ ✅
protected ✅ ✅ ❌
local ✅ ❌ ❌

Benzetme: Bir bankayı düşünün. Public: lobide herkesin kullanabildiği ATM. Protected: çalışanlar ve şube yöneticilerinin (alt sınıflar) girebildiği arka ofis. Local: yalnızca kasa sorumlusunun açabildiği kasa. Kasaya para "ATM" üzerinden (deposit/withdraw) girer; kimse kasaya elini uzatıp bakiyeyi doğrudan değiştiremez.

Doğrulamada Kapsülleme Nerede İşe Yarar?

  • Transaction'ın hesaplanan alanları: crc, parity gibi alanları local yapıp yalnızca calc_crc() ile güncelletmek, kullanıcının yanlışlıkla tutarsız CRC yazmasını engeller.
  • Driver'ın iç durumu: Sürülen işlem sayısı gibi sayaçlar protected tutulur; dışarı yalnızca get_count() ile açılır.
  • Kütüphane (VIP) geliştirmek: Başkalarının kullanacağı bir doğrulama bileşeni yazıyorsanız, iç mekanizmayı gizlemek sonradan yapılan değişikliklerin kullanıcıları kırmamasını sağlar.
  • UVM'de: UVM temel sınıflarında m_ ile başlayan üyelerin çoğu local/protected'tır; bu yüzden uvm_component'in iç alanlarına doğrudan erişemez, get_name(), get_parent() gibi getter'ları kullanırsınız.

Sık Yapılan Hatalar

  • Her şeyi local yapıp alt sınıfı kilitlemek: Türetilmiş sınıfın erişmesi gereken alanlar local ise alt sınıf çalışmaz. Kalıtım planlıyorsanız protected seçin.
  • Getter'ın handle döndürmesi: function Payload get_payload(); return payload; yazarsanız dışarıya iç nesnenin handle'ını verirsiniz; kapsülleme fiilen delinir. Gerekirse kopya döndürün.
  • Setter'da doğrulama yapmamak: Doğrulama içermeyen bir setter, alanı public yapmaktan farksızdır. Setter'ın var olma nedeni kural koymaktır.
  • local üyeye alt sınıftan "süper" aracılığıyla erişmeyi denemek: super.balance de çalışmaz; local kesin olarak yalnızca tanımlandığı sınıfa aittir.

Kendinizi Deneyin

  1. acc1.balance = 0; satırını yorumdan çıkarıp derleyin; hata mesajını not alın. Ardından balance'ı protected yapıp tekrar deneyin: sınıf dışından erişim hâlâ hata veriyor mu?
  2. PremiumAccount içine, kredi limiti dahilinde eksiye düşmeye izin veren overdraft_withdraw(real amount) metodu ekleyin. balance local olduğu için doğrudan yazamayacaksınız; çözümü üst sınıfa protected function void adjust_balance(real delta); ekleyerek bulun.
  3. BankAccount'a function bit same_owner(BankAccount other); return (owner == other.owner); endfunction ekleyin ve other.account_no'ya erişmenin neden mümkün olduğunu açıklayın.
  4. next_account sayacını local static yerine public static yapın ve dışarıdan BankAccount::next_account = 5; ile bozun. Kapsülleme olmadan ne tür hatalar doğabilir?
Hızlı Kontrol: Alt sınıfın erişebilmesini ama dış dünyanın erişememesini istediğiniz bir üye hangi belirleyiciyle tanımlanır?

protected. local alt sınıfa bile kapalıdır; public (varsayılan) ise herkese açıktır.