C# programlamada en çok karıştırılan konulardan biri olan Abstract Class ve Interface kavramlarını, pratik bir araç kiralama örneği üzerinden detaylı olarak inceleyeceğiz. Bu yazıda, ne zaman hangisini kullanacağınızı ve aralarındaki temel farkları öğreneceksiniz.
Senaryo: Araç Kiralama Sistemi
Bir araç kiralama yazılımı yazdığınızı düşünün. Bu yazılımda farklı türde araçlarınız olacak: Arabalar, Motosikletler, Kamyonlar vb.
Bu örnek üzerinden hem Abstract Class hem de Interface kavramlarını pratik olarak göreceğiz ve hangi durumda hangisini kullanmamız gerektiğini anlayacağız.
Soyut Sınıf (Abstract Class) Örneği: Arac
Diyelim ki tüm araçların ortak bazı temel özellikleri ve bir başlangıç değeri olmalı: her aracın bir markası, bir modeli ve tüm araçlara otomatik olarak atanacak bir seri numarası (Id) olmalı. Ayrıca, her araç hareket edebilir ama her araç farklı şekilde hareket eder.
public abstract class Arac
{
// Ortak Özellikler (Durum tutabiliriz)
public Guid Id { get; set; } // Tüm araçların benzersiz bir ID'si olacak
public string Marka { get; set; }
public string Model { get; set; }
// Yapıcı metot (Constructor) - ID'yi otomatik atıyoruz
public Arac(string marka, string model)
{
Id = Guid.NewGuid(); // Her araç oluşturulduğunda benzersiz ID alır
Marka = marka;
Model = model;
}
// Ortak SOMUT Metot (Tüm araçlar için aynı davranış)
public void BilgileriGoster()
{
Console.WriteLine($"ID: {Id}, Marka: {Marka}, Model: {Model}");
}
// Ortak SOYUT Metot (Her araç farklı hareket eder, alt sınıflar bunu uygulamak zorunda)
public abstract void HareketEt();
}
Neden abstract class Arac
?
- Ortak Alanlar/Özellikler:
Id
,Marka
,Model
gibi tüm araçlarda bulunacak alanları tanımlayabiliriz. - Ortak Başlatma Mantığı: Yapıcı metot sayesinde her
Arac
nesnesi oluştuğundaId
'sinin otomatik olarakGuid.NewGuid()
ile atanmasını sağlayabiliriz. Bir arayüzde yapıcı metot olamaz. - Ortak Somut Davranış:
BilgileriGoster()
gibi tüm araçların aynı şekilde çalışacak metotlarını yazabiliriz. - Zorunlu Soyut Davranış:
HareketEt()
metodu soyut olduğu için,Arac
sınıfından türeyen her sınıf (örneğinAraba
,Motosiklet
) bu metodu kendi içinde uygulamak zorundadır.
Şimdi bu Arac
sınıfından türeyen gerçek araçları oluşturalım:
public class Araba : Arac
{
public int KapiSayisi { get; set; }
public Araba(string marka, string model, int kapiSayisi)
: base(marka, model) // Base class'ın (Arac) yapıcı metodunu çağırıyoruz
{
KapiSayisi = kapiSayisi;
}
// Soyut metodu uygulamak zorundayız
public override void HareketEt()
{
Console.WriteLine("Araba dört teker üzerinde ilerliyor.");
}
}
public class Motosiklet : Arac
{
public bool TekerlekliYanCantasiVarMi { get; set; }
public Motosiklet(string marka, string model, bool tekerlekliYanCantasiVarMi)
: base(marka, model) // Base class'ın (Arac) yapıcı metodunu çağırıyoruz
{
TekerlekliYanCantasiVarMi = tekerlekliYanCantasiVarMi;
}
// Soyut metodu uygulamak zorundayız
public override void HareketEt()
{
Console.WriteLine("Motosiklet iki teker üzerinde hızla ilerliyor.");
}
}
Arayüz (Interface) Örneği: IKiralayabilir
ve IYukTasiyabilir
Şimdi, bazı araçların kiralanabilir, bazı araçların yük taşıyabilir özelliklere sahip olması gerektiğini düşünelim. Bir araba hem kiralanabilir hem de yük taşıyabilir, bir motorsiklet sadece kiralanabilir olabilir.
// Kiralama yeteneği olan her varlığın bu metodu uygulaması gerektiğini belirten bir sözleşme
public interface IKiralayabilir
{
// İmza tanımlıyoruz, uygulama detayı yok
void Kirala(int gunSayisi);
decimal HesaplaKiraBedeli(int gunSayisi, decimal gunlukFiyat);
}
// Yük taşıma yeteneği olan her varlığın bu metodu uygulaması gerektiğini belirten bir sözleşme
public interface IYukTasiyabilir
{
// İmza tanımlıyoruz, uygulama detayı yok
void YukDoldur(int miktar);
void YukBosalt(int miktar);
}
Şimdi bu arayüzleri araçlarımıza uygulayalım:
public class Araba : Arac, IKiralayabilir // Araba, Arac'tan miras alır ve IKiralayabilir'i uygular
{
public int KapiSayisi { get; set; }
public Araba(string marka, string model, int kapiSayisi)
: base(marka, model)
{
KapiSayisi = kapiSayisi;
}
public override void HareketEt()
{
Console.WriteLine("Araba dört teker üzerinde ilerliyor.");
}
// IKiralayabilir arayüzünün metotlarını uyguluyoruz
public void Kirala(int gunSayisi)
{
Console.WriteLine($"{Marka} {Model} {gunSayisi} günlüğüne kiralandı.");
}
public decimal HesaplaKiraBedeli(int gunSayisi, decimal gunlukFiyat)
{
return gunSayisi * gunlukFiyat;
}
}
public class Kamyon : Arac, IYukTasiyabilir, IKiralayabilir // Kamyon hem yük taşıyabilir hem kiralanabilir
{
public int KapasiteTon { get; set; }
public Kamyon(string marka, string model, int kapasiteTon)
: base(marka, model)
{
KapasiteTon = kapasiteTon;
}
public override void HareketEt()
{
Console.WriteLine("Kamyon ağır yüklerle yolda ilerliyor.");
}
// IYukTasiyabilir arayüzünün metotlarını uyguluyoruz
public void YukDoldur(int miktar)
{
Console.WriteLine($"{Marka} {Model}'e {miktar} ton yük yüklendi.");
}
public void YukBosalt(int miktar)
{
Console.WriteLine($"{Marka} {Model}'den {miktar} ton yük boşaltıldı.");
}
// IKiralayabilir arayüzünün metotlarını uyguluyoruz (örneğin kamyonları da kiralayabiliriz)
public void Kirala(int gunSayisi)
{
Console.WriteLine($"{Marka} {Model} {gunSayisi} günlüğüne kiralandı (kamyon).");
}
public decimal HesaplaKiraBedeli(int gunSayisi, decimal gunlukFiyat)
{
return gunSayisi * gunlukFiyat * 1.5m; // Kamyon olduğu için daha pahalı olabilir
}
}
Pratik Kullanım Örneği
class Program
{
static void Main(string[] args)
{
// Farklı araç türleri oluşturuyoruz
var araba = new Araba("Toyota", "Corolla", 4);
var motosiklet = new Motosiklet("Yamaha", "R1", false);
var kamyon = new Kamyon("Mercedes", "Actros", 25);
// Tüm araçlar ortak Arac sınıfından türediği için aynı şekilde kullanabiliyoruz
Console.WriteLine("=== Araç Bilgileri ===");
araba.BilgileriGoster();
motosiklet.BilgileriGoster();
kamyon.BilgileriGoster();
Console.WriteLine("\n=== Hareket Şekilleri ===");
araba.HareketEt();
motosiklet.HareketEt();
kamyon.HareketEt();
// Interface'ler sayesinde farklı yetenekleri kullanabiliyoruz
Console.WriteLine("\n=== Kiralama İşlemleri ===");
araba.Kirala(7);
kamyon.Kirala(3);
Console.WriteLine("\n=== Yük Taşıma İşlemleri ===");
kamyon.YukDoldur(15);
kamyon.YukBosalt(10);
// Polimorfizm örneği
Console.WriteLine("\n=== Polimorfizm Örneği ===");
List<IKiralayabilir> kiralanabilirAraclar = new List<IKiralayabilir>
{
araba,
kamyon
};
foreach (var arac in kiralanabilirAraclar)
{
arac.Kirala(5);
}
}
}
Ne Zaman Hangisini Kullanmalıyız?
Abstract Class
Interface
Ortak Durum Paylaşımı
Tüm türetilmiş sınıfların ortak bir temel Id'ye, yaratılma/güncellenme zamanına vb. sahip olmasını garanti etmek istediğinizde.
Yetenek Sözleşmesi
Farklı sınıfların ortak bir "yapabilirlik" (capability) veya "sözleşme" paylaşmasını istediğinizde.
Varsayılan Implementasyon
Bazı metotların varsayılan (somut) uygulamalarını sağlamak ve kod tekrarını önlemek istediğinizde.
Çoklu Yetenek
Birden fazla "yapabilirliği" bir sınıfa eklemek istediğinizde. Bir sınıf birden fazla arayüz uygulayabilir.
Tek Kalıtım
Türetilmiş sınıfların paylaşması gereken durum (fields/properties) varsa ve sadece tek bir temel tipte olmasını istediğinizde.
Esnek Implementasyon
Uygulama detaylarının her sınıfa bırakılmasını veya varsayılan arayüz metotları ile çok genel bir uygulama sağlamak istediğinizde.
Karar Ağacı
Özet ve Önemli Noktalar
Abstract Class
- Ortak durum (state) paylaşımı
- Yapıcı metot (constructor) olabilir
- Hem somut hem soyut metotlar
- Tek kalıtım (single inheritance)
- Varsayılan implementasyon sağlar
Interface
- Sadece sözleşme (contract) tanımlar
- Yapıcı metot olamaz
- Sadece metot imzaları (C# 8.0+ default methods)
- Çoklu implementasyon (multiple inheritance)
- Esneklik ve polimorfizm sağlar
Dikkat Edilmesi Gerekenler
- Abstract class'tan direkt nesne oluşturamazsınız
- Interface'lerdeki tüm metotları implement etmek zorundasınız
- Bir sınıf sadece bir abstract class'tan türeyebilir ama birden fazla interface implement edebilir
- Abstract metotlar mutlaka override edilmelidir
Sonuç
Abstract Class ve Interface kavramları, nesne yönelimli programlamanın temel taşlarıdır. Doğru kullanıldığında kodunuzun daha modüler, esnek ve sürdürülebilir olmasını sağlarlar.
Araç kiralama örneğinde gördüğümüz gibi, Abstract Class ortak özellikleri ve davranışları paylaşmak için, Interface ise farklı yetenekleri (capabilities) tanımlamak için kullanılır. Her ikisinin de kendine özgü kullanım alanları vardır ve doğru seçim yapmak kodunuzun kalitesini önemli ölçüde artırır.
Video Kaynaklar
Konuyu pekiştirmek için önerilen videolar: