D.ershane D Programlama Dili Dersleri

alt sınıf: [subclass], başka sınıftan türetilen sınıf
arayüz: [interface], yapının, sınıfın, veya modülün sunduğu işlevler
bildirim: [declare], tanımını vermeden belirtmek
gerçekleştirme: [implementation], kodun oluşturulması
sıradüzen: [hierarchy], sınıfların türeyerek oluşturdukları aile ağacı
soyut: [abstract], somut gerçekleştirmesi verilmemiş olan
üst sınıf: [super class], kendisinden sınıf türetilen sınıf
... bütün sözlük

Bölümler
İngilizce Kaynaklar
Diğer



Arayüzler

interface, yalnızca soyut işlevler içeren bir sınıf gibidir. Hiçbir gerçekleştirme sunmaz, yalnızca bir sınıf arayüzü bildirir.

Not: Yukarıda söylenen artık doğru değil. Bu ders yazıldıktan sonra D'de yapılan değişiklikler, interface'lerin de static ve final işlevler sunmalarına izin verir. Bu dersi değiştireceğim.

Önceki derslerde gördüğümüz class'tan türeme, gerçekleştirme türemesi idi. Yeni tanımlanan sınıf, türetildiği sınıfın bütün üyelerini ve işlevlerini ediniyordu.

Türeme dersinde gördüğümüz gibi; abstract anahtar sözcüğü de üye işlevlerin ve dolayısıyla sınıfların soyut olmalarını sağlıyordu. Hatırlarsanız, soyut sınıfların kendi nesneleri oluşturulamaz; yalnızca onlardan türeyen ve o soyut işlevleri tanımlayan türlerin nesneleri oluşturulabilir.

class'tan türeme özetle şunları getirir:

interface, bütünüyle soyut işlevlerden oluşan bir sınıf olarak düşünülebilir. Tek yaptığı, belirli bir arayüz belirlemek, ve bu arayüzün alt sınıflar tarafından tanımlamalarını şart koşmaktır. Kendi üyeleri veya üye işlevleri bulunamaz, ve bir class'tan türeyemez. Dolayısıyla üye edinmeye değil, belirli bir tür gibi kullanılabilmeyi garanti etmeye yarar.

interface'ten türeme özetle şunu sağlar:

Tanımlanması

class yerine interface yazılarak tanımlanır:

interface SesliAlet
{
    // ...
}

interface, o arayüzün gerektirdiği işlevleri bildirir; ama tanımlarını vermez:

interface SesliAlet
{
    string ses();     // Yalnızca bildirilir (tanımı verilmez)
}

O arayüz ile kullanılabilmeleri için, interface'ten türeyen sınıfların interface'in bildirdiği işlevleri tanımlamaları gerekir.

interface'ten türetme

Türeme söz dizimi class'tan farklı değildir:

class Keman : SesliAlet
{
    string ses()
    {
        return "♩♪♪";
    }
}

class Çan : SesliAlet
{
    string ses()
    {
        return "çın";
    }
}

Üst sınıflarda da olduğu gibi; parametre olarak interface alan işlevler, onları asıl türlerini bilmeden kullanabilirler. Örneğin işlemleri sırasında bir SesliAlet kullanan bir işlev, hangi tür bir sesli alet olduğunu bilmeden onun ses işlevinden yararlanabilir:

void sesliAletKullan(SesliAlet alet)
{
    // ... bazı işlemler ...
    writeln(alet.ses());
    // ... başka işlemler ...
}

Sınıflarda da olduğu gibi, o işlev SesliAlet arayüzünden türeyen her sınıf ile çağrılabilir:

    sesliAletKullan(new Keman);
    sesliAletKullan(new Çan);

Her aletin kendi asıl türünün tanımladığı ses işlevi çağrılır ve sonuçta sırasıyla Keman.ses ve Çan.ses üye işlevlerinin çıktısı görülür:

♩♪♪
çın
Birden fazla interface'ten türetme

Bir sınıf, ancak tek bir class'tan türetilebiliyordu. interface'ten türemede ise böyle bir kısıtlama yoktur.

Örneğin haberleşme aletlerini temsil eden şöyle bir arayüz olduğunu düşünelim:

interface HaberleşmeAleti
{
    void konuş(string mesaj);
    string dinle();
}

Telefon diye bir sınıfı hem sesli bir alet, hem de bir haberleşme aleti olarak kullanabilmek için onu bu iki arayüzden birden türeterek tanımlayabiliriz:

class Telefon : SesliAlet, HaberleşmeAleti
{
    // ...
}

O tanım şu iki ilişkiyi birden sağlar: "telefon bir sesli alettir", ve "telefon bir haberleşme aletidir".

Nesnelerinin oluşturulabilmesi için, Telefon sınıfının bu iki arayüzün gerektirdiği bütün işlevleri tanımlaması gerekir:

class Telefon : SesliAlet, HaberleşmeAleti
{
    string ses()                       // SesliAlet için
    {
        return "zırrr zırrr";
    }

    void konuş(string mesaj)           // HaberleşmeAleti için
    {
        // ... mesajı hatta ilet ...
    }

    string dinle()                     // HaberleşmeAleti için
    {
        string hattaDuyulanSes;
        // ... sesi hattan oku ...
        return hattaDuyulanSes;
    }
}

Bu örnekte görüldüğü gibi iki interface ile sınırlı değildir; programın gerekleri doğrultusunda sınırsız sayıda interface'ten türetilebilir.

interface'ten ve class'tan türetme

Bir sınıf; bir veya daha fazla interface'ten türetilmenin yanında, bir adet olduğu sürece aynı zamanda bir sınıftan da türetilebilir:

class Saat
{
    // ... kendi gerçekleştirmesi ...
}

class ÇalarSaat : Saat, SesliAlet
{
    string ses()
    {
        return "bi bi biip";
    }
}

ÇalarSaat, Saat'in bütün üyelerini ve üye işlevlerini edinmenin yanında, bir de SesliAlet arayüzünün gerektirdiği ses işlevini tanımlamak zorundadır.

interface'ten interface türetme

Başka bir arayüzden türetilen bir arayüz, kendisinden türetilecek olan sınıfların kendi bildirdiği işlevleri de tanımlamalarını gerektirir:

interface MüzikAleti : SesliAlet
{
    void akortEt();
}

Yukarıdaki tanıma göre; bir MüzikAleti olabilmek için, hem SesliAlet'in gerektirdiği ses işlevini, hem de kendi gerektirdiği akortEt işlevini tanımlamak gerekir.

Örneğin yukarıdaki Keman sınıfını doğrudan SesliAlet arayüzünden türetmek yerine MüzikAleti'nden türetsek, onun bildirdiği akortEt işlevini de tanımlamamız gerekir:

class Keman : MüzikAleti
{
    string ses()             // dolaylı olarak SesliAlet için
    {
        return "♩♪♪";
    }

    void akortEt()           // MüzikAleti için
    {
        // ... akort işlemleri ...
    }
}
Ne zaman kullanmalı

Oldukça sık kullanılır. Hemen hemen bütün sıradüzenlerin en üstünde bir veya daha fazla interface bulunur. En sık karşılaşılan sıradüzenlerden birisi, tek bir interface'ten türeyen basit gerçekleştirme sınıflarından oluşan sıradüzendir:

                 MüzikAleti
                 (interface)
                /   |   \     \
          Kemençe  Saz  Kaval  ...

Daha karmaşık sıradüzenlerle de karşılaşılır, ama bu basit yapı çoğu programın ihtiyacı için yeterlidir.

Bütün alt sınıflarda da bulunan ortak gerçekleştirmelerin bir ara sınıfta tanımlandığı durumlarla da sık karşılaşılır. Alt sınıflar bu ortak sınıftan türerler. Aşağıdaki sıradüzende TelliMüzikAleti ve NefesliMüzikAleti sınıfları, kendi alt türlerinin ortak üyelerini ve üye işlevlerini içeriyor olabilir:

                 MüzikAleti
                 (interface)
                /      \
     TelliMüzikAleti  NefesliMüzikAleti
        /   |  \         /     |  \
  Kemençe  Saz  ...    Kaval  Ney  ...

O ortak sınıflardan türeyen alt sınıflar da kendi daha özel tanımlarını içerebilirler.

Soyutlama

Arayüzler programların alt bölümlerini birbirlerinden bağımsızlaştırmaya yararlar. Buna soyutlama denir. Örneğin müzik aletleri kullanan bir programın büyük bir bölümü yalnızca MüzikAleti arayüzünden haberi olacak şekilde, ve yalnızca onu kullanarak yazılabilir.

Müzisyen gibi bir sınıf, asıl türünü bilmeden bir MüzikAleti içerebilir:

class Müzisyen
{
    MüzikAleti alet;
    // ...
}

Birden fazla müzik aletini bir araya getiren türler, o aletlerin asıl türlerini bilmek zorunda değillerdir:

    MüzikAleti[] orkestradakiAletler;

Programın çoğu işlevi yalnızca bu arayüzü kullanarak yazılabilir:

bool akortGerekiyor_mu(MüzikAleti alet)
{
    bool karar;
    // ...
    return karar;
}

void güzelÇal(MüzikAleti alet)
{
    if (akortGerekiyor_mu(alet)){
        alet.akortEt();
    }

    writeln(alet.ses());
}

Bu şekilde bir soyutlama kullanarak programın bölümlerinin birbirlerinden bağımsız hale getirilmeleri, alt sınıflarda ileride gerekebilecek kod düzenlemelerinin serbestçe yapılabilmelerini sağlar. Alt sınıfların gerçekleştirmeleri bu arayüzün arkasında oldukları için, bu arayüzü kullanan kodlar o değişikliklerden etkilenmemiş olurlar.

Örnek

Yukarıdaki bütün arayüzleri ve sınıfları içeren bir program şöyle yazılabilir:

import std.stdio;

interface SesliAlet
{
    string ses();
}

class Çan : SesliAlet
{
    string ses()
    {
        return "çın";
    }
}

interface MüzikAleti : SesliAlet
{
    void akortEt();
}

class Keman : MüzikAleti
{
    string ses()
    {
        return "♩♪♪";
    }

    void akortEt()
    {
        // ... akort işlemleri ...
    }
}

interface HaberleşmeAleti
{
    void konuş(string mesaj);
    string dinle();
}

class Telefon : SesliAlet, HaberleşmeAleti
{
    string ses()
    {
        return "zırrr zırrr";
    }

    void konuş(string mesaj)
    {
        // ... mesajı hatta ilet ...
    }

    string dinle()
    {
        string hattaDuyulanSes;
        // ... sesi hattan oku ...
        return hattaDuyulanSes;
    }
}

class Saat
{
    // ... Saat'in gerçekleştirilmesi
}

class ÇalarSaat : Saat, SesliAlet
{
    string ses()
    {
        return "bi bi biip";
    }

    // ... ÇalarSaat'in gerçekleştirilmesi
}

void main()
{
    SesliAlet[] aletler;

    aletler ~= new Çan;
    aletler ~= new Keman;
    aletler ~= new Telefon;
    aletler ~= new ÇalarSaat;

    foreach (alet; aletler) {
        writeln(alet.ses());
    }
}

main'in içindeki aletler bir SesliAlet dizisi olduğu için, o diziye SesliAlet'ten türeyen her tür eklenebiliyor. Sonuçta programın çıktısı bütün aletlerin ürettikleri sesleri içerir:

çın
♩♪♪
zırrr zırrr
bi bi biip
Özet