Nesne yönemli programlama genelde kısa adı olan OOP (İng. Object Oriented Programming) ile anılmaktadır. OOP için biz ölümlülerin karmaşık projelerdeki sıkıntılı olayları gerçek hayata yakınsayarak çözme çabası desek herhalde sürçülisan etmeyiz.
Bu olay biz geliştiricilerin hayatını o kadar kolaylaştırıyor ki anlatırken kendimi Eminönü'nde turistlere mal satmaya çalışan esnaf gibi hissediyorum. - @haydar
Bu yaklaşımı anlatırken hayvanı korkutmamak adına tümevarım tekniğini kullanacağım. :sweat_smile: Korkmayın dinozor avlamak kadar zor değil. :
Mevzuya girmeden önce bilememiz gereken ilk şey sınıf ve nesnelerin ne olduğudur.
Sınıflarımızı, programımıza anlatmak istediğimiz şeylerin somut taslakları olarak düşünebiliriz. Sınıflar betimledikleri şeylerle ilgili hangi verileri ve işlevleri içericeklerini belirtirler. Sınıf adları kural olmasa da büyük harfle başlar.
Örneğin aşağıdaki kod parçasını ele alalım.
Burada Hayvan sınıfımızı class anahtar kelimesiyle oluşturduk. Bu sınıfta olmasını istediğimiz özellikleri/nitelikleri yani property'lerimizi belirttik. Bu nitelikle yukarıda gördüğünüz gibi değişkenler veya sabitlerden olabileceği gibi başka bir sınıfın nesneleri de olabilir. Bir de bunların başında public diye bir şey yazıyor, ne olduğuna sonra değineceğiz.
Nesneler taslaklarımız olan sınıflarımızın örneklemidir. (İng. Instance) Daha basit bir şekilde anlatmak gerekirse oturduğunuz evin projesi sınıfsa, oturduğunuz ev sınıfın nesnesidir. Bu arada nesne ve örneklem aynı şeyi ifade ediyor. Yani instance eşittir object diyebiliriz.
Öncelikle new anahtar kelimesiyle (İng. Keyword) sınıfımızdan nesnelerimizi oluşturabiliriz. Nesnelerimizin property'lerine ve metotlarına -> ve sabitlerine ise tüm nesnelerde aynı kalacağından :: karakterleriyle erişebiliriz.
Yukarıdaki sınıf örneğini devam ettirerek gidelim. Hayvan sınıfımızdan kuş ve köpek adında iki nesne oluşturalım. Bunlar tüm hayvan özelliklerine sahip olduklarından hayvan sınıfı içerisindeki tüm metotlara (Bir sınıfın fonksiyonlarına metot denir.) erişebilir.
Yukarıdaki kodun çıktısı şu şekilde olacaktır.
Peki ben nesnelerimin sonunda yazdırdığım mesajımı "Ben bir <nesnenin nerede yaşadığı>yım ve Dünya'da yaşarım" olarak değiştirip, bunu hayvan sınıfımın içerisinde tanımladığım bir fonksiyonla yaptırabilir miyim? Hem böyle her nesne için aynı şeyi tekrar yazmamış olacağım. Bunun için $this anahtar sözcüğünün ne işe yaradığını bilmemiz gerekiyor. $this içinde bulunduğu sınıftan türetilen nesneyi ifade eder. Bu tanımdan bir şey anlamdıysanız telaşa gerek yok. Sakin olun ve aşağıdaki örneği inceleyin.
Çıktımız şu şekilde olacaktır.
Bir şeyler canlandı mı? Peki o zaman, güzel… Nesne ile sınıfın ne olduğunu anladığımıza göre static şey de nedirmiş öğrenelim…
Herhangi bir nesne oluşturmadan kullanılabilmeye yapılara static (Tr. Durağan) deriz. Yani static property ve metotlar nesnenin oluşup oluşmadığına bakmazlar. Static yapılarda herhangi bir nesne oluşturma şartı olmadığı için sınıf içinde çağrılar $this anahtar kelimesi yerine self anahtar kelimesiyle veya sınfın adıyla, sınıf dışında ise yalnızca sınıfın adıyla gerçekleşir.
Sınıfın içinde ;
Sınıfın dışında ;
Çağrı şekillerini de gördüğümüze göre hemen bir örnekle açıklayalım.
Yukarıdaki önce kod sayacı arttır metodunu kullanarak sayaç statik değişkenini ikişer ikişer arttırıp dörde eşitliyor. Daha sonra sınıf dışından bu değişkene 20 değerini ekliyor. En sonra bir kez daha ikişer arttırıyor.
Çıktısı da şu şekilde;
Ortak özelliklere sahip yapıların birbirinden türetilmesine denir. Örneğin; Otobüs ve otomobil birçok ortak özellikleğe ve işleve sahip olduğu için aynı şeyleri ikisine ayrı ayrı tanımlamak yerine Araç adında bir sınıfta bunları tutabiliriz. Daha sonra bu Araç sınıfından kalıtım/miras alarak özellikler ve işlevleri Otobüs ve Otomobil sınıflarına aktarabiliriz.
Kalıtım alan sınıfa çocuk sınıf (İng. Child class), katılım alınan sınıfa ise ebeveyn sınıf (İng. Parent class) denir. Ebeveyn sınıftaki üyelere parent:: anahtar kelimesiyle erişilir.
Sınıf içinde bir sınıf öyesini çağırıyorsak self:: veya static:: anahtar kelimelerini kullanabiliriz. static çağrıldığı sınıfı döndürürken, self yazılı olduğu sınıfı döndürür. Bu örnek daha açıklayıcı olacaktır.
Ekran çıktsı şu şekildedir;
Sadece bir sınıftan kalıtım alınabilir ancak örnek verecek olursak Çocuk sınıfı baba sınıfı, baba sınıfını kalıtım alabilir. Baba sınıfı da dede sınıfını kalıtım alabilir. Yani kalıtım sırası şu olabilir;
Çocuk->Baba->Dede
Buna rağmen çocuk sınıf hem babayı hem de dedeyi kalıtım alamaz. (@kadirermantr 'ye selamlar :sunglasses:) Yani şu olamaz.
Katılım extends anahtar kelimesiyle alınır. Yine bir örnekle pekiştirelim.
Çıktımız şu olacaktır.
Yukarıdaki örneğe baktığımızda kuş sınıfıyla oluşturduğumuz nesneden çağırdığımız hiç bir sabit (İng. constant), özellik (İng. Property) ve metodun (İng. Method) kuş sınıfı içerisinde yazılmadığını görürüz. Fakat çalışıyor çünkü tüm bu özellik ve işlevleri hayvan sınıfından kalıtım alındı.
Fakat burada aklımıza şöyle bir soru gelebilir. Güvercin (İng. Pigeon) nesnesini doğrudan hayvan sınıfıyla da oluşturabilirdim. Peki neden ayrıyetten kuş diye bir sınıf oluşturdum? Bu örnekte haklı olsanız da bir sonraki başlık bu sorunuzun cevabını verecek…
Sınıfım biçim biçim ölürüm nesnem için, kalıtım bana düşmandır, sevdiğim nesnem için…
Goy goyu bir kenara bırakırsak kalıtım alan bir sınıf, kalıtım özellikleri ve işlevleri değiştirmek veya yeni özellikler vermek isteyebilir.
Haydi bunu yapalım bakalım.
Ahanda çıktısı şu şekilde olacaktır.
Örnekte kuş sınıfına cıvıldama (İng. Tweet) fonksiyonunu eklediğim için güvencin adlı nesneden bu fonksiyonu çağırabildim. Hayvan sınıfından gelen tür özelliğini ezerek (İng. Override) varsayılan bir değer verdim. Böylelikle kullanıcıdan değer almama gerek kalmadı. Üstüne üstük selamlaşma metodunu yeniden yazarak, hayvan sınıfından kalıtım aldığım selamlamlaşma metodunu ezdim.
Bu konuda dikkat etmemiz gerekn iki şey var.
Not : Ezme kurallarında ileride göreceğimiz __construct metodu istisnadır.
Kapsülleme, ha! Vay anam, babam ne kadar da havalı bir isim! Olayın ismi sizi aldatmasın yaptığı işin tanımı çok basit. Sınıflardaki özellik (property) ve işlevlerin (method) başka yerlerden erişilip erişilemeyeceğini denetleyen yapıdır. Yani her önüne gelen istediğine erişemeyecek. Olayı kurgulamak için örnekte kullanacağımız bir metot ve bir konu sıkıştıracağım.
Başlarına geldiği yapının nerelerden erişilebileceğini belirleyen anahtar kelimelerdir.
public : Varsayılandır. Eğer erişim belirliyicisi olarak bir şey yazmadıysanız programlama dili public atar. Her yerden erişilebilen özellik ve davranışları belirtir.
protected : Tanımladığı sınıfı miras alan diğer alt sınıflar tarafından kullanılabileceğini belirtir.
private : Etliye sütlüye karışmadığını sadece tanımlandığı sınıfta erişilebildiğini belirtir.
new anahtar kelimesiyle bir metot oluşturulduğunda otomatik çalıştıralacak metottur. Kurucu metotta istediğimiz parametreleri nesneyi oluşturken () arasına ekleriz. Dikkat ederseniz static fonksiyonları çağrılırken bir nesne oluşturmadığınızdan kurucu metot çağrılmayacaktır.
Yukarıdaki kodun çıktısı şu şekilde olacaktır.
Animal doğdu.
Şimdi kapsüllemeye geri dönelim. Ne demiştik? Ha, önüne gelenin erişmesini engelleyeceğiz demiştik. Şimdi örneğimize bakalım.
Kodumuzun ekran çıktısı şu şekildedir;
Dikkat ettiyseniz önceki örneklerden farklı olarak propertlerimi public yerine private tanımladım. Yani bu propertylerime sınıf dışından erişimi kapattım. Ayrıca private olarak privateWhoami adında bir metot tanımladım. Koda baktığımızda private olan sınıf üyelerine (property ve metotlar) herhangi bir şekilde erişmeye çalıştığımda hata aldığımızı görüyorsunuz.
Peki nasıl bunlara eriştik? Private erişim belirliyici ile erişimi sınıf dışına engelledik, sınıf içinden hala erişebiliyoruz. Bu sınıf üyelerine dışarıdan çağırabildiğimiz metotlar yardımıyla erişebiliriz. Bu şekilde properylerde değer atama ve okuma yazma yapabiliriz. Bu metotlara getter ve setter yani alıcı ve atayıcı metotlar denir. Aynı şekilde private bir metodu araya public bir metot koyarak dışarıdan çağırabiliriz.
Projelerimizde çoğu zaman teke başına geliştirme yapmıyoruz. Her geliştiricinin aynı yapıya uygun şekilde kod yazması için yapıyı açıkladığımız planlara ihtiyacımız vardır. Bu nedenle yapının anlatıldığı soyut iskeletlere ihtiyaç duyarız. Bu yapıların kurulmasına soyutlama denir.
Daha önce sınıflarımızın nasıl çalışıtığını görmüştük. Abstract sınıflar ve fonksiyonlar normal fonksiyon ve sınıflardan farklı olarak başlarına abstract anahtar kelimesi getirerek oluşturulur. Soyut bir yapıda olabilmesi için en az bir adet soyut metot içermesi gerekmektedir. Soyut metodun işlevi soyut sınıfta tanımlanmaz. Sadece bu adda varsa şu parametleri alan bir metoddur diye tanımlanabilir. Soyut metodun dışında somut metotlar da içerebilir. Soyut bir sınıftan new anahtar sözcüğü ile yeni bir nesne oluşturalamaz.
Haydi örneğimiz bir göz atalım.
Kodun çıktısı şu olacaktır.
Önce örnekteki soyut sınıfın içine odaklanalım. construct ve greet olmak üzere iki somu ve talk adında bir soyut fonksiyonum var. Talk metodu soyut olduğundan adını vermek dışında bir tanımlama yapmadık. Bunun yerine yapacağı işlevleri soyut sınıfımızı kalıtım alan çocuk sınıflarda yaptık. Zaten yapmadığımız takdirde kod hata verecekti. Somut fonksiyonlar için ise böyle bir şey yapmama gerek yok.
Soyut sınıflar başka bir soyut sınıfta veya birden fazla arayüzden kalıtım alabilirler fakat aynı isimde metot içeremezler.
Arayüzler kendinden kalıtım alan sınıfların hangi işlevleri yerine getireceğini gösteren bir katologdur. interface anahtar kelimesiyle oluşturulurlar. Arayüzde yazılan işlevlerin içeriği yazılmaz, yalnızca imzaları yazılır. İşlevleri kalıtım alan sınıflar işlevlerin içeriğini kendileri belirtmek zorundadırlar. Arayüzler new anahtar kelimesiyle nesne türetemezler. Arayüz içindeki tüm metotların erişim belirleyicisi public olmak zorundadır. Arayüz içinde özellikler bulunmaz ancak bir istisna olarak sabit (const) tanımı yapılabilir. Arayüzler extends ile başka bir arayüz tarafından kalıtım alınabilir. PHP’de arayüzler diğer OOP destekleyen dillerden farklı olarak static metotlara sahip olabilir. Arayüzler sınıflar tarafından implements anahtar kelimesi ile kalıtım alınabilirler.
Bir sınafın birden fazla arayüzden kalıtım alması durumunu inceleyelim.
Çıktımız şu olacaktır;
Hello World
Çıktısı şu şekilde olacaktır;
Hyvor
INTERFACE CLASS | ABSTRACT CLASS |
---|---|
Interface çoklu kalıtım destekler | Abstract sınıf, birden çok mirası desteklemez |
Sabitler dışında veri üyesi (property) içermez. | Abstract sınıf bir veri üyesi içerir |
Bir interface sınıfı, yalnızca üyenin imzasına atıfta bulunan tamamlanmamış üyeler içerir. | Abstract sınıf hem eksik (yani soyut) hem de eksiksiz üyeler içerir. |
Her şeyin genel olduğu varsayıldığından, bir interface sınıfının varsayılan olarak erişim değiştiricileri yoktur. | Absract bir sınıf, alt öğeler, fonksiyonlar ve özellikler içinde erişim değiştiricileri içerebilir. |
Temel bir şeyi unutmamalıyız: bunlar birbirinin alternatifi olarak kullanılamayacak tamamen farklı iki yapıdır.
Arayüz sınıfları, alt sınıfların kendileri için her şeyi uygulamasını bekleyen içi boş kabuklardır. Soyut sınıflar, yalnızca kabuklar arasındaki ortak bilgi parçasını içermekle kalmaz, aynı zamanda içindeki alt sınıfların boşlukları doldurmasını bekler.
Yukarıdaki görüntü, Sınıf B ve Sınıf C'nin A Sınıfından nasıl miras alındığını ve Sınıf D'nin Sınıf B ve Sınıf C'den miras almak istediğini gösterir. Ancak, A işlevini çalıştırmak istediğimizde A() işlevinin hangi sürümünü çağıracağız( )? B'den mi yoksa C'den mi? Bu aslında Çoklu kalıtım problemidir ve PHP de çoklu kalıtımı desteklemez ve neden desteklemediğini görüyoruz, ancak kodun yeniden kullanılabilirliği için birden fazla sınıfa genişletmek isteyeceğiniz ve DRY (Kendinizi tekrar etmeyin) konseptini takip etmek isteyeceğiniz birçok durum var.
PHP'de bir sınıf sadece diğer bir sınıftan miras alabilir. Yukarıdaki görüntü, Sınıf D'nin Sınıf C'den nasıl miras aldığını gösterir, bu nedenle A() fonksiyonu, C Sınıfının A fonksiyonundan çağrılır. Bir veya daha fazla sınıftan miras almak istersek ne olur? Tekli kalıtım sorunu budur. Peki bunu nasıl çözeceğiz, işte kahramanımız Traitler oluyor.Traits bir sınıfa çok daha benzer, ancak yalnızca metodların ayrıntılı ve tutarlı bir şekilde gruplandırılması içindir Bir sınıf gibi bir trait'i somutlaştıramazsınız, aslında, bir traiti kendi başına somutlaştırmak mümkün değildir. PHP'de özellikleri nasıl uygulayabileceğimize hızlıca bir göz atalım.
Çıktımız ;
Bu kez de traitten gelen metotları ezmeye çalışalım.
Çıktımız;
Traitlerin interface ile kullanımına bir bakalım.
Çıktımız;
Traitlerin metot isimleri çakışırsa ne olacak? Sorun yok, çözüm var…
Çıktımız;
Trait içindeki Erişim Belirleyiciler as anahtar kelimesiyle değiştirilebilir.
Çıktımız;
Şimdi dışarıya çıkın biraz yürüyün, beyninize oksijen girsin :smile: