Yazılıma yeni başlayan hemen herkesin zaman zaman karıştırdığı, hangisini ne zaman kullanacağına karar veremediği Abstract Class ve Interface ikilisini ele almak istedim. İlk bakışta yazdığımız koda oldukça benzer yetenekler kazandırdıkları görünse de aralarındaki farkların üzerinde durarak hangi senaryolarda hangisine karar vermemiz gerektiğini iyice açığa çıkartacağız 🙂
Abstract sınıflara geçmeden önce C# ve tüm Object Oriented dillerin bize sunduğu bir özellik olan abstraction(soyutlama) kavramını ele alalım. Böylece Abstract sınıflara ve Interface’lere neden ihtiyaç duyduğumuzu daha net bir şekilde anlayabiliriz.
Abstraction (Soyutlama) Nedir?
Abstraction, OOP (Object Oriented Programming-Nesne Tabanlı Programlama) içerisindeki önemli kavramlardan birisidir. C#’taki soyutlama; diğer Object Oriented dillerde olduğu gibi iç detayları gizleyerek sadece işlevleri göstermeye denir. Bu durumu gerçek hayatın içerisinden bir senaryo ile örneklendirelim.
Senaryo: Bir gün canınız sıkıldı ve çocukluğunuzda kalmış tatlı bir aktivite olan lunaparka gidip çarpışan arabaya binmek istediniz. Biletinizi aldınız ve seçtiğiniz arabaya oturup size gelen görevliye bileti teslim ettiniz. Görevli alana baktı ve araçları hareket ettirecek olan elektriği vermek için yeterli sayıda kişinin olduğuna karar verip bir düğmeye bastı. Görevlinin düğmeye basmasıyla çarpışan arabaların hareket etmesi için gerekli olan elektrik akımı verildi ve arabanız çalışır hale geldi. Direksiyonu, gaz pedalını ve freni kullanarak dilediğiniz şekilde aracı kullanmaya başladınız.
Şimdi gelin bu gerçek hayatın birebir içinden olan senaryodaki soyutlamaları (abstractions) inceleyelim. Böylece yazılım alanında da aslında aynı mantığın kullanıldığını ve soyutlamanın hayatımızı nasıl kolaylaştırdığını daha iyi anlamış olacağız.
Senaryoda Yer Alan Soyutlamalar(Abstractions): İlk soyutlama örneğimiz biletinizi teslim ettiğiniz görevlinin eğlenceyi başlatmak için bastığı küçük kırmızı düğme 🙂 Görevli o kırmızı düğmeye bastığında bizler sadece eğlencenin başlayacağını biliyoruz, görevli de on dakikalık bir seansa daha start vereceğini biliyor yalnızca. Düğmeye basıldığı anda elektrik devresindeki anahtarın kapanıp akımın başlayacağını ne biz ne de görevli aklının ucundan dahi geçirmiyor. İşte bu tam bir soyutlama örneği. Yalnızca input(düğmeye basmak) ve output(eğlencenin başlaması) değerleriyle ilgileniyoruz. Arka tarafta dönen teknik hadiseleri hiç düşünmeden bize sunulmuş bir düğmeyi kullanarak işimizi görüyoruz.
Eğer hala bir şeyler oturmadıysa kafanızda bir de çarpışan arabamızdaki soyutlamaya bakalım. Siz arabayı kullanırken direksiyonu gitmek istediğiniz yöne çeviriyor, gaz pedalına basarak ilerliyorsunuz. Gaz pedalına bastığınızdaki motorda oluşan tetiklenmeleri, direksiyonu çevirdiğinizde lastiklerin dönmesini sağlayan mekanizmayı hiç düşünmeden yalnızca iki pedal bir direksiyonla aslında arka planında onlarca aksiyon yatan bir işlevi gerçekleştirmiş oluyorsunuz. Input (gaz/fren pedalları, direksiyon) ve output (arabanın gitmesi) değerlerini bilmek size yetiyor.
Object Oriented programlamada sınıfların soyutlanmasını ise Abstract Class ve Interface ikilisi sağlamaktadır. Abstraction kavramı kafamızda biraz olsun şekillendiğine göre şimdi Abstract Class’ları inceleyebiliriz.
Abstract Class
Abstract sınıflar sınıf hiyerarşisinde genellikle base class (temel sınıf) tanımlamak için kullanılan ve soyutlama yeteneği kazandıran sınıflardır. Bir sınıfı abstract yapmak için abstract keywordünü kullanırız. Abstract sınıfların en az bir tane abstract metod bulundurması bir best practice’tir.
1 2 3 4 | abstract class Yazdir { public abstract void KonsolaYazdir(); } |
Yukarıda Yazdir isimli basitçe bir abstract sınıfı tanımladık ve kendisinden türeyecek metodların override edeceği KonsolaYazdir() abstract metodunu oluşturduk.
Genel olarak abstract sınıfların özelliklerini bir araya toplarsak;
- Abstract sınıfları genel olarak inheritance (kalıtım) uygularken kullanırız.
- new anahtar sözcüğü ile nesneleri oluşturulamaz.
- İçerisinde değişken ve metod bulundurabilir.
- Abstract sınıflardan türetilen sınıfların abstract metodları implement etmesi zorunludur. Diğer metodları override etmeden de kullanabilir.
- Constructors (yapıcı metodlar) ve destructors (yıkıcı metodlar) bulundurabilirler.
- Static tanımlanamazlar. ( Tanımlanmaya çalışılırsa compiler “an abstract class cannot be sealed or static” hatası verir)
- Bir sınıf yalnızca bir abstract sınıfı inheritance yoluyla implement edebilir. Çoklu kalıtım (multiple inheritance) desteklenmez.
- Abstract olmayan metodları da bulundurabilir.
- Kendisinden inherit alacak sınıflar ile arasında “is-a” ilişkisi vardır. (Burası ilk başlarda çok önemsenmeyen ancak hangi senaryoda Abstract hangi senaryoda Interface kullanacağımızı netleştirmede bize oldukça yardımcı olan bir detaydır, hemen aşağıda açıklamasını bulabilirsiniz)
Is-a İlişkisi Nedir?
Senaryomuzdan bir örnekle bunun daha iyi anlaşılabileceğini düşünüyorum. (Şimdiye kadar Türkçe kelimeler üzerinden gittiğim için örneğimde de is-a ilişkisi hariç cümlemi Türkçe kuruyorum)
Çarpışan araba is-a araba. Yani en nihayetinde çarpışan araba da bir arabadır ve arabanın sahip olduğu özellikleri bünyesinde barındırır.
Şimdi Araba için abstract bir sınıf oluşturup çarpışan arabayı da bu sınıftan inherit alarak oluşturalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | abstract class Araba { //Arabalarin ortak ozelliklerini burada tanimliyoruz public string Marka { get; set; } public int MaksimumHiz { get; set; } public string Tur { get; set; } //Her araba icin yolculuga baslandi bilgisini farkli bir sekilde //ekrana yazdirmak icin override dilecek abstract metodumuzu yaziyoruz public abstract void YolculugaBasla(); } class CarpisanAraba : Araba { //Abstract metodumuzu carpisan arabaya ozel bir sekilde override ediyoruz. public override void YolculugaBasla() { Console.WriteLine("Carpisan araba ile yolculuga baslandi"); } } |
Araba isminde bir abstract sınıf tanımlayarak CarpisanAraba sınıfını bu abstract base sınıftan türettik. Böylece Araba sınıfında yer alan tüm özellikleri tekrardan yazmak zorunda kalmadan kolayca sahip olurken, YolculugaBasla() metodunu sınıfımıza özel override ettik. Abstract sınıfları inherit alan sınıfların abstract metodları override etmesinin zorunlu olduğunu belirtmiştik. Bu sebeple Visual Studio’da CarpisanAraba sınıfını Araba sınıfından inherit ettiğinizde, YolculugaBasla() metodunu override etmezseniz hata alırsınız.
Abstract sınıfları genel itibariyle mercek altına aldığımıza göre şimdi bir de Interface’leri inceleyelim. Sonrasında ikisi arasındaki farkları tablo halinde gördüğümüzde hangi senaryoda hangi soyutlama mekanizmasını kullanacağımızı çok daha iyi kestirebilir bir hale geleceğiz.
Interface
Interface, içerisinde sadece kendisinden türeyecek olan sınıfların içini dolduracağı metod tanımlarının bulunduğu ve soyutlama yapmamıza olanak sağlayan bir yapıdır. Interface’leri tanımlarken interface keywordünü kullanırız. Tanımladığımız yapının interface olduğunu belirtmek için isminin önüne I harfini getirmek bir best-practice olacaktır. Böylece kodlama yaparken inherit aldığımız yapının bir class mı yoksa interface mi olduğunu kolaylıkla ayırt edebiliriz. Bu kadar teorik bilgiden sonra basitçe bir interface tanımlayalım.
1 2 3 4 | interface ILog { void LogEkle(string kayit); } |
Bu interface’ten türeyen tüm sınıflar LogEkle(string kayit) metodunu implement etmek zorundadır. Interface hakkında internette bir arama yaptığınızda genellikle kendisini inherit alan sınıflar için bir kontrat olduğundan bahsedilir. Kontrat kelimesiyle anlatılmak istenen tam olarak budur. Türeyen sınıflar, interface’in içerisinde yer alan tüm metodları implement edeceğine dair bir söz vermekte, sözleşme yapmaktadır. Aksi halde derleyici hata verecek, kodunuz çalışmayacaktır.
Genel olarak Interface’lerin özelliklerinden bahsetmemiz gerekirse;
- new keywordü ile nesneleri oluşturulamaz.
- Bir sınıfın ne yapması gerektiğini belirtir, nasıl yapması gerektiğini değil.
- Default olarak tüm Interface üyeleri abstract ve public olarak tanımlanır. Sizin özellikle belirtmeniz gerekmez.
- Bir sınıf birden fazla interface’i inherit edebilir, çoklu kalıtım (multiple-inheritance) desteklenir.
- İçerisinde yalnızca metodların imzaları yer alır, içi dolu metod bulunduramazlar.
- Kendisinden inherit alacak sınıflar ile arasında “can-do” ilişkisi vardır.
Can-do İlişkisi Nedir?
Çarpışan araba senaryosundan bir örnekle açıklamak gerekirse,
Çarpışan araba can-do hızlanmak. Açıklamaya gerek var mı bilemedim açıkçası ama abstract sınıflarda açıklamışken burada boş bırakmak içime sinmezdi. Çarpışan araba hızlanabilir diyoruz. Çarpışan arabanın bir davranışından bahsediyoruz.
Interfaceleri tam olarak can-do ilişkileri içeren yapılarda kullanmamız isabetli olacaktır. Senaryomuzdaki çarpışan araba hızlanabilir. Bu çarpışan arabanın yapabileceği bir kabiliyeti göstermektedir. Can-do ilişkisi davranışları, kabiliyetleri belirtir. Bu kabiliyetin interface içerisinde tanımlanması çok daha doğru olacaktır.
Abstract Class ve Interface Arasındaki Farklar
ABSTRACT CLASS | INTERFACE |
Constructor içerebilir. | Constructor içeremez. |
Static üyeler içerebilir. | Static üyeler içeremez. |
Farklı tiplerde access modifier (erişim belirleyicisi) içerebilir. public, private, protected gibi. | Farklı tiplerde access modifier içeremez. Interface’te tanımlanan her metod default olarak public kabul edilir. |
Sınıfın ait olduğu kimliği belirtmek için kullanılır. (is-a ilişkisi) | Sınıfın yapabileceği kabiliyetleri belirtmek için kullanılır. (can-do ilişkisi) |
Bir sınıf sadece bir tane abstract sınıfı inherit edebilir. | Bir sınıf birden fazla interface’i inherit edebilir. |
Eğer birçok sınıf aynı türden ve ortak davrnanışlar sergiliyorsa abstract sınıfı base class olarak kullanmak doğru olacaktır. | Eğer birçok sınıf yalnızca ortak metodları kullanıyor ise interface’ten türetilmeleri doğru olacaktır. |
Abstract sınıf metod, fields, contants vb. üyeleri içerebilir. | Interface yalnızca metod imzalarını içerebilir. |
Türetilen sınıflar abstract sınıfı tamamen veya kısmi implemente edebilir. | Türetilen sınıflar interface’i tamamen implement etmek zorundadır. |
Metod imzaları veya implementasyonları içerebilir. | Yalnızca metod imzalarını içerebilir. |
Abstract sınıflar ve Interface’ler arasındaki farkları işlediğimiz bu yazıyla birlikte bir yazının daha sonuna geldik. Dilerim aydınlatıcı olmuştur, anlayamadığınız veya gözümden kaçan herhangi bir şey varsa yorumlarda belirtmeniz beni memnun edecektir. Bir sonraki yazıda görüşmek dileğiyle; keyifli kodlamalar, mutlu günler :)!
KAYNAKLAR
- https://www.geeksforgeeks.org/difference-between-abstract-class-and-interface-in-c-sharp/
- https://www.agile-code.com/blog/difference-between-interfaces-and-abstract-classes-in-microsoft-net/