Kompozisyon(Composition) kavramının Python’da Miras(Inheritance) kavramı ile yakından ilişkisi vardır. Her ikisi de, iki sınıf arasındaki ilişkiyi tanımlayarak, kodun yeniden kullanılabilmesini sağlarlar, fakat bunu farklı şekillerde yaparlar.
Aynı zamanda, daha önce bahsettiğimiz gibi Soyutlama(Abstraction) da Kompozisyon(Composition) ile yapılabilir.
Kompozisyon temelinde bir ilişkiyi tanımlayan bir kavramdır. Farklı türlerdeki nesneleri birleştirerek kompleks türler oluşturmayı sağlar. Bu bir sınıfın diğer bir sınıfın nesnelerini içerebileceği anlamına gelir.
Kompozisyon oluşturulurken, içinde diğer sınıfların nesnelerini barındıran sınıflar Bileşik Sınıflar(Composite Class), daha karmaşık türler oluşturulmak üzere kullanılan sınıflara ise Bileşenler(Components) adı verilir.
Kompozisyon, özellikleri(değişkenleri) ve metodları diğer sınıflardan miras almak yerine, nesnelere başka nesneler ekleyerek kodu yeniden kullanmanıza olanak tanır.
Kompozisyon, bileşik sınıfların içerdiği bileşenlerin metodlarının yeniden kullanılabilmesine izin verir. Bileşik sınıf bileşenin sınıfından miras almaz ancak, metodlarından yararlanabilir.
İki sınıf arasındaki kompozisyon ilişkisinin gevşek bir şekilde bağlı olduğu kabul edilir. Bu, bileşen sınıfındaki değişikliklerin nadiren bileşik sınıfı etkilediği ve bileşik sınıftaki değişikliklerin bileşen sınıfını hiçbir zaman etkilemediği anlamına gelir. Bu durum, mevcut kodu etkilemeden yeni gereksinimlere uygun bir şekilde uygulamanın değiştirilmesine daha iyi uyum sağlar.
Biri miras ile diğeri kompozisyon ile ele alınmış iki uygulama tasarımını karşılaştırdığınızda, kompozisyon yaklaşımının genellikle daha esnek olduğunu görürsünüz.
Şimdi kompozisyonun nasıl kodlandığına bakalım;
Daha önce örneklerimizde kullandığımız Personel() sınıfımıza ek olarak bir de Adres() isimli bir sınıf oluşturalım.
class Personel: def __init__(kisi,ad,soyad, programlamaDili, deneyimSuresi): kisi.ad=ad kisi.soyad=soyad kisi.programlamaDili=programlamaDili kisi.deneyimSuresi=deneyimSuresi class Adres: def __init__(kisi, acikAdres, ilce, sehir, semt=""): kisi.acikAdres=acikAdres kisi.semt=semt kisi.ilce=ilce kisi.sehir=sehir # Adres verisinin şık görünmesi için __str__ metodu oluşturalım. def __str__(kisi): detaylar=[kisi.acikAdres] if kisi.semt: detaylar.append(kisi.semt) detaylar.append(f'{kisi.ilce},{kisi.sehir}) return '\n'.join(detaylar)
Gördüğünüz gibi adres verimizin daha şık ve okunabilir olması için __str__() metodu kullandık. Artık print() fonksiyonuyla adres verisini yazdırmak istediğimizde bizim formatladığımız şekliyle ekrana gelecek.
Yeri gelmişken çift alt çizgi ile yazılan bu metodlardan biraz bahsedelim. Bu metodlar çift alt çizgi ifadesinin ingilizce bir kısaltması olarak Dunder(double underscore) metodları olarak adlandırılırlar. Bir sınıf tanımladığınızda __init__() gibi bir takım dunder metodlar sizin için oluşturulurlar. Dilerseniz bu metodların yaptıkları işi siz yeniden tanımlayarak varsayılan davranışlarının üzerine yazıp, kendi özelleştirdiğiniz metodu kullanabilirsiniz. Bir sınıf oluşturduğunuzda bu sınıfın hangi değişkenlere(özelliklere) ve metodlara sahip olduğunu görmek için dir() fonksiyonunu kullandığınızda öntanılı gelen çift altçizgili(dunder) metodları da görürsünüz. Örneğin oluşturduğumuz Personel sınıfında hangi değişken ve metodların tanımlı olduğuna göz atalım;
print(dir(Personel))
Çıktı;
['Goruntule', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
__str__ metodu Python’daki metin dönüştürme metodlarından biridir. Buna benzer bir de __repr__ metodu vardır. Her ikisi de nesnenin metin olarak gösterimini sağlar. Sınıftan oluşturduğunuz bir nesneyi direkt yazdırmak isterseniz nesnenin bilgisayardaki RAM adresini öğrenirsiniz. Örneğin Adres sınıfından bir nesne oluşturalım ve __str__() metodunu kullanmadan önce bu nesneyi ekrana yazdırmaya çalışalım;
class Adres: def __init__(kisi, acikAdres, ilce, sehir, semt=""): kisi.acikAdres=acikAdres kisi.semt=semt kisi.ilce=ilce kisi.sehir=sehir yeniAdres=Adres("Tahran Cad. No:13", "Çankaya","Ankara", "GOP") print(yeniAdres)
Çıktı:
<__main__.Adres object at 0x7f80222304f0>
Şimdi de, __str__() metodu ile nesneyi metine dönüştürerek görüntüleme işlemini yapalım ve nesneyi tekrar ekrana yazdırmaya çalışalım;
class Adres: def __init__(kisi, acikAdres, ilce, sehir, semt=""): kisi.acikAdres=acikAdres kisi.semt=semt kisi.ilce=ilce kisi.sehir=sehir # Adres verisinin şık görünmesi için __str__ metodu oluşturalım. def __str__(kisi): detaylar=[kisi.acikAdres] if kisi.semt: detaylar.append(kisi.semt) detaylar.append(f"{kisi.ilce}/{kisi.sehir}") return '\n'.join(detaylar) yeniAdres=Adres("Tahran Cad. No:13", "Çankaya","Ankara", "GOP") print(yeniAdres)
Çıktı:
Tahran Cad. No:13 GOP Çankaya/Ankara
Burada yaptığımız işlem nesneyi metne dönüştürme ve onu formatlanmış bir şekilde görüntüleme işlemidir. Böylece yazdığınız sınıflarda bir nesne türettiğinizde sınıf içinde tekrar ekranda görüntülemenizi sağlayacak bir metod yazmanıza gerek kalmadan nesneyi ekrana yazdırabilirsiniz.
__repr__() metodu ise daha teknik bilgi veren bir metod olduğu için daha çok geliştirme esnasında hata ayıklama işlemlerinde kullanılır.
Şimdi tekrar Python’da Kompozisyon konumuza geri dönecek olursak; artık Adres() sınıfını kompozisyon ile Personel() sınıfına ekleyebiliriz;
class Personel: def __init__(kisi,ad,soyad, programlamaDili, deneyimSuresi): kisi.ad=ad kisi.soyad=soyad kisi.programlamaDili=programlamaDili kisi.deneyimSuresi=deneyimSuresi kisi.adres=None
Adres verisine başlangıçta None değerini atayarak opsiyonel olmasını sağladık. Böylece, her personelin adres bilgisi girilmemiş olsa da hata vermez.
class Personel: def __init__(kisi,ad,soyad, programlamaDili, deneyimSuresi): kisi.ad=ad kisi.soyad=soyad kisi.programlamaDili=programlamaDili kisi.deneyimSuresi=deneyimSuresi kisi.adres=None def Goruntule(kisi): print(f"Personel Ad-Soyad: {kisi.ad} {kisi.soyad}") print(f"Deneyim Süresi: {kisi.deneyimSuresi}") print() if kisi.adres: print("Adres: ") print(kisi.adres) print() print("Kullandığı Programlama Dilleri: ") veriTuru=type(kisi.programlamaDili) if veriTuru is str: print(" - ", kisi.programlamaDili) elif veriTuru is tuple or list: for dil in kisi.programlamaDili: print(" - ", dil) else: print("Hatalı bir veri girişi yapılmış!") print("") print("-------------------------") print("") class Adres: def __init__(kisi, acikAdres, ilce, sehir, semt=""): kisi.acikAdres=acikAdres kisi.semt=semt kisi.ilce=ilce kisi.sehir=sehir # Adres verisinin şık görünmesi için __str__ metodu oluşturalım. def __str__(kisi): detaylar=[kisi.acikAdres] if kisi.semt: detaylar.append(kisi.semt) detaylar.append(f"{kisi.ilce}/{kisi.sehir}") return '\n'.join(detaylar) personel1 = Personel("Oben","Seven",("Python","PHP","Javascript"),18) personel1.adres=Adres("Tahran Cad. No:13", "Çankaya","Ankara", "GOP") personel2 = Personel("Mert","Demir",("NodeJS","ReactJS"),6) personel3 = Personel("Ceren","Toktay","Javascript", 4) personel1.Goruntule() personel2.Goruntule() personel3.Goruntule()
Çıktı:
Personel Ad-Soyad: Oben Seven Deneyim Süresi: 18 Adres: Tahran Cad. No:13 GOP Çankaya/Ankara Kullandığı Programlama Dilleri: - Python - PHP - Javascript ------------------------- Personel Ad-Soyad: Mert Demir Deneyim Süresi: 6 Kullandığı Programlama Dilleri: - NodeJS - ReactJS ------------------------- Personel Ad-Soyad: Ceren Toktay Deneyim Süresi: 4 Kullandığı Programlama Dilleri: - Javascript -------------------------
Burada yer alan kod örneklerine şu adreste göz atabilirsiniz;
Bağlantıda Kalalım