Skip to content

Bölüm 14 / 15

Interface ve soyutlama

Sözleşme kavramı, çoklu implementasyon.

8 dakikalık okuma

Hattın son kavram dersi, OOP'nin en çok iş yapan aracını getiriyor: interface — hiçbir gövde içermeyen saf sözleşme. Kalıtımın tek ebeveyn sınırına takılmayan, akraba olmayan tiplere ortak yetenek kazandıran, "somuta değil sözleşmeye kodla" ilkesini mümkün kılan yapı budur. ASP.NET Core'un dependency injection'ı, EF Core, test mock'ları — ileride kuracağın her şey bu dersin üzerinde duruyor; mülakattaki "abstract class ile interface farkı" klasiği de buradadır.

01Ne öğreneceksin

  • Interface tanımı ve implementasyonu; bir sınıfın birden çok interface uygulayabilmesi
  • Interface referansı ve parametresi: bağımlılığı sözleşmeye indirmek
  • Abstract class ile interface'in tam karşılaştırması — ne zaman hangisi
  • Baştan beri kullandığın .NET interface'leri: IComparable, IEnumerable, IReadOnlyList

02Sorun: akraba olmayanların ortak yeteneği

Kurs sisteminde raporlama gelsin: Ogrenci de Fatura da rapor satırı üretebilmeli. Kalıtım burada iki duvara çarpar. Birincisi: Ogrenci ile Fatura akraba değildir — "Fatura bir Kisi'dir" diyemezsin; ortak ata uydurmak (RaporlanabilirSey?) is-a testini çöpe atmaktır. İkincisi: Ogrenci'nin ebeveyn hakkı zaten dolu (: Kisi) — C# tek kalıtım tanır, ikinci sınıftan türeyemez.

İhtiyacın olan şey soy ilişkisi değil, yetenek beyanı: "bu tip, şu işi yapabilir" — kim olduğundan bağımsız. Kalıtım is-a (nedir) sorusuna, interface can-do (ne yapabilir) sorusuna cevaptır.

03Tanım ve implementasyon

interface IRaporlanabilir                 // I öneki: C#'ın yerleşik geleneği
{
    string RaporSatiri();                  // yalnızca İMZA — gövde yok, public varsayılır
    string Baslik { get; }                 // property imzası da olur
}

class Ogrenci : Kisi, IRaporlanabilir      // önce sınıf (varsa), sonra interface'ler
{
    public int Not { get; private set; }
    public Ogrenci(string ad, int not) : base(ad) { Not = not; }

    public string Baslik => "Öğrenci Kaydı";
    public string RaporSatiri() => $"{Ad};{Not}";      // sözleşmeyi YERİNE GETİRMEK zorunda
}

class Fatura : IRaporlanabilir             // akrabalık gerekmedi
{
    public decimal Tutar { get; }
    public Fatura(decimal tutar) { Tutar = tutar; }

    public string Baslik => "Fatura";
    public string RaporSatiri() => $"FTR;{Tutar:0.00}";
}

Kurallar: interface yalnızca üye imzaları içerir — metotlar, property'ler (alan içeremez, kurucu içeremez); üyeleri doğal olarak public'tir. Uygulayan sınıf, listedeki her üyeyi yazmak zorundadır — birini unutursan derleme hatası: "does not implement interface member". Ve dersin kilit kuralı: sınıf tek sınıftan türer ama istediği kadar interface uygular — class X : Taban, IA, IB, IC. Adlandırma geleneği I önekidir (IRaporlanabilir, IDisposable); kodda bir tipin interface olduğunu adından tanırsın.

04Sözleşmeye kodlamak

Interface de tıpkı sınıf gibi referans tipi olarak kullanılır — ve asıl gücü buradadır:

IRaporlanabilir r = new Fatura(149.90m);   // interface referansı

void RaporYaz(List<IRaporlanabilir> kayitlar)      // parametre: SÖZLEŞME, somut tip değil
{
    foreach (var kayit in kayitlar)
    {
        Console.WriteLine($"[{kayit.Baslik}] {kayit.RaporSatiri()}");
    }
}

RaporYaz(new List<IRaporlanabilir>
{
    new Ogrenci("Aysel", 85),
    new Fatura(149.90m)                     // akraba olmayanlar aynı listede
});

RaporYaz'ın Ogrenci'den, Fatura'dan, gelecekte yazılacak Sertifika'dan haberi yok — yalnızca sözleşmeyi tanıyor. Çağrılar geçen dersin mekanizmasıyla, çalışma zamanında gerçek tipin implementasyonuna gider (polimorfizm, interface'lerde doğal olarak açıktır — virtual yazmak gerekmez). Bu desenin ilkesel adı şudur ve meslek hayatın boyunca duyacaksın: somut tipe değil, soyutlamaya bağımlı ol. Metotların parametrelerinde ve alanlarında mümkün olan en soyut tipi iste — geçen dersin IReadOnlyList'i de tam buydu.

05Abstract class ile interface: tam karşılaştırma

Mülakat klasiği; tabloyu ezber değil gerekçeleriyle bil:

ÖzellikAbstract classInterface
İlişki türüis-a — "nedir"can-do — "ne yapabilir"
Kaç tane?Tek (tek kalıtım)Sınırsız
Alan (field) / durumTaşırTaşımaz
ConstructorVar (türevler için)Yok
Gövdeli üyeSerbest (virtual, normal)Kural olarak yok*
Erişim belirleyicilerTam kontrolÜyeler public
Akraba olmayan tiplerBağlayamazBağlar

* Modern C# interface'lere varsayılan gövdeli üye (default interface method) ekleme imkânı tanır; ileri ve nadir bir özelliktir — sınav cevabın "interface gövde içermez" olsun, bu notu dipnot olarak bil.

Seçim kılavuzu: türevlerin ortak veri ve gövde paylaşması gerekiyorsa (Kisi'nin Ad'ı, kurucusu, KendiniTanit gövdesi) → abstract class. Tiplere yalnızca ortak yetenek imzası kazandırıyorsan, hele bu tipler akraba değilse → interface. İkisi rakip değil ortaktır ve sık birlikte kullanılır: geçen derslerin hiyerarşisinde abstract class Kisi + interface IRaporlanabilir yan yana yaşıyor.

06Baştan beri interface kullanıyordun

9. dersteki "zaten nesne kullanıyordun" gerçeğinin interface karşılığı:

  • IReadOnlyList<T> — 11. derste koleksiyon sızıntısına karşı kullandın; şimdi ne olduğunu tam biliyorsun: List'in uyguladığı, yalnızca okuma üyeleri içeren bir sözleşme.
  • IEnumerable<T> — foreach'in çalışma sebebi: foreach, "üzerinde gezinilebilir" sözleşmesini uygulayan her şeyle çalışır. Dizi, List, Dictionary, HashSet, hatta string — hepsi bu interface'i uygular; foreach'in hepsinde aynı sözdizimiyle işlemesi tesadüf değil, sözleşmedir (mekanizmanın derinliği Hat 03'te LINQ ile).
  • IComparable<T> — Array.Sort'un ve List.Sort'un sırrı: sıralayıcı, elemanların tipini tanımaz; "kendini benzerinle karşılaştırabilir" sözleşmesini çağırır. int'in, string'in, DateTime'ın sıralanabilmesi bu interface'i uygulamalarındandır — ve kendi tipini de uygulatabilirsin:
class Ogrenci : Kisi, IRaporlanabilir, IComparable<Ogrenci>
{
    // ...
    public int CompareTo(Ogrenci diger) => Not.CompareTo(diger.Not);
    // sözleşme: negatif = ben öndeyim, 0 = eşit, pozitif = o önde
}

ogrenciler.Sort();          // artık çalışır — Sort, CompareTo sözleşmesini çağırıyor

Desen hep aynı: genel araç (Sort, foreach) + sözleşme (interface) + senin implementasyonun. Framework'lerin seninle konuşma biçimi budur.

07Neden her yerde: değiştirilebilirlik ve test

Interface'in sektördeki ağırlığını bir senaryoyla gör. Sipariş servisin e-posta göndersin:

interface IBildirimGonderici
{
    void Gonder(string alici, string mesaj);
}

class SiparisServisi
{
    private readonly IBildirimGonderici gonderici;     // SOMUT TİP DEĞİL, sözleşme

    public SiparisServisi(IBildirimGonderici gonderici)   // bağımlılık dışarıdan gelir
    {
        this.gonderici = gonderici;
    }

    public void SiparisiTamamla(string musteri)
    {
        // ... sipariş işleri ...
        gonderici.Gonder(musteri, "Siparişiniz alındı");
    }
}

SiparisServisi, e-postanın nasıl gönderildiğini bilmez — bilmediği için de özgürsün: bugün EpostaGonderici, yarın SmsGonderici, testte hiçbir yere gerçek mesaj atmayan SahteGonderici — servis satırı değişmeden takas edilir. Bu kurulumun üç durağı ileride seni bekliyor: ilkesi Hat 04'te (DIP — SOLID'in D'si), otomasyonu Hat 05'te (ASP.NET Core'un DI container'ı — kurucuya kimin ne vereceğini framework yönetir), test tarafı Hat 15'te (Moq ile sahte implementasyon üretimi). Üçünün de dilbilgisi bu derste öğrendiğin interface'tir; İlan B'nin ASP.NET Core ve birim test maddeleri bu zincirin üzerinde durur.

08Mini alıştırma — ödeme sözleşmesi

Yaklaşık 20 dakika · dotnet new console -o arayuzler

  1. IOdemeYontemi interface'ini yaz: string Ad { get; } ve bool OdemeAl(decimal tutar). İki implementasyon: KrediKarti (limit alanı tutsun; tutar limiti aşarsa false, aşmazsa limitten düş ve true) ve Havale (tutar pozitifse her zaman true).
  2. void SatisiTamamla(IOdemeYontemi yontem, decimal tutar) metodu yaz: OdemeAl'ı çağırsın, sonuca göre "{yontem.Ad} ile {tutar:0.00} TL alındı / reddedildi" yazsın. Aynı metodu iki farklı implementasyonla çağır — metodun hangi sınıfla çalıştığını bilmediğini vurgula.
  3. Bir üyeyi (örneğin Ad'ı) KrediKarti'ndan silmeyi dene; "does not implement" derleme hatasını oku, geri koy. Interface'e üye eklemenin tüm implementasyonları derleme hatasıyla güncelletmeye zorladığını not et — sözleşmenin gücü budur.
  4. Kalıtım projesindeki Ogrenci'ye IComparable<Ogrenci> uygula (nota göre); List<Ogrenci>.Sort() ile sırala ve yazdır. CompareTo'yu ters çevirip (diger.Not.CompareTo(Not)) azalan sıralamayı da gör.
  5. Kapanış — üç seviyeli soyutlama provası: List<KrediKarti>, List<IOdemeYontemi> ve karışık içerikli ikincisinin foreach'le işlenişini yaz. Hangi listenin yarın MobilOdeme eklendiğinde değişmeden kalacağını bir cümleyle not et.

09Kendini sına

Interface nedir? Kalıtımdan hangi soruyu cevaplamasıyla ayrılır?

Interface, gövde içermeyen saf üye sözleşmesidir: metot ve property imzaları. Uygulayan sınıf her üyeyi yazmak zorundadır. Kalıtım is-a ("nedir") sorusuna cevaptır ve tek ebeveynle sınırlıdır; interface can-do ("ne yapabilir") sorusuna cevaptır, sınırsız sayıda uygulanır ve akraba olmayan tipleri ortak yetenek altında birleştirir.

Abstract class ile interface arasındaki dört temel farkı say. Ne zaman hangisini seçersin?

Abstract class: tek kalıtım, alan/durum taşır, constructor'ı vardır, gövdeli üye serbesttir. Interface: sınırsız uygulanır, durum ve constructor taşımaz, kural olarak yalnızca imza içerir. Seçim: türevler ortak veri ve gövde paylaşacaksa abstract class; tiplere — özellikle akraba olmayanlara — yalnızca ortak yetenek imzası kazandırılacaksa interface. İkisi birlikte de kullanılır: abstract Kisi + IRaporlanabilir gibi.

foreach ve Array.Sort hangi interface'lere dayanır? Bu neyi gösterir?

foreach, IEnumerable sözleşmesini uygulayan her tiple çalışır — dizi, List, Dictionary, string'in tek sözdizimiyle gezilebilmesinin sebebi budur. Sort, elemanların IComparable sözleşmesindeki CompareTo'sunu çağırır; kendi tipin de uygularsa sıralanabilir olur. İkisi de aynı deseni gösterir: genel araç + sözleşme + senin implementasyonun — framework'lerle çalışmanın temel biçimi.

Bir servisin bağımlılığını somut sınıf yerine interface olarak almasının iki somut kazancı nedir?

(1) Değiştirilebilirlik: implementasyon (e-posta → SMS) servis kodu değişmeden takas edilir; yeni yöntem eklemek mevcut koda dokunmaz. (2) Test edilebilirlik: teste gerçek bağımlılık yerine sahte implementasyon verilir — gerçek e-posta atmadan servis test edilir. Bu kurulum DIP ilkesinin ve ASP.NET Core'daki dependency injection'ın temelidir.

10Sınav köşesi

Yazılıda nasıl sorulur

Bu konunun yazılı klasiği doğrudan karşılaştırmadır: "Interface ile abstract sınıf için aşağıdakilerden hangisi yanlıştır?" — şıklar tablonun satırlarıdır (constructor, alan, çoklu uygulama, gövde). İkinci kalıp dil kuralı: "Bir sınıf kaç interface uygulayabilir?" (sınırsız), "Interface üyesinin varsayılan erişimi?" (public), "Interface'te alan tanımlanabilir mi?" (hayır). Kod verilen sorularda eksik implementasyonun derleme hatası olduğu ve interface referansına hangi nesnelerin atanabileceği sorulur.

Sözlüde nasıl anlatılır — 2 dakikalık taslak

"Interface nedir, ne zaman kullanırsınız?" sorusuna üç katlı cevap: (1) Tanım — gövdesiz sözleşme; is-a değil can-do; tek kalıtım sınırını aşar, akraba olmayan tipleri bağlar. (2) Karşılaştırma — abstract class'la dört farkı gerekçeli say; "ortak gövde varsa abstract, ortak yetenek imzası varsa interface, sık sık ikisi birlikte" cümlesiyle bağla. (3) Değer — asıl vuruş: "Bağımlılıklarımı interface olarak alırım; implementasyonu takas edebilir, testte sahtesini verebilirim — ASP.NET Core'un DI mekanizması da tam bunun üzerine kurulu." Bu cümle, Hat 05 ve 15'in kapılarını mülakat masasında açar.