Sarma ve Erişim Hakları
Şimdiye kadar tasarladığımız bütün yapı ve sınıf türlerinin bütün üyeleri dışarıdan erişime açıktı.
Hatırlamak için şöyle bir öğrenci yapısı düşünelim.
enum Cinsiyet { kız, erkek } struct Öğrenci { string isim; Cinsiyet cinsiyet; }
O yapının nesnelerinin üyelerine istediğimiz gibi erişebiliyorduk:
auto öğrenci = Öğrenci("Tolga", Cinsiyet.erkek); dout.writefln(öğrenci.isim, ", bir ", to!string(öğrenci.cinsiyet), " öğrencidir");
Üyelere böyle serbestçe erişebilmek, o üyeleri programda istediğimiz gibi kullanma olanağı sağladığı için yararlıdır. O kod, öğrenci hakkında bilgiyi çıkışa şöyle yazdırır:
Tolga, bir erkek öğrencidir
Ancak, üye erişiminin bu kadar serbest olması sakıncalar da doğurabilir. Örneğin belki de yanlışlıkla, öğrencinin yalnızca ismini değiştirdiğimizi düşünelim:
öğrenci.isim = "Ayşe";
O atama sonucunda artık nesnenin geçerliliği bozulmuş olabilir:
Ayşe, bir erkek öğrencidir
Başka bir örnek olarak, bir grup öğrenciyi barındıran Okul isminde bir sınıfa bakalım. Bu sınıf, okuldaki kız ve erkek öğrencilerin sayılarını ayrı olarak tutuyor olsun:
class Okul { Öğrenci[] öğrenciler; int kızToplamı; int erkekToplamı; void ekle(in Öğrenci öğrenci) { öğrenciler ~= öğrenci; final switch (öğrenci.cinsiyet) { case Cinsiyet.kız: ++kızToplamı; break; case Cinsiyet.erkek: ++erkekToplamı; break; } } override string toString() { return format( "%s kız, %s erkek; toplam %s öğrenci", kızToplamı, erkekToplamı, öğrenciler.length); } }
ekle işlevini kullanarak o sınıfın nesnelerine yeni öğrenciler ekleyebiliriz:
auto okul = new Okul; okul.ekle(Öğrenci("Leyla", Cinsiyet.kız)); okul.ekle(Öğrenci("Metin", Cinsiyet.erkek)); dout.writefln(okul);
ve tutarlı bir çıktı elde ederiz:
1 kız, 1 erkek; toplam 2 öğrenci
Oysa bu sınıfın üyelerine serbestçe erişebiliyor olmak, onun nesnelerini de tutarsız hale getirebilir. Örneğin öğrenciler üyesine doğrudan yeni bir öğrenci eklediğimizi düşünelim:
okul.öğrenciler ~= Öğrenci("Nimet", Cinsiyet.kız);
Yeni öğrenci, toplamları sayan ekle işlevi çağrılmadan eklendiği için bu Okul nesnesi artık tutarsızdır:
1 kız, 1 erkek; toplam 3 öğrenci
Sarma
İşte sarma, bu tür durumları önlemek için üyelere erişimi kısıtlayan bir olanaktır.
Başka bir yararı, kullanıcıların sınıfın iç yapısını bilmek zorunda kalmamalarıdır. Sınıf, sarma yoluyla bir anlamda bir kara kutu haline gelir ve ancak arayüzünü belirleyen işlevler aracılığıyla kullanılabilir.
Kullanıcıların sınıfın üyelerine doğrudan erişememeleri, ayrıca sınıfın üyelerinin ileride rahatça değiştirilebilmelerini de sağlar. Sınıfın arayüzündeki işlevlerin tanımına dokunulmadığı sürece, içinin yapısı istendiği gibi değiştirilebilir.
Sarma, kredi kartı numarası veya şifre gibi değerli veya gizli verilere erişimi kısıtlamak için değildir, ve bu amaçla kullanılamaz. Sarma, program geliştirme konusunda yararlı bir olanaktır: tanımlarımızın kolay ve doğru kullanılmalarını ve kolayca değiştirilebilmelerini sağlar.
Erişim hakları
D'de erişim hakları iki bağlamda belirtilebilir:
- yapı veya sınıf düzeyinde: her yapı veya sınıf üyesinin erişim hakkı ayrı olarak belirtilebilir
- modül düzeyinde: modül içinde tanımlanmış olan her tür olanağın erişim hakkı ayrı olarak belirtilebilir: sınıflar, yapılar, işlevler, enum'lar, vs.
Üyelerin veya modül tanımlarının erişim hakları, aşağıdaki dört özellikten birisi olarak belirtilebilir.
public, genel: Programın her tarafından erişilebilmeyi ifade eder; hiçbir erişim kısıtlaması yoktur.private, özel: özel erişimi ifade ederpackage, pakede açık: paketteki modüller tarafından erişilebilmeyi ifade ederprotected, korumalı: türetilen sınıf tarafından da erişilebilmeyi ifade eder
Bunun bir örneği olarak dout standart akımını düşünebilirsiniz. Onu bildiren std.cstream modülünün import ile eklenmesi, dout'un serbestçe kullanılabilmesi için yeterlidir.
Bu şekilde tanımlanan üyelere içinde tanımlı oldukları sınıfın kodları tarafından, veya o sınıfı barındıran modüldeki kodlar tarafından erişilebilir.
Modüllerin özel olarak ekledikleri modüllere yalnızca o modül tarafından erişilebilir.
Bu şekilde işaretlenmiş olan bir tanım, onu barındıran paketteki bütün kodlara açıktır. Bu erişim hakkı, yalnızca modülü içeren en içteki pakede verilir.
Örneğin hayvan.omurgalilar.kedi isimli bir modül içinde package olarak işaretlenmiş olan bir tanım; kedi modülünün kendisinden başka, omurgalilar pakedindeki bütün modüllere de açıktır.
private erişimi genişleten bir erişimdir: bu şekilde işaretlenmiş olan üyeye, sınıf tarafından erişilmek yanında, o sınıftan türetilen sınıflardan da erişilebilir.
Belirtilmesi
Erişim hakları üç şekilde belirtilebilir.
Tek bir tanımın önüne yazıldığında yalnızca o tanımın erişim haklarını belirler. Bu, Java ve bazı başka dillerdeki gibidir:
private int birSayı; private void birİşlev() { // ... }
İki nokta üst üste karakteriyle yazıldığında, aynı şekilde yeni bir erişim hakkı yazılana kadarki bütün tanımları etkiler. Bu, C++'daki gibidir:
private: // ... // ... buradaki bütün tanımlar özel ... // ... protected: // ... // ... buradakiler korumalı ... // ...
Blok söz dizimiyle yazıldığında bütün bloğun içini etkiler:
private { // ... // ... buradakilerin hepsi özel ... // ... }
Bu üç yöntemin etkisi aynıdır. Hangisini kullanacağınıza tasarımınıza uygun olduğunu düşündüğünüz şekilde serbestçe karar verebilirsiniz.
import'lar normalde modüle özeldir
import ile eklenen modüller, o modülü dolaylı olarak ekleyen başka modüller tarafından görülemezler. Örneğin okul modülü std.cstream modülünü eklese, okul modülünü ekleyen başka modüller std.cstream'den otomatik olarak yararlanamazlar.
Örneğin okul modülü şöyle başlıyor olsun:
module okul.okul; import std.cstream; // kendi işi için eklenmiş... // ...
Onu kullanan şu program derlenemez:
import okul.okul; void main() { dout.writefln("merhaba"); // ← derleme HATASI }
O yüzden, std.cstream'i asıl modülün ayrıca eklemesi gerekir:
import okul.okul; import std.cstream; void main() { dout.writefln("merhaba"); // şimdi derlenir }
Bazen, eklenen bir modülün başka modülleri de otomatik ve dolaylı olarak sunması istenebilir. Örneğin okul isimli bir modülün eklenmesinin, ogrenci modülünü de otomatik olarak eklemesi istenebilir. Bu, import işlemi public olarak işaretlenerek sağlanır:
module okul.okul; public import okul.ogrenci; // ...
Artık okul modülünü ekleyen modüller ogrenci modülünü açıkça eklemek zorunda kalmadan, onun içindeki Öğrenci yapısını kullanabilirler:
import std.cstream; import okul.okul; void main() { auto öğrenci = Öğrenci("Tolga", Cinsiyet.erkek); // ...
O program yalnızca okul modülünü eklediği halde Öğrenci yapısını da kullanabilmektedir.
Sarmayı ne zaman kullanmalı
Sarma, giriş bölümünde gösterdiğim sorunları önlemek ve sınıf tasarımlarını serbest bırakmak için çok etkili bir olanaktır.
Üyelerin ve başka değişkenlerin ilgisiz kodlar tarafından serbestçe değiştirilmeleri önlenmiş olur. Böylece, yukarıdaki ekle işlevinde olduğu gibi, nesnelerin tutarlılıkları denetim altına alınmış olur.
Ayrıca, başka kodlar yapı ve sınıf gerçekleştirmelerine bağımlı kalmamış olurlar. Örneğin Okul.öğrenciler üyesine erişilebiliyor olması, sizin o diziyi daha sonradan örneğin bir eşleme tablosu olarak değiştirmenizi güçleştirir. Çünkü bu değişiklik kullanıcı kodlarını da etkileyecektir.
Sarma, nesne yönelimli programlamanın en yararlı olanakları arasındadır.
Örnek
Yukarıdaki Öğrenci yapısını ve Okul sınıfını sarmaya uygun olacak şekilde tanımlayalım ve küçük bir deneme programında kullanalım.
Bu örnekte toplam üç dosya tanımlayacağız. Önceki dersten de hatırlayacağınız gibi; "okul" isminde bir klasör içinde tanımlandıkları için ilk ikisi okul pakedinin parçaları olacaklar:
- "okul/ogrenci.d":
Öğrenciyapısını içerenogrencimodülü - "okul/okul.d":
Okulsınıfını tanımlayanokulmodülü - "deneme.d": küçük bir deneme programı
İlk önce "okul/ogrenci.d" dosyasını görelim:
module okul.ogrenci; import std.string; import std.conv; enum Cinsiyet { kız, erkek } struct Öğrenci { package string isim; package Cinsiyet cinsiyet; string toString() { return format("%s bir %s öğrencidir", isim, to!string(cinsiyet)); } }
Bu yapının üyelerini yalnızca kendi pakedindeki kodlara açmak için package olarak belirledim; çünkü biraz sonra göreceğimiz Öğrenci sınıfının bu yapının üyelerine doğrudan erişmesini istedim.
Aynı pakedin parçası olsa bile başka bir modül tarafından yapının üyelerine erişilmesi, aslında temelde sarmaya karşıdır. Yine de; Öğrenci ve Okul'un birbirlerinin üyelerine doğrudan erişebilecek kadar yakın tanımlar oldukları düşünülebilir.
Bu sefer de, o modülden yararlanan "okul/okul.d" dosyasına bakalım:
module okul.okul; public import okul.ogrenci; // 1 import std.string; class Okul { private: // 2 Öğrenci[] öğrenciler; int kızToplamı; int erkekToplamı; public: // 3 void ekle(in Öğrenci öğrenci) { öğrenciler ~= öğrenci; final switch (öğrenci.cinsiyet) { // 4a case Cinsiyet.kız: ++kızToplamı; break; case Cinsiyet.erkek: ++erkekToplamı; break; } } override string toString() { string sonuç = format( "%s kız, %s erkek; toplam %s öğrenci", kızToplamı, erkekToplamı, öğrenciler.length); for (int i = 0; i != öğrenciler.length; ++i) { sonuç ~= (i == 0) ? ": " : ", "; sonuç ~= öğrenciler[i].isim; // 4b } return sonuç; } }
- Bu modülü ekleyen programlar
ogrencimodülünü de ayrıca eklemek zorunda kalmasınlar diye;publicolarak ekleniyor. Bir anlamda, bu "ekleme", genele açılıyor. Okulsınıfının bütün üyeleri özel olarak işaretleniyor. Bu sayede sınıf nesnelerinin tutarlılığı güvence altına alınmış oluyor.- Bu sınıfın herhangi bir şekilde kullanışlı olabilmesi için üye işlevler sunması gerekir;
eklevetoStringişlevleri, kullanılabilmeleri içinpublicolarak işaretleniyorlar. - Önceki dosyada
packageolarak işaretlendikleri için,Öğrenciyapısının her iki üyesine de bu modüldeki kodlar tarafından erişilebiliyor.
Son olarak bu iki modülü kullanan program dosyasına da bakalım:
import std.cstream; import okul.okul; void main() { auto öğrenci = Öğrenci("Tolga", Cinsiyet.erkek); dout.writefln(öğrenci.toString()); auto okul = new Okul; okul.ekle(Öğrenci("Leyla", Cinsiyet.kız)); okul.ekle(Öğrenci("Metin", Cinsiyet.erkek)); okul.ekle(Öğrenci("Nimet", Cinsiyet.kız)); dout.writefln(okul); }
Bu program, Öğrenci ve Okul'u ancak genel erişime açık olan arayüzleri aracılığıyla kullanabilir. Ne Öğrenci'nin, ne de Okul'un üyelerine erişemez ve bu yüzden nesneler her zaman için tutarlıdır:
Tolga bir erkek öğrencidir 2 kız, 1 erkek; toplam 3 öğrenci: Leyla, Metin, Nimet
Dikkat ederseniz, o program bu tanımları yalnızca Okul.ekle ve Öğrenci.toString işlevleri aracılığıyla kullanıyor. O işlevlerin kullanımları değiştirilmediği sürece, Öğrenci'nin ve Okul'un tanımlarında yapılan hiçbir değişiklik bu programı etkilemez.
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları