D.ershane D Programlama Dili Dersleri

genel erişim: [public], herkese açık erişim
nitelik: [property], 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

Bölümler
İngilizce Kaynaklar
Diğer



Nitelikler

Nitelikler, yapı ve sınıf işlevlerinin sanki üye değişkenlermiş gibi çağrılmalarını sağlayan bir 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;

    // ...
}

Ancak, bu niteliğin diğer kullanımı bunun doğru olamayacağını gösterir. Çünkü 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;               // şimdi 5 eleman var
    assert(dizi.length == 5);

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

Yukarıdaki atama yalnızca bir değer değişikliği değildir. length'e yapılan o atamanın arkasında üye değeri değiştirmekten çok daha karmaşık başka işlemler gizlidir:

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

Nitelikler, üye değişken gibi kullanılmalarına rağmen, duruma göre belki de çok karmaşık işlemleri olan işlevlerdir. Bu işlevler @property belirteciyle işaretlenerek tanımlanırlar.

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;
}

Daha sonradan, bu dikdörtgenin alanını bildiren bir üyesinin olmasını da isteyelim:

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

Şimdiye kadarki derslerde öğrendiğimiz kadarıyla, bunu o söz dizimiyle gerçekleştirmek için bir üçüncü üye eklememiz gerekir:

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

Bu tasarımın sakıncası, bu yapı nesnelerinin tutarsız durumlara düşebilecek olmalarıdır: aralarında her zaman için "en * boy == alan" gibi bir ilişkinin bulunması gerekirken, bu yapının üyeleri kullanıcılar tarafından serbestçe değiştirilebildikleri için bu ilişki kullanım sırasında bozulabilir.

Bir örnek olarak, nesne kurulurken tamamen ilgisiz değerler kullanılabilir:

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

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

struct Dikdörtgen
{
    double en;
    double boy;

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

Not: İşlev bildiriminin sonundaki const, const ref Parametreler ve const Üye İşlevler dersinden 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. Örneğin Dikdörtgen yapısının alanının böyle bir atama ile değiştirilebilmesini isteyelim:

    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örtgenimizin esnek olduğunu kabul edebiliriz: "en * boy == alan" ilişkisini koruyabilmek için kenar uzunluklarının uygun şekilde değişmeleri gerekir.

Niteliklerin atama işleminde kullanılmalarını sağlayan işlev de @property ile işaretlenir. İşlevin ismi yine niteliğin isminin aynısıdır. Atama işleminin sağ tarafında kullanılan değer, bu işlevin tek parametresinin değeri olarak gelir.

alan niteliğine değer atamayı sağlayan bir sınıf şöyle yazılabilir:

import std.stdio;
import std.math;

struct Dikdörtgen
{
    double en;
    double boy;

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

    @property void alan(double yeniAlan)
    {
        const double 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.

Örneğin alan niteliğine dörtte biri kadar bir değer atandığında, yani 200 yerine 50 atandığında, kenarların uzunlukları yarıya iner:

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)
    {
        const double 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 dersinde de anlatıldığı gibi, bu iki işlevin ismini aynı da seçebiliriz:

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

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

Bu derste anlatılan nitelik işlevleri ile daha önceki derslerde 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ı dersinde 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:

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

    @property void alan(double yeniAlan)
    {
        const double büyültme = sqrt(yeniAlan / alan);

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

    @property double en() const
    {
        return en_;
    }

    @property 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ı, nesneye yönelik 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 hiçbir fark 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 yapının veya sınıfın 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.