Anasayfa » 8. TypeScript’de Interface Kullanımı

8. TypeScript’de Interface Kullanımı

by Levent KARAGÖL
10 dakikalık okuma süresi

Bu makalemizde, TypeScript’de Interface’lerin kullanım amaçlarını ve tanımını, Interface’lerin tip, fonksiyon ve sınıf seviyesinde kullanımını, ardından Interface’lerde kalıtım konusunu ve çoklu Interface kullanımını işleyeceğiz.


Interface Kullanımına Neden İhtiyacımız Var?

Interface konusu tasarım kalıplarının giriş kapısı, mimari tasarımların da kilit noktasıdır. Sık karşılaşılan problemlere hazır çözümler sunmak için onlarca yıllık bilgi birikimi ile oluşturulmuş kalıpların uygulanmasına imkân sağlar. Ne yazık ki tasarım kalıplarını tek bir makaleye sığdırmak mümkün değil. Bu konu için ayrı bir seri hazırlamayı planlıyorum. Tasarım kalıpları olmadan Interface’lerin gerekliliğini tam olarak anlamak mümkün değildir ancak bir yerden başlamamız gerekiyor.

Alışagelmiş kullanım şekliyle Interface’ler, sınıfların ortak bir imzaya sahip olmasını garanti altına alıp, ihtiyaç durumunda bir sınıfı bir diğeri ile değiştirmemize imkân tanır. Böylece dışarıdan bir Interface üzerinden çağrı yaptığımızda, aslında bu Interface’i hangi sınıfın uyguladığını ve bize hangi sınıfın cevap vereceğini bilmeden fonksiyonları çağırırız veya değerlere ulaşırız. Bu da loose coupling denilen parçaları birbirinden soyutlanmış, ihtiyaca göre lego gibi sökülüp takılabilen ve dönüştürülebilen mimarilere sahip yazılımlar geliştirmemize imkân sağlar.

Böyle bir mimaride yapılan ek geliştirmeler, mevcut kodlarda minimum, bazen de sıfır değişikliğe sebep olacağından, yapılacak geliştirmenin etki analizini yapmanın maliyeti düşüktür, ortaya çıkması muhtemel hata sayısı çok daha azdır, iş bittikten sonra ortaya çıkan yapıda hedeflenen mimariyi korumak çok daha kolaydır. Özetle, her şey bittikten sonra elimizde büyük bir çamur topu değil, mimari açıdan özenilmiş bir sanat eseri kalır. Tasarım kalıpları serisinde devam etmek üzere bu konuyu burada bırakıyor, Interface’lerin nasıl tanımlandığı konusuna geçiyoruz.


Tip Seviyesinde Interface Kullanımı

Öncelikle TypeScript’de Interface konusu diğer dillerden biraz daha farklı. Normalde Interface’ler sınıfların yapısını standarda sokmak için kullanılırken, TypeScript’de bu standardizasyon tipler seviyesinde başlar. TypeScript’de Özel Tip Tanımı isimli makalemizde “type” ifadesi ile kendi tip tanımımızı yapmıştık. Interface’ler ile tiplere standart getirerek aynı şekilde kendi tip tanımımızı yapabiliriz ve bunlarda opsiyonel veya salt-okunur tipler tanımlayabiliriz. Hatta Interface’ler kalıtımı desteklediği için, katmanlar arası veri taşıyan Dto’lar için genellikle Type yerine Interface’ler tercih edilir.

Not: Interface isimlendirmesinde genel kabul görmüş standart, ilk harfin büyük “I” harfi ile başlaması ve takip eden büyük bir harf ile Interface isminin verilmesi şeklindedir.

Aşağıda basit bir Interface tanımı ve bu Interface tanımına uygun oluşturulan bir nesne örneği bulunmaktadır.

interface IMyInterface {
    value1: number;
    value2: string;
}

// Aşağıdaki örnekte, tüm değerler geçerli şekilde atanmıştır
let myObject1: IMyInterface = {
    value1: 7,
    value2: "Metinsel ifade"
};

// Aşağıdaki örnekte atanan değerler tip tanımına uygun olmadığı için derleme sırasında hataya sebep olur
let myObject2: IMyInterface = {
    value1: "Metinsel ifade",
    value2: false
};

// Aşağıdaki örnekte tip tanımında var olan value2 değeri atanmadığı için derleme sırasında hataya sebep olur
let myObject3: IMyInterface = {
    value1: 8
};

// Aşağıdaki örnekte tip tanımında var olmayan value3 değeri atanmaya çalışıldığı için derleme sırasında hataya sebep olur
let myObject4: IMyInterface = {
    value1: 7,
    value2: "Metinsel ifade",
    value3: true // Hata: value3 tanımlanmamış bir özelliktir
};

Bu örnekte bulunan kod, ilk baştaki “interface” kelimesi haricinde TypeScript’de Özel Tip Tanımı makalemizdeki ilk örnek ile hemen hemen aynıdır. Burada bizim tanımladığımız Interface’e uygun tanımlanacak nesneler için bir standart getirmiş olduk. Bu noktadan sonra biz biliyoruz ki, bu Interface’i kendine uygulamış tüm nesneler sayısal tipte “value1” değerini ve metinsel tipte “value2” değerini içerir.


Fonksiyon Seviyesinde Interface Kullanımı

Şimdi bir adım daha ileri gidelim ve standarda sokma işini fonksiyonlar seviyesine getirelim. Örneğimiz aşağıda yer alıyor.

interface IMyInterface {
    (value1: number, value2: string): void;
}

// Aşağıdaki örnekte, fonksiyon yapısı geçerli şekilde tanımlanmıştır
let myFunction1: IMyInterface = function (value1: number, value2: string): void {

    console.log(`${value1} - ${value2}`);
};


// Aşağıdaki örnekte hem fonksiyona geçilen parametreler, hem de dönen değer Interface tanımına uygun olmadığı için derleme sırasında hataya sebep olur
let myFunction2: IMyInterface = function (value1: string, value2: number): number {

    console.log(`${value1} - ${value2}`);
};


// Aşağıdaki örnekte Interface tanımında var olan value2 parametresi geçilmediği için derleme sırasında hataya sebep olur
let myFunction3: IMyInterface = function (value1: number): void {

    console.log(value1);
};


// Aşağıdaki örnekte Interface tanımında var olmayan value3 parametresi geçilmeye çalışıldığı için derleme sırasında hataya sebep olur
let myFunction4: IMyInterface = function (value1: number, value2: string, value3: Boolean): void {

    console.log(`${value1} - ${value2} - ${value3}`);
};

Burada bizim tanımladığımız Interface’e uygun tanımlanacak tüm fonksiyonlar için bir standart getirmiş olduk. Bu noktadan sonra biz biliyoruz ki, bu Interface’i kendine uygulamış tüm fonksiyonlar sayısal tipte “value1” değerini ve metinsel tipte “value2” parametresini alır, geriye de değer dönmez. Fonksiyonun içinde ne yaptığını bilemeyiz ancak aldığı parametreleri ve döneceği tipi biliriz.


Sınıf Seviyesinde Interface Kullanımı

Bir adım daha ileri gidelim ve standarda sokma işini sınıf seviyesine getirelim. Sınıflarda Interface uygulamak için “implements” sözcüğünü kullanırız. Örneğimiz aşağıda yer alıyor.

interface IMyInterface {
    value1: number;

    myFunction(value2: string): void;
}

// Aşağıdaki örnekte, sınıf yapısı geçerli şekilde tanımlanmıştır
class Class1 implements IMyInterface {
    value1: number = 7;

    myFunction(value2: string): void {
        console.log(`${this.value1} - ${value2}`);
    }
}


// Aşağıdaki örnekte fonksiyon ve değişken isimleri aynı olsa da, tipleri farklı olduğu için derleme sırasında hataya sebep olur
class Class2 implements IMyInterface {
    value1: string = "Metinsel ifade";

    myFunction(value2: number): number {
        console.log(`${this.value1} - ${value2}`);
    }
}


// Aşağıdaki örneğimiz biraz farklı
// Interface tanımında var olan tüm değer ve fonksiyonlar, parametreleri ile birlikte hazır bulunuyor
// Interface tanımında var olmayan bir değer ve fonksiyon da sınıf içerisinde ekstradan bulunuyor
// Bu durum hataya sebep olmaz, sınıf tanımı halen geçerlidir
class Class3 implements IMyInterface {
    value1: number = 7;
    value3: string = "Metinsel ifade";

    myFunction(value2: string): void {
        console.log(`${this.value1} - ${value2}`);
    }

    myFunction2(value4: number): void {
        console.log(`${this.value3} - ${value4}`);
    }
}

Burada bizim tanımladığımız Interface’e uygun tanımlanacak tüm sınıflar için bir standart getirmiş olduk. Bu noktadan sonra biz biliyoruz ki, bu Interface’i kendine uygulamış tüm sınıflar en az sayısal tipte “value1” değerini, ayrıca “myFunction” isminde ve metinsel tipte “value2” parametresini alan bir fonksiyon içerir, Bunun haricinde başka değerlerin ve fonksiyonların bulunması, bu şartları yerine getirdiği sürece Interface kuralını bozmaz.

Buradaki “en az” kısmı çok önemli. Bir sınıf ne kadar ekstra değer, fonksiyon veya işlevsellik içerirse içersin, Interface tanımındaki işlevselliğe sahip olduğu sürece kuralı bozmuş sayılmaz. Böylece örneğin E-Posta gönderen bir sınıfımızla aynı Interface’i uygulayan SMS gönderen bir sınıfı değiştirdiğimizde, programın geri kalanın haberi dahi olmadan E-Posta göndermek yerine SMS göndermeye başlar.


Interface’lerde Kalıtım

Daha önceden TypeScript’de Sınıflar isimli makalemizin “Sınıflarda Kalıtım” başlığı altında, üst sınıfın özelliklerini devralan sınıfları işlemiştik. Benzer mantıkta Interface’ler de bir başka Interface’in özelliklerini kalıtım yoluyla devralabilirler. Böyle bir Interface’i uygulayan sınıflar, her iki Interface’in tüm özelliklerini uygulamak zorundadır. Konuyu bir örnek üzerinde inceleyelim.

interface IMyBaseInterface {
    value1: number;

    myFunction1(value2: string): void;
}

interface IMyInterface extends IMyBaseInterface {
    value3: string;

    myFunction2(value4: Boolean): string;
}


// Bu sınıf her iki Interface'i de eksiksiz uygulamak zorundadır
class MyClass implements IMyInterface {
    value1: number = 7;
    value3: string = "Metinsel ifade";

    myFunction1(value2: string): void {
        console.log(`${this.value1} - ${value2}`);
    }

    myFunction2(value4: boolean): string {
        return `${this.value3} - ${value4}`;
    }
}


Çoklu Interface Kullanımı

Bir sınıfın aynı anda birden fazla Interface’i uygulaması da sık kullanılan bir yöntemdir. Örneğin mesaj göndermekten sorumlu bir sınıf olmanın yanında, aynı zamanda loglama yapmak zorunda olan bir sınıf şartı da getirilebilir. Bu tarz sınıflar, uyguladıkları tüm Interface’lerdeki kurallara ayrı ayrı uymak zorundadır. Bir sınıfın birden fazla Interface’i uygulaması için “implements” sözcüğünden sonra Interface’leri yan yana yazarak, aralarına virgül koymak yeterlidir. Konuyu bir örnek üzerinde inceleyelim.

interface IMessenger {

    sendMessage(receiver: string, message: string): boolean;
}

interface ILogger {

    writeLog(content: string): void;
}


// Bu sınıf her iki Interface'i de eksiksiz uygulamak zorundadır
class MyClass implements IMessenger, ILogger {

    sendMessage(receiver: string, message: string): boolean {

        console.log(`Gönderilecek Kişi: ${receiver} - Gönderilecek Mesaj: ${message}`);

        return true;
    }

    writeLog(content: string): void {

        console.log(content);
    }
}

Böylece TypeScript’de Interface kullanımı konusunun sonuna geldik. İnşallah tasarım kalıpları serisinde hem bu konuyu hem de soyut sınıflar konusunu ve kullanım şekillerini daha derinlemesine inceleme fırsatımız olacak. Bir sonraki makalemizde TypeScript’de Generic tipler konusunu işleyeceğiz.

Herkese iyi çalışmalar…

İLGİNİZİ ÇEKEBİLİR

Bir Yorum Yaz