Interfaces
Go'daki interface türü bir nevi tanım gibidir. Başka bir türün sahip olması gereken method (yöntem) leri tam olarak tanımlar ve açıklar. Bir türün sahip olması gereken tüm metod yapıları kavramıyla OOP'deki class yapılarına benzerlik gösterir.
Bir örnek vermek gerekirse, daire, kare, üçgen, beşgen iki boyutlu geometrik şekiller olup geometrikSekil gibi bir interface türüne ait aittir diyelim. Bu şekillerin hepsi alan() ve çevreUzunluğu() gibi hesaplanabilir method özelliklere sahiptirler.
Bu ifadeyi bir interface haline dönüştürdüğümüzde aşağıdakine benzer bir yapı elde ederiz:
// geometrik sekiller turunun ortak metodları
type geometrikSekil interface {
alan() float64
cevreUzunlugu() float64
}
Bu ifade altında bir kare tanımlayalım :
type kare struct{
genislik, yukseklik float64
}
Şimdi bu karenin metod özelliğini oluşturalım :
func (r kare) alan() float64 {
return r.genislik * r.yukseklik
}
Aynı şekilde bir de çevre uzunluğu hesaplayalım :
func (r kare) cevreUzunlugu() float64{
return (r.genislik + r.yukseklik) * 2
}
şimdi bu tanımlamalarla interface icinde karemizin nasıl davranacağını belirttik. Bu özellikleri görüntülemek için ise başka bir fonksiyon yazıyoruz:
func olcum(g geometrikSekil){
fmt.Println(g.alan())
fmt.Println(g.cevreUzunlugu())
}
Bu yazdığımız tanımlayacağımız her geometrik sekil için genel gecer bir ifadedir. Misal bir daire tanımladık ve daire alanının hesaplaması daha farklı ve pi sayısını işleme dahil ettik. Burada hesaplama kareden farklı olacaktır ve yeni bir alan fonksiyonu belirtmemiz gerekir. Aynı şekilde çevre uzunluğu için de yeni bir fonksiyon belirtmeliyiz. Fakat ölçüm fonksiyonu her yeni şekil için aynı kalmaktadır ve ekrana alan ve cevre uzunluğu yazdırmaktadır.
empty interface tipi aslında hiçbir method'u tanımlamaz. Hiçbir kuralı yoktur. Ve bundan dolayı her nesnenin empty interface yapısını karşıladığı sonucu çıkar.
Polymorphism Kavramı
interface yapıları diğer programlama dillerinde var olan Polymorphism kavramını GO dilinde gerçekleştirmek amacıyla yazılırlar.
Polimorfizm, aynı isimli metodların farklı tiplerdeki nesneler üzerinde farklı şekillerde çalışabilmesini sağlayan bir özelliktir. Bu, aynı metod isminin, farklı sınıflar veya tipler için farklı işlevselliklere sahip olabileceği anlamına gelir. Polimorfizm sayesinde kodlar daha esnek hale gelir ve farklı tipler arasında geçişler daha kolay yapılabilir.
Polymorphism sayesinde daha az kodlama yaparak daha çok çıktı elde edebilirsiniz. interface yapıları GO dilinde bu amaca hizmet eder.
Interface Uygulaması: Selamlamalar
package main
import "fmt"
type Kisi struct {
Ad string
Soyad string
}
type Hayvan struct {
Ses string
}
type Selamlama interface {
merhabaDe() string
}
func (p Kisi) merhabaDe() string {
return "Merhaba benim adım " + p.Ad
}
func (a Hayvan) merhabaDe() string {
return a.Ses
}
func selamla(i Selamlama) {
fmt.Println(i.merhabaDe())
}
func main() {
man := Kisi{Ad: "Billy", Soyad: "Smith"}
dog := Hayvan{Ses: "Hav!"}
fmt.Println("Kisi:")
selamla(man)
fmt.Println("Hayvan:")
selamla(dog)
}
Örneğimizi incelediğimizde ilk olarak struct yapılarıyla nesneler yaratarak işe başlıyoruz. OOP Nesne Yönelimli Programlama'daki nesne yapılarına benzeyen bu yapılar, belirli özellikleri taşıyan nesneler oluşturmak için kullanılırlar. Bir nevi şablon görevi görürler. Kisi struct yapısında olusturulan tüm objeler Ad ve Soyad isimli string yapılarını içerirler. Benzer şekilde Hayvan isimli struct yapısı da Ses isimli özelliği taşır. Buraya kadar olanları structs konusunda anlatmıştık.
type Kisi struct {
Ad string
Soyad string
}
type Hayvan struct {
Ses string
}
Burada yeni olarak eklenen yapımız interface yapısıdır. Belirli çatı vazifesi görür ve bu interface yapısı içinde tanımlanan yapılar interface içindeki metod özelliklerine sahip olurlar.
type Selamlama interface {
merhabaDe() string
}
Bizim örneğimizde Selamlama isimli interface içinde tanımlanan tüm yapılar bu interface içinde yer alan merhabaDe() isimli metodun özelliklerine sahip olurlar.
func (p Kisi) merhabaDe() string {
return "Merhaba benim adım " + p.Ad
}
func (a Hayvan) merhabaDe() string {
return a.Ses
}
Yukarıdaki yazımda gördüğünüz üzere p isimli Kisi struct ifadesi merhabaDe() fonksiyonu altında Merhaba benim adım ... şeklinde Kisi isimli struct ifadesinin Ad isimli özelliğini döndürecek şekilde hareket eder.
Aynı şekilde a isimli Hayvan struct ifadesi de merhabaDe() ile çalıştırıldığında Hayvan isimli struct ifadesinin Ses isimli özelliğini döndürecek şekilde tanımlanmıştır.
func selamla(i Selamlama) {
fmt.Println(i.merhabaDe())
}
Şimdi ise Selamlama isimli interface yapısında çalışmak üzere bir fonksiyon yazıyoruz ve bu fonksiyon merhabaDe() isimli metodu çağırıp ekrana yazdıracaktır.
Bir sonraki aşamada ise nesne tanımlamalarımızı yazıyoruz:
func main() {
man := Kisi{Ad: "Billy", Soyad: "Smith"}
dog := Hayvan{Ses: "Hav!"}
fmt.Println("Kisi:")
selamla(man)
fmt.Println("Hayvan:")
selamla(dog)
}
Burada man isimli bir nesne ve dog isimli bir nesne değişkeni tanımladık. Bu nesneler Kisi ve Hayvan isimli struct yapılarına göre değer alırlar. Tanımlamalar sonrasında bu nesneleri selamla() isimli interface yapısında çağırdık. Böylece bu nesneler merhabaDe() isimli Selamlama interface'inin içindeki method'a sahip olmuş oldu.
fonksiyonu çalıştırdığımızda aşağıdaki çıktıyı elde ederiz.
Kisi:
Merhaba benim adım Billy
Hayvan:
Hav!
Interface Uygulaması: Geometrik Hesaplamalar
Şimdi başka bir örnekle devam edelim :
package main
import (
"fmt"
"math"
)
type geometri interface {
alan() float64
cevre() float64
}
type kare struct {
genislik, yukseklik float64
}
type daire struct {
yaricap float64
}
func (r kare) alan() float64 {
return r.genislik * r.yukseklik
}
func (r kare) cevre() float64 {
return 2*r.genislik + 2*r.yukseklik
}
func (c daire) alan() float64 {
return math.Pi * c.yaricap * c.yaricap
}
func (c daire) cevre() float64 {
return 2 * math.Pi * c.yaricap
}
func olcumle(g geometri) {
fmt.Println(g)
fmt.Println(g.alan())
fmt.Println(g.cevre())
}
func main() {
r := kare{genislik: 3, yukseklik: 4}
c := daire{yaricap: 5}
olcumle(r)
olcumle(c)
}
Bu örneğimizde kare ve daire için hesaplamalar yapıyoruz. Öncelikle geometri isimli bir interface oluşturalım.
type geometri interface {
alan() float64
cevre() float64
}
Bu interface içinde alan() isimli ve cevre() isimli float64 veri döndüren iki metod tanımlanmıştır.
type kare struct {
genislik, yukseklik float64
}
type daire struct {
yaricap float64
}
kare ve daire isimli struct ifadeleri tanımladık.Bu ifadelere ait genislik, yukseklik, yaricap isimli float64 veri tipli özellikler de tanımlanmıştır.
Şimdi alan ve çevre isimli metod ifadeleri tanımlıyoruz.
func (r kare) alan() float64 {
return r.genislik * r.yukseklik
}
func (r kare) cevre() float64 {
return 2*r.genislik + 2*r.yukseklik
}
func (c daire) alan() float64 {
return math.Pi * c.yaricap * c.yaricap
}
func (c daire) cevre() float64 {
return 2 * math.Pi * c.yaricap
}
Burada da hesaplama ifadeleri return kısmında hesaplamaları tanımlıyoruz.
func olcumle(g geometri) {
fmt.Println(g)
fmt.Println(g.alan())
fmt.Println(g.cevre())
}
interface icerisinde olcumleme(g geometri) isimli bir metod yazılmıştır. Burada ekranda görüntüleme işlemleri tanımlanmıştır.
func main() {
r := kare{genislik: 3, yukseklik: 4}
c := daire{yaricap: 5}
olcumle(r)
olcumle(c)
}
r ve c isimli nesneler tanımlayıp bunları olcumle() isimli metoda atıyoruz.
Kodu çalıştırdığımızda aşağıdaki ekran çıktısını elde edeceğiz:
{3 4}
12
14
{5}
78.53981633974483
31.41592653589793
Boş Interface Yapıları - Empty Interfaces
empty interface yani boş interface yapıları içine her şeyi koyabileceğiniz ve kendi tanımlamalarınızı yapabileceğiniz interface yapılarıdır. Bir nevi boş sepet gibidir ve içine koyarsanız o sepeti olurlar. Örnekle daha da pekişecektir.
package main
import (
"fmt"
)
func main() {
VeriTipiGoruntule(62)
VeriTipiGoruntule(5.14)
VeriTipiGoruntule("撃入朝工球全連幼運聞的受携。記決構欺張済新芸九今阪")
}
func VeriTipiGoruntule(i interface{}) {
switch veriTipi := i.(type) {
case int:
fmt.Printf("%d ifadesi bir integer ifadesidir.\n", veriTipi)
case float64:
fmt.Printf("%f ifadesi 64bit float ifadesidir.\n", veriTipi)
case string:
fmt.Printf("%s ifadesi bir string ifadesidir.\n", veriTipi)
default:
fmt.Printf("I don't know what %v is\n", veriTipi)
}
}
Bu ifadede i interface{} ifadesiyle her metod değerini alabilen boş bir interface oluşturduk ve i.(type) ifadesiyle veri tiplerini görüntüleyen bir yapı elde ettik. Hemen ardından switch ifadesiyle durum senaryoları oluşturup bunları test ettik. Eğer ifademiz int veri tipinde ise case int: yolu çalışır ve ekrana ... ifadesi bir integer ifadesidir. şeklinde yazdırır.
Kodumuzu çalıştırdığımızda aşağıdaki sonucu elde edeceğiz:
62 ifadesi bir integer ifadesidir.
5.140000 ifadesi 64bit float ifadesidir.
撃入朝工球全連幼運聞的受携。記決構欺張済新芸九今阪 ifadesi bir string ifadesidir.
Interface ile Type Assertation
Burada bir ifadeye ait veri türünü interface yardımıyla belirleme hakkında bilgi vereceğiz.
package main
import (
"fmt"
)
func main() {
var herhangiDegisken interface{} = "Edirne İstanbul Bursa"
birString := herhangiDegisken.(string) // type assertation
// tür görüntüleme
fmt.Printf("İfade : %s Tür : %T", birString, birString)
}
Komut çalıştırıldığında herhangiDegisken isimli interface{} tipinde değişkenimiz Edirne İstanbul Bursa değerlerini almıştır. Burada ifademis string ifadesi olduğu için type assertation ile ifademizin bir string ifadesi olduğunu belirttiğimizde hata almadan ekranda bir string ifadesi görüntüleriz.
Ekran çıktımız aşağıdaki şekilde olacaktır.
İfade : Edirne İstanbul Bursa Tür : string%
DIKKAT: Burada tür iddiası yardımıyla tür değişimi
type assertioneğer sistem belirtilen yapıyı elde edemezsepanicifadesiyle komutu sonlandırır.
Bu tip dönüşümlerini string ifadelerinden float64 ya da int ifadelerine dönüştürmek için kullanabilirsiniz. Tabi ki alternatifler de her zaman mevcuttur. Uzmanlaştıkça diğer veri tiplerine de dönüşümler gerçekleştirebilirsiniz.