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

genel erişim: [public], herkese açık erişim
nitelik: [property, attribute], bir türün veya nesnenin bir özelliği
özel erişim: [private], başkalarına kapalı erişim
sarma: [encapsulation], üyelere dışarıdan erişimi kısıtlamak
... bütün sözlük



İngilizce Kaynaklar


Diğer




Nitelikler

Nitelikler üye işlevlerin üye değişkenlermiş gibi kullanılmalarını sağlayan olanaktır.

Bu olanağı dinamik dizilerden tanıyorsunuz. Dizilerin length niteliği dizideki eleman adedini bildirir:

    int[] dizi = [ 7, 8, 9 ];
    assert(dizi.length == 3);

Yalnızca bu kullanıma, yani uzunluğu bildirmesine bakarsak, length'in bir üye değişken olarak tasarlandığını düşünebiliriz:

struct BirDiziGerçekleştirmesi {
    int length;

    // ...
}

Oysa bu niteliğin diğer kullanımı bunun doğru olamayacağını gösterir. Dinamik dizilerde length niteliğine yeni bir değer atamak dizi uzunluğunu belki de yeni elemanlar ekleyecek biçimde değiştirir:

    dizi.length = 5;               // Artık 5 eleman var
    assert(dizi.length == 5);

Not: Sabit uzunluklu dizilerde length niteliği değiştirilemez.

Yukarıdaki atama basit bir değer değişikliği değildir. length'e yapılan o atamanın arkasında daha karmaşık başka işlemler gizlidir: Dizinin sığasının yeni elemanlar için yeterli olup olmadığına bakılması, gerekiyorsa daha büyük yeni bir yer ayrılması ve dizi elemanlarının o yeni yerin baş tarafına kopyalanmaları.

Bu açıdan bakınca length'e yapılan atamanın aslında bir işlev gibi çalışması gerektiği görülür.

Nitelikler, üye değişken gibi kullanılmalarına rağmen duruma göre belki de çok karmaşık işlemleri olan işlevlerdir.

İşlevlerin parantezsiz çağrılabilmeleri

Bir önceki bölümde de değinildiği gibi, parametre değeri gerekmeyen durumlarda işlev çağırırken parantez yazmak gerekmez:

    writeln();
    writeln;      // Üsttekinin eşdeğeri

Bu olanak niteliklerle çok yakından ilgilidir. Niteliklerin kullanımında hemen hemen hiçbir zaman parantez yazılmaz.

Değer üreten nitelik işlevleri

Çok basit bir örnek olarak yalnızca en ve boy üyeleri bulunan bir dikdörtgen yapısına bakalım:

struct Dikdörtgen {
    double en;
    double boy;
}

Dikdörtgenin alanını bildiren bir üye daha olsun:

    auto bahçe = Dikdörtgen(10, 20);
    writeln(bahçe.alan);

Şimdiye kadarki bölümlerde öğrendiğimiz kadarıyla, bunu yukarıdaki söz dizimiyle gerçekleştirebilmek için bir üçüncü üye eklememiz gerekir:

struct Dikdörtgen {
    double en;
    double boy;
    double alan;
}

Bu tasarımın sakıncası, bu yapının nesnelerinin tutarsız durumlara düşebilecek olmalarıdır: Aralarında her zaman için "en * boy == alan" gibi bir ilişkinin bulunması gerektiği halde bu ilişki üyeler serbestçe değiştirildikçe bozulabilir.

Hatta, nesne tamamen ilgisiz değerlerle bile kurulabilir:

    // Tutarsız nesne: alanı 10 * 20 == 200 değil, 1111
    auto bahçe = Dikdörtgen(10, 20, 1111);

Böyle durumları önlemenin bir yolu, alan bilgisini D'nin nitelik olanağından yararlanarak sunmaktır. Bu durumda yapıya yeni üye eklenmez; değer, bir işlevin sonucu olarak hesaplanır. İşlevin ismi üye değişken gibi kullanılacak olan isimdir: alan. Bu işlevin dönüş değeri niteliğin değeri haline gelir:

struct Dikdörtgen {
    double en;
    double boy;

    double alan() const {
        return en * boy;
    }
}

Not: İşlev bildiriminin sonundaki const, const ref Parametreler ve const Üye İşlevler bölümünden hatırlayacağınız gibi, bu nesnenin bu işlev içinde değiştirilmediğini bildirir.

Artık o yapıyı sanki üçüncü bir üyesi varmış gibi kullanabiliriz:

    auto bahçe = Dikdörtgen(10, 20);
    writeln("Bahçenin alanı: ", bahçe.alan);

Bu olanak sayesinde, alan niteliğinin değeri işlevde enin ve boyun çarpımı olarak hesaplandığı için her zaman tutarlı olacaktır:

Bahçenin alanı: 200
Atama işleci ile kullanılan nitelik işlevleri

Dizilerin length niteliğinde olduğu gibi, kendi tanımladığımız nitelikleri de atama işlemlerinde kullanabiliriz:

    bahçe.alan = 50;

O atamanın sonucunda alanın gerçekten değişmesi için dikdörtgenin üyelerinin, yani eninin veya boyunun değişmesi gerekir. Bunu sağlamak için dikdörtgenin esnek olduğunu kabul edebiliriz: "en * boy == alan" ilişkisini koruyabilmek için kenar uzunluklarının değişmeleri gerekir.

Niteliklerin atama işleminde kullanılmalarını sağlayan işlevin ismi de niteliğin isminin aynısıdır. Atama işleminin sağ tarafında kullanılan değer bu işlevin tek parametresinin değeri haline gelir.

alan niteliğine değer atamayı da sağlayan bir tür şöyle yazılabilir:

import std.stdio;
import std.math;

struct Dikdörtgen {
    double en;
    double boy;

    double alan() const {
        return en * boy;
    }

    void alan(double yeniAlan) {
        auto büyültme = sqrt(yeniAlan / alan);

        en *= büyültme;
        boy *= büyültme;
    }
}

void main() {
    auto bahçe = Dikdörtgen(10, 20);
    writeln("Bahçenin alanı: ", bahçe.alan);

    bahçe.alan = 50;
    writefln("Yeni durum: %s x %s = %s",
             bahçe.en, bahçe.boy, bahçe.alan);
}

Atama işlemi ile kullanılan işlevde std.math modülünün karekök almaya yarayan işlevi olan sqrt'u kullandım. Dikdörtgenin hem eni hem de boyu oranın karekökü kadar değişince alan da yeni değere gelmiş olur.

alan niteliğine yukarıda dörtte biri kadar bir değer atandığında (200 yerine 50), kenarların uzunlukları yarıya inmiş olur:

Bahçenin alanı: 200
Yeni durum: 5 x 10 = 50
Nitelikler şart değildir

Yukarıdaki örnekteki yapının nasıl sanki üçüncü bir üyesi varmış gibi kullanılabildiğini gördük. Ancak bu hiçbir zaman kesinlikle gerekmez çünkü değişik şekilde yazılıyor olsa da aynı işi üye işlevler yoluyla da gerçekleştirebiliriz:

import std.stdio;
import std.math;

struct Dikdörtgen {
    double en;
    double boy;

    double alan() const {
        return en * boy;
    }

    void alanDeğiştir(double yeniAlan) {
        auto büyültme = sqrt(yeniAlan / alan);

        en *= büyültme;
        boy *= büyültme;
    }
}

void main() {
    auto bahçe = Dikdörtgen(10, 20);
    writeln("Bahçenin alanı: ", bahçe.alan());

    bahçe.alanDeğiştir(50);
    writefln("Yeni durum: %s x %s = %s",
             bahçe.en, bahçe.boy, bahçe.alan());
}

Hatta, İşlev Yükleme bölümünde de anlatıldığı gibi, bu iki işlevin isimleri aynı da olabilir:

    double alan() const {
        // ...
    }

    void alan(double yeniAlan) {
        // ...
    }
Ne zaman kullanmalı

Bu bölümde anlatılan nitelik işlevleri ile daha önceki bölümlerde gördüğümüz erişim işlevleri arasında seçim yapmak her zaman kolay olmayabilir. Bazen erişim işlevleri, bazen nitelikler, bazen de ikisi birden doğal gelecektir. Niteliklerin kullanılmamaları da bir kayıp değildir. Örneğin, C++ gibi başka bazı dillerde nitelik olanağı bulunmaz.

Ancak ne olursa olsun, Sarma ve Erişim Hakları bölümünde gördüğümüz gibi, üyelere doğrudan erişimin engellenmesi önemlidir. Yapı ve sınıf tasarımları zamanla geliştikçe üyelerin kullanıcı kodları tarafından doğrudan değiştirilmeleri sorun haline gelebilir. O yüzden, üye erişimlerini mutlaka nitelikler veya erişim işlevleri yoluyla sağlamanızı öneririm.

Örneğin yukarıdaki Dikdörtgen yapısının en ve boy üyelerinin erişime açık bırakılmaları, yani public olmaları, ancak çok basit yapılarda kabul edilir bir davranıştır. Normalde bunun yerine ya üye işlevler, ya da nitelikler kullanılmalıdır:

struct Dikdörtgen {
private:

    double en_;
    double boy_;

public:

    double alan() const {
        return en * boy;
    }

    void alan(double yeniAlan) {
        auto büyültme = sqrt(yeniAlan / alan);

        en_ *= büyültme;
        boy_ *= büyültme;
    }

    double en() const {
        return en_;
    }

    double boy() const {
        return boy_;
    }
}

Üyelerin private olarak işaretlendiklerine ve o sayede değerlerine yalnızca nitelik işlevleri yoluyla erişilebildiklerine dikkat edin.

Ayrıca aynı isimdeki nitelik işlevleriyle karışmasınlar diye üyelerin isimlerinin sonlarına _ karakteri eklediğime dikkat edin. Üye isimlerinin bu şekilde farklılaştırılmaları nesne yönelimli programlamada oldukça sık karşılaşılan bir uygulamadır.

Yukarıda da gördüğümüz gibi, üyelere erişimin nitelik işlevleri yoluyla sağlanması kullanım açısından farklılık getirmez. en ve boy yine sanki nesnenin üyeleriymiş gibi kullanılabilir:

    auto bahçe = Dikdörtgen(10, 20);
    writeln("en: ", bahçe.en, " boy: ", bahçe.boy);

Hatta, atama işleci ile kullanılan nitelik işlevini bu üyeler için bilerek tanımlamadığımız için enin ve boyun dışarıdan değiştirilmeleri de artık olanaksızdır:

    bahçe.en = 100;    // ← derleme HATASI

Bu da, üyelere yapılan değişikliklerin kendi denetimimiz altında olması açısından çok önemlidir. Bu üyeler ancak bu sınıfın kendi işlevleri tarafından değiştirilebilirler. Nesnelerin tutarlılıkları bu sayede bu türün üye işlevleri tarafından sağlanabilir.

Dışarıdan değiştirilmelerinin yine de uygun olduğu üyeler varsa, atamayı sağlayan nitelik işlevi onlar için özel olarak tanımlanabilir.

@property

Nitelik tanımlarında @property anahtar sözcüğü de kullanılabilir. Ancak, kesinlikle gerekli olmadığından, bu sözcüğün kullanımı önerilmez.

import std.stdio;

struct Foo {
    @property int a() const {
        return 42;
    }

    int b() const {    // ← @property kullanılmadan tanımlanmış
        return 42;
    }
}

void main() {
    auto f = Foo();

    writeln(typeof(f.a).stringof);
    writeln(typeof(f.b).stringof);
}

@property anahtar sözcüğünün tek etkisi, nitelik söz dizimine sahip farklı çeşitten ifadelerin türlerini belirlerken farkedilir. Aşağıdaki çıktıda görüldüğü gibi, f.a ve f.b ifadelerinin türleri faklıdır:

int            ← f.a ifadesinin türü (dönüş değerinin türü)
const int()    ← Foo.b üye işlevinin türü