D Programlama Dili – Programlama dersleri ve D referansı
Ali Çehreli

blok: [block], küme parantezleriyle gruplanmış ifadelerin tümü
genel erişim: [public], herkese açık erişim
korumalı erişim: [protected], belirli ölçüde korumalı erişim
modül: [module], programın veya kütüphanenin işlev ve tür tanımlarından oluşan bir alt birimi
özel erişim: [private], başkalarına kapalı erişim
paket: [package], aynı klasörde bulunan modüller
sarma: [encapsulation], üyelere dışarıdan erişimi kısıtlamak
varsayılan: [default], özellikle belirtilmediğinde kullanılan
... bütün sözlük



İngilizce Kaynaklar


Diğer




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);
    writefln("%s bir %s öğrencidir.", öğrenci.isim, öğrenci.cinsiyet);

Ü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ındaki 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;
    size_t kızToplamı;
    size_t erkekToplamı;

    void ekle(Öğrenci öğrenci) {
        öğrenciler ~= öğrenci;

        final switch (öğrenci.cinsiyet) {

        case Cinsiyet.kız:
            ++kızToplamı;
            break;

        case Cinsiyet.erkek:
            ++erkekToplamı;
            break;
        }
    }

    override string toString() const {
        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));
    writeln(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

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 iç tasarımının ileride rahatça değiştirilebilmesini 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: Programdaki tanımları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:

Bir üyenin veya modül tanımının erişim hakkı aşağıdaki özelliklerden birisi olarak belirtilebilir. Varsayılan erişim public'tir.

Ek olarak, export belirteci program içinde tanımlanmış olanaklara programın dışından da erişilebilmesini sağlar.

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.stdio modülünü eklese, okul modülünü ekleyen başka modüller std.stdio'dan otomatik olarak yararlanamazlar.

Örneğin okul modülü şöyle başlıyor olsun:

module okul.okul;

import std.stdio;       // kendi işi için eklenmiş...

// ...

Onu kullanan şu program derlenemez:

import okul.okul;

void main() {
    writeln("merhaba");   // ← derleme HATASI
}

O yüzden, std.stdio'yu asıl modülün ayrıca eklemesi gerekir:

import okul.okul;
import std.stdio;

void main() {
    writeln("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 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 bölümden 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:

Bu programın oluşturulabilmesi için bütün dosyaların belirtilmesi gerektiğini unutmayın:

$ dmd deneme.d okul/ogrenci.d okul/okul.d -w

İ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() const {
        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 Okul 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;
    size_t kızToplamı;
    size_t erkekToplamı;

public:                                        // 3

    void ekle(Öğrenci öğrenci) {
        öğrenciler ~= öğrenci;

        final switch (öğrenci.cinsiyet) {      // 4a

        case Cinsiyet.kız:
            ++kızToplamı;
            break;

        case Cinsiyet.erkek:
            ++erkekToplamı;
            break;
        }
    }

    override string toString() const {
        string sonuç = format(
            "%s kız, %s erkek; toplam %s öğrenci",
            kızToplamı, erkekToplamı, öğrenciler.length);

        foreach (i, öğrenci; öğrenciler) {
            sonuç ~= (i == 0) ? ": " : ", ";
            sonuç ~= öğrenci.isim;             // 4b
        }

        return sonuç;
    }
}
  1. Bu modülü ekleyen programlar ogrenci modülünü de ayrıca eklemek zorunda kalmasınlar diye; public olarak ekleniyor. Bir anlamda, bu "ekleme", genele açılıyor.
  2. Okul sı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.
  3. Bu sınıfın herhangi bir şekilde kullanışlı olabilmesi için üye işlevler sunması gerekir; ekle ve toString işlevleri, kullanılabilmeleri için public olarak işaretleniyorlar.
  4. Önceki dosyada package olarak işaretlendikleri için, Öğrenci yapı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.stdio;
import okul.okul;

void main() {
    auto öğrenci = Öğrenci("Tolga", Cinsiyet.erkek);
    writeln(öğrenci);

    auto okul = new Okul;

    okul.ekle(Öğrenci("Leyla", Cinsiyet.kız));
    okul.ekle(Öğrenci("Metin", Cinsiyet.erkek));
    okul.ekle(Öğrenci("Nimet", Cinsiyet.kız));

    writeln(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.