Dart Dilinde Nesne Tabanlı Programlama(OOP) — Kapsülleme(Encapsulation) Kavramı |3.Bölüm
Merhaba, değerl geliştirici arkadaşlar. Serimizin 3. yazısı olan Kapsülleme ile devam ediyoruz.
Serinin 1. ve 2. yazılarını aşağıdaki bağlantılardan erişebilirsiniz;
1. Yazı;
https://abdullahtas.medium.com/dart-dilinde-nesne-tabanl%C4%B1-programlama-oop-s%C4%B1n%C4%B1f-class-kavram%C4%B1-1-b%C3%B6l%C3%BCm-5f28e29f212e
Değerli arkadaşlar 2. bölümde Kurucu/Yapıcı(Constructor) yapısını öğrendik. Şu ana kadar hem bir sınıf oluşturup o sınıf içindeki özelliklere değer atayabilir seviyeye geldik. Ancak burada şöyle bir sorunumuz var;
Yine sosyal medya uygulaması örneği ile devam edelim. Takipçi özelliğine değer atarken eksi değerler vermek gibi hatalı durumlar için çok müsait bir zeminimiz var. Bu gibi durumların önüne geçmek için kapsülleme yaparız ve özelliklere değer atanma durumuyla ilgili güvenlik önlemi alırız.
Şimdi gelin kod üzerinden konuşalım
Kapsülleme(Encapsulation)
Önceki yazılarımızda sınıflarımızı oluşturduk, onlardan nesneler ürettik ve bu nesneleri constructor’lar sayesinde başlangıç verileriyle, tutarlı bir şekilde hayata getirmeyi öğrendik. Artık şöyle bir kod yazabiliyoruz:
var kullanici = Kullanici('Ali Veli', 150);
Her şey yolunda gibi görünüyor, değil mi? Ama aslında çok büyük bir güvenlik açığımız var. Bir düşünelim:
var kullanici = Kullanici('Ali Veli', 150);
print(kullanici.takipciSayisi); // Çıktı: 150
// Peki ya başka bir geliştirici veya kodun başka bir parçası bunu yaparsa?
kullanici.takipciSayisi = -50;
print(kullanici.takipciSayisi); // Çıktı: -50 ... Bu nasıl olabilir?!
Bir kullanıcının takipçi sayısı negatif olabilir mi? Asla. Veya bir e-posta adresinin @ işareti içermemesi mümkün mü? Hayır. Ama mevcut kodumuzda, nesnenin özelliklerine dışarıdan doğrudan ve kontrolsüz bir şekilde erişilebildiği için bu tür mantık hatalarına kapımız sonuna kadar açık.İşte bu noktada, OOP’nin en temel prensiplerinden biri olan Encapsulation (Kapsülleme) devreye girer. Anlatmaya çalıştım ancak tekrar sormak ve cevaplamaktan zarar gelmez; Neden buna ihtiyacımız var?
Kapsülleme, adından da anlaşılacağı gibi, bir nesnenin verilerini (özelliklerini) ve bu veriler üzerinde işlem yapan metotları bir “kapsül” içine alıp, veriye doğrudan erişimi kısıtlama prensibidir.
Neden böyle bir şeye ihtiyacımız var?
- Veri Bütünlüğünü Korumak (Data Integrity): En önemli sebep budur. Yukarıdaki -50 takipçi örneğinde olduğu gibi, verilerin anlamsız veya geçersiz değerler almasını engelleriz. Veri, sadece bizim belirlediğimiz kurallar çerçevesinde değiştirilebilir.
- Kontrolü Ele Almak: Veriye kimin, ne zaman ve nasıl erişeceğinin kontrolü tamamen bizde olur.
- Esneklik ve Bakım Kolaylığı: Sınıfın iç yapısını, dışarıyı etkilemeden rahatça değiştirebiliriz. Örneğin, takipciSayisi’nı ileride bir List olarak tutmaya karar verirsek, bunu sınıfın dışındaki kodları bozmadan yapabiliriz çünkü dış dünya veriye doğrudan değil, bizim kontrol metotlarımız üzerinden erişiyordur.
Betimlemek gerekirse şöyle düşünün arkadaşlar;
Cüzdanınız ve banka hesabınız olsun. Cüzdanınızı masaya bıraktınız ve hane içindeki herkes cüzdanınızdan istediği gibi para alabiliyor. Ancak banka hesabınız şifre gerektiren, kod gerektiren bir yapıda olduğu için şifreyi bilmeyen, kodu bilmeyen kişiler para transfer edemez. Yani cüzdanınız halka açıkken banka hesabınız sadece şifresini bilenlere açıktır. Belki biraz garip bir örnek oldu ama kapsüllemede tam olarak böyle..
Şimdi gelin kod üzerinde bu yapıyı nasıl kullanıyoruz ona göz atalım
Dart’ta Kapsülleme Nasıl Yapılır?
Kapsülleme iki temel adımda gerçekleştirilir:
- Veriyi Gizle (Private Yap): Dış dünyanın doğrudan erişmesini istemediğimiz özelliği “private” (özel) hale getiririz.
- Kontrollü Erişim Sağla: Bu özel veriye erişmek için “public” (genel) metotlar (Getter ve Setter) sunarız.
Dart dilinde, bir sınıf üyesinin (özellik veya metot) adının başına _ (alt çizgi) koyarsanız, o üye private olur. Bu, o özelliğe sadece aynı dosya (.dart dosyası) içinden erişilebileceği, dosya dışından erişilemeyeceği anlamına gelir.
Hadi Kullanici sınıfımızı güncelleyelim:
// kullanici.dart dosyası
class Kullanici {
String ad;
int _takipciSayisi; // Artık bu özellik private!
Kullanici(this.ad, int baslangicTakipci) {
// Constructor içinde de kontrol yapmalıyız!
_takipciSayisi = (baslangicTakipci >= 0) ? baslangicTakipci : 0;
}
}
int _takipciSayisi özelliğimizi private yani dışarıdan erişime kapalı olarak tanımladık. Şimdi bu sınıf dışından bu özelliğe erişmeye çalışalım bakalım neler olacak;
// main.dart dosyası
import 'kullanici.dart';
void main() {
var kullanici = Kullanici('Ali Veli', 150);
// HATA! '_takipciSayisi' is private and can't be accessed from 'main.dart'.
// print(kullanici._takipciSayisi);
// kullanici._takipciSayisi = -50; // Bu da HATA verir!
}
Yukarıdaki örnekteki gibi bir kullanım yaptığımızda hata alırız ve özelliğimize değer atayamayız yani özelliğimizi koruma altına aldık diyebiliriz. Ama şimdi de bir sorun var: Dışarıdan kimse bu veriyi okuyamıyor veya güncelleyemiyor. Bu veriyi okumak istediğimiz zaman ya da güncellemek istediğimiz zaman ne yapacağız?
Burada iki kavram ile tanışmamız gerekiyor; Setter ve Getter..
Setter — Getter
Bu yapıların amacı private olarak tanımlanan yani kapsül içine alınan verilerin okunması ve güncellenmesi gereken durumlarda ortaya çıkıp bu işlemleri yapmaktır.
Getter: Private bir veriyi dışarıya okumak için kullanılan özel bir metottur.
Setter: Private bir veriyi dışarıdan gelen bir değerle güncellemek için kullanılan özel bir metottur. En önemli özelliği, gelen veriyi bir kontrolden (validasyon) geçirebilmesidir.
Şimdi kullanıcı sınıfımıza bu yapıları ekleyelim.
// kullanici.dart dosyası
class Kullanici {
String ad;
int _takipciSayisi;
Kullanici(this.ad, int baslangicTakipci) {
// Setter'ı kullanarak başlangıç değerini bile kontrolle atıyoruz.
takipciSayisi = baslangicTakipci;
}
// GETTER: _takipciSayisi'ni okumak için bir kapı açıyoruz.
// "int get" ile tanımlanır ve normal bir özellik gibi çağrılır.
int get takipciSayisi {
print('Getter çalıştı!');
return _takipciSayisi;
}
// Daha kısa yazımı:
// int get takipciSayisi => _takipciSayisi;
// SETTER: _takipciSayisi'ni güncellemek için bir kapı açıyoruz.
// "void set" ile tanımlanır ve atama operatörü (=) ile tetiklenir.
set takipciSayisi(int yeniDeger) {
print('Setter çalıştı! Gelen değer: $yeniDeger');
if (yeniDeger >= 0) {
// KURAL: Sadece gelen değer 0 veya daha büyükse ata.
_takipciSayisi = yeniDeger;
} else {
// KURAL İHLALİ: Negatif değer gelirse 0 ata veya hata fırlat.
_takipciSayisi = 0;
print('Hata: Takipçi sayısı negatif olamaz. 0 olarak ayarlandı.');
}
}
}
Kodları incelediyseniz get takipciSayisi ve set takipciSayisi adında iki yeni metot olduğunu göreceksiniz. Bu metotlardan get bize takipci sayımızı verirken set ise takipci sayısının artışını kontrol ediyor ve duruma göre güncelliyor. Dikkat ettiyseniz get ve set metotlarını sınıfımızın içine tanımladık. Şimdi gelin bu son sınıfımızı nasıl kullanabiliriz ona bakalım.
// main.dart dosyası
import 'kullanici.dart';
void main() {
var kullanici = Kullanici('Ali Veli', 150);
// Getter'ı çağırmak (sanki normal bir özellikmiş gibi)
print('Kullanıcının takipçi sayısı: ${kullanici.takipciSayisi}');
// Çıktı:
// Setter çalıştı! Gelen değer: 150
// Getter çalıştı!
// Kullanıcının takipçi sayısı: 150
print('\n--- Değer Güncelleniyor ---');
// Setter'ı çağırmak (sanki normal bir özelliğe atama yapıyormuş gibi)
kullanici.takipciSayisi = 200;
print('Yeni takipçi sayısı: ${kullanici.takipciSayisi}');
// Çıktı:
// Setter çalıştı! Gelen değer: 200
// Getter çalıştı!
// Yeni takipçi sayısı: 200
print('\n--- Hatalı Değer Atanmaya Çalışılıyor ---');
kullanici.takipciSayisi = -50;
print('Son durumdaki takipçi sayısı: ${kullanici.takipciSayisi}');
// Çıktı:
// Setter çalıştı! Gelen değer: -50
// Hata: Takipçi sayısı negatif olamaz. 0 olarak ayarlandı.
// Getter çalıştı!
// Son durumdaki takipçi sayısı: 0
}
Önce kullanici adında bir nesne oluşturup ad ve takipçi sayısını verdik. Kullanıcının takipci sayısını almak için bir özelliği veya metodu çağırmaktan farklı bir işlem yapmıyoruz. Aynı şekilde oluşturduğumuz nesnenin takipçi sayısını güncellemek içinde = operetörü devreye giriyor.
Set ve Get metotlarını kullanırken .(nokta) işareti ile erişip herhangi bir değer ataması yapmadan bırakıyorsak bu get işlemini tetikler .(nokta) işaretini kullanıp = operatörüyle değer atarsak buda set işlemini tetikler. Kafanız bu noktada karışmasın.
Artık Kapsülleme sayesinde, _takipciSayisi özelliğimizin bütünlüğünü korumayı başardık. Dış dünya, özelliğe kullanici.takipciSayisi şeklinde sanki doğrudan erişiyormuş gibi hisseder, ama arka planda bizim Getter/Setter metotlarımız çalışarak tüm kontrolleri yapar.
Evet 3. bölümün sonuna geldik. Buraya kadar okuyan ve destek olan herkese çok teşekkür ederim. Umarım ilgilisi ve meraklısı için faydalı olur.
Seri bir sonraki bölümle devam edecek takipte kalmaya devam edin
Github: www.github.com/abdullah017
Linkedin: www.linkedin.com/in/abdullahtas
Stackoverflow: https://stackoverflow.com/users/13807726/abdullah-t