C dilinde Kapsülleme (Encapsulation) ve Bilgi Gizleme (Information Hiding)
C dili imperatif (imperative) ve prosedürel (procedural) bir programlama dilidir. Buradaki imperatif ve prosedürel terimleri programlama paradigmalarından gelmektedir. OOP (Object Oriented Programming) nesne yönemli programlama da bu programlama paradigmalarından bir tanesidir. C dili OOP paradigmasına doğrudan destek vermemektedir. C dilinde C++ ‘da olduğu gibi doğrudan OOP’ye yönelik araçlar mevcut değil ancak dolaylı olarak aşağıda belirttiğim OOP konseptlerini C’de de uygulayabilmemiz mümkün olmaktadir.
- Encapsulation
- Inheritance
- Polymorphism
- Abstraction
Bu yazımda C dilinde dolaylı olarak Kapsülleme (Encapsulation) ve Bilgi Gizleme (Data Hiding) konseptlerini nasıl uygulayabileceğimizi anlatacağım.
Kapsülleme bir nesnenin metotlarını ve değişkenlerini bir araya getirerek bilgi gizleme ile bu metotlara ve değişkenlere erişimi sınırlandıran ve böylelilikle nesneyi yanlış kullanımlarından koruyan bir konsepttir. C’de bir yapı nesnesi (struct) içinde sadece varsayılan türde değişkenler (fundemental types), fonksiyon göstericileri (function pointers) ve kullanıcı tanımlı data tipleri (struct, enum, union, typedef) kullanılabilmektedir. C dilinde ne yazık ki C++ dilinde olduğu gibi class içerisindeki bu öğelere erişimi engelleyecek private, protected, public gibi erişim kontrolü (access modifiers) araçları yok. Aynı zamanda C++’da class içinde fonksiyon tanımlaması veya bildirimi (member functions) yapılabilirken C’de struct içinde bunları yapamıyoruz.
C’de kapsüllemeyi iki yolla yapabilmemiz mümkün. Bunlardan ilki aslında kapsülleme sağlasa da tam olarak bir bilgi gizliliği sağlamamaktadır. İkincisi ise kapsülleme yanında bilgi gizliliği de sağlamaktadır. Bu iki yöntem de olgunlaşmış bir çok profesyonel C kütüphanesinde kullanılmaktadır.
Örnek uygulama olarak biraz eğlenceli olması açısından yukarı ok tuşu ile hızlanan ve ona bağlı olarak yakıtı azalan, aşağı ok tuşu ile yavaşlayan, f tuşu ile yakıt doldurulabilen ve ESC tuşu ile sonlanan bir araba nesnesini örnekledim. Bu implementasyonu yukarıda bahsettiğim gibi iki farklı tasarımda gerçekleştirdim. Uygulama PC üzerinde çalışmaktadır.
İlk tasarım aşağıdaki gibidir. Bu şekilde yazılan kodlara bazı kaynaklarda semi-object-oriented kod adı da verilmektedir. Çünkü bu teknikte her ne kadar araba nesnesi kapsüllense de ona ait değişkenler dışarıya açık durumdadir. Bilgi gizleme bu teknikte uygulanmamaktadır.
Bu tasarımda car_t adında bir kullanıcı tanımlı yapı (struct) oluşturdum. Bu nesnenin tüm öğeleri header dosyasında ve dışarıya açık (public) bir durumdadır. Dışarı açık arayüze sahip bu fonksiyonlar araba nesnesine erişip işlevlerini gerçekleştirmektedir.
main.c dosyasında car.h başlık dosyası çağrılıp car_t’den car adında bir nesne ürettik. Bu nesne otomatik ömürlü bir nesnedir. Daha sonra bu nesneyi costructor API’sini kullanarak ilk değerleriyle başlattık ve arabanın hızlanmasını, yavaşlamasını ve yakıt almasını sağlayacak kaynak dosyamızda tanımlı dışarı açık API’lere parametre olarak gönderdik. Programın çalişma zamanında da hız ve yakıt değişkenleri kullanıcının seçimlerine göre değişmiş oldu.
Şimdi bu tasarımda evet herşey güzel çalışıyor görünüyor ama şöyle bir durum var. Burada araba nesnesinin elemanlarına client kod istediği gibi ulaşabiliyor. Biz her ne kadar dışarı açık API’ler ile gerekli işlevleri sağlasak da client kodun halen bu araba nesnesinin öğelerini yanlışlıkla değiştirme olasılığı bulunuyor. Yani sadece değiştirme olasılığı değil şu açıdan da düşünmemiz gerekiyor. Bilgi gizleme kavramını burada uygulayamamış oluyoruz.
1. Tasarım
2. Tasarım
Bu tasarımda C’nin incomplete type / forward bildirim özelliğini kullanarak başlık dosyasında sadece struct nesnesinin bildirimini veriyoruz. Yani client kod için bu nesne sadece public API’ler ile kullanılacak bir nesnedir bunun dışında bu nesnenin hiçbir elemanına erişim mümkün olmamaktadir. Ek olarak tüm değişkenleri client koda kapadığımız için gerekli parametreler için getter/setter API’leri de oluşturulmuştur.
Daha önceden struct tanımlamasını başlık dosyasında yapmak yerine bu sefer kaynak dosyada yapıyoruz. Bu kaynak dosyasındaki fonksiyonlar struct elemanlarına ulaşabilirken header dosyasında sadece struct bildirimini yaptığımız için client kod bu nesneden sadece dinamik olarak nesne oluşturabilecektir.
Bu tasarımda araba objesi artık dinamik ömürlü bir nesne haline gelmektedir. Yani araba nesnesi ile işimiz bittiği zaman araba nesnesinin hayatını sonlandırmak zorundayiz. Bir önceki tasarımda destructor dummy amaçlı bir görev yapıyordu ancak bu tasarımda asıl görevini yapacak ve dinamik bellekte var olan araba nesnesinin hayatını sonlandıracaktır. Destructor fonksiyonunu çağırmadığımız zaman tasarımsal olarak memory leak sorununu ortaya çıkarmış oluruz.
car nesnesine client kodun -> (member selection operator) ile ulaşması mümkün değildir. Böylelikle car.h dosyasında client kod (main.c) kendisi için gerekli API’ler ile işlem yapmaktadir ve struct elemanın nesnelerine ulaşıp bu nesnenin elemanlarını değiştirebilme yetkisi de bulunmamaktadır. Bu tasarım ile hem kapsülleme hem de bilgi gizleme kavramlarını C’de dolaylı olarak gerçekleştirme imkanı elde ettik.
Diğer yazılarımda görüşmek üzere…