SOLID Tasarım İlkeleri
Bu serinin 1. bölümünde Bağımlılık Enjeksiyonu ve IoC tartışıldı. Bağımlılık Enjeksiyonu derinlemesine girmeden önce, nesne yönelimli tasarım modelinin beş SOLID ilkesini anlamak önemlidir. Bu bölümde ilk üç ilke incelenecektir.
1) Tek Sorumluluk İlkesi (SRP, Single Responsibility Principle)
> Bir sınıfın değişmesi için hiçbir zaman birden fazla sebep olmamalıdır.
Bir şaka duymuş olabilirsin:
- Hata olmadan kod yazdın mı?
- Evet, daha önce yaptım: Merhaba Dünya.
Bu paragraf bize doğruyu söyler: kod ne kadar basitse, bir hata olması o kadar az olasıdır.
Peki kodu nasıl basitleştiririz? Her kod satırını yazarak tek bir şey yapmak için. Uygulamada, bir sınıfın sadece bir şey yapmasına izin verdik. Ve eğer bu sınıfa basit ve sezgisel bir isim verebilirsek, kodun kendisinin bildirilmesi çok iyi olacaktır. Alibaba’nun Java spesifikasyonunun, bir sınıfın bir tasarım deseni kullanıyormuş gibi adlandırma için gereksinimleri olduğunu hatırlıyorum. Tasarım deseninin adını ismin içinde yerleştirmek gerekir. Şu anda, yorumlar gereksizdir ve kod insanların okuması ve bakımı için kolaydır.
Tek sorumluluk ilkesinin tanımına geri dönelim. Martin, bir sorumluluğu değişimin nedeni olarak tanımlar ve bir sınıf veya modülün değiştirilmek üzere bir ve yalnızca bir nedeni olması gerektiği sonucuna varır (yani yeniden yazılması). Örnek olarak, bir rapor derleyen ve yazdıran bir modül düşünün. Böyle bir modülün iki nedenden dolayı değiştirilebileceğini hayal edin. İlk olarak, raporun içeriği değişebilir. İkincisi, raporun formatı değişebilir. Bu iki şey çok farklı nedenlerle değişir. Tek sorumluluk ilkesi, sorunun bu iki yönünün gerçekten iki ayrı sorumluluk olduğunu ve bu nedenle ayrı sınıf veya modüllerde olması gerektiğini söylüyor. Farklı zamanlarda farklı nedenlerle değişen iki şeyi birleştirmek/eşleştirmek, kötü bir tasarım olacaktır.
Bir sınıfı tek bir endişeye odaklamanın önemli olmasının nedeni, sınıfı daha sağlam ve güçlü kılmasıdır. Yukarıdaki örnekten devam edersek rapor derleme işleminde bir değişiklik olursa, aynı sınıfın bir parçasıysa yazdırma kodunun kırılması daha büyük bir tehlikedir.
Ayrıca bir sınıfın tek sorumluluk ilkesine uygun olup olmadığına karar vermek için bir standart vardır. Bu, sınıftaki öğelerin uygunluğuna bağlıdır. Bir yineleyici uygulayan bir sınıfta yazma fonksiyonuna sahip olamazsınız. Bunun hakkında düşünmeyin. En önemseyeceği panonun boyutunu kontrol etmesidir. Başka bir deyişle, panonun kendisi yineleyicilerin sınıfına dahil edilmemeli, yalnızca bir hizmet olarak var olmalı ve Depo modunu kullanmayı düşünebilirsiniz.
Yüksek korelasyon, yüksek uyum ve işlevde daha küçük bir granülite anlamına gelir.
Burada belirtilen sınıf, bir varlığa veya bir servise başvurabilir. Domain-driven Design kitabında Eric, uygulamadaki nesneleri üç türe ayırır: değerler, varlıklar ve hizmetler. Bir varlık, değerlerin bir kombinasyonu olarak düşünülebilir ve bir hizmet, iş mantığını içeren koddur.
2) Açık Kapalı Prensibi (Open Closed Principle - OCP)
> Sınıflar, modüller ve fonksiyonlar gibi yazılım varlıkları; uzantı için açık olmalı ancak varlığın kaynak kodunu değiştirmeden davranışının genişletilmesine izin verebilecek şekilde değişikliklere kapalı olmalıdır.
Açık Kapalı Prensipten bahsedersek bu, dekoratör modeli aracılığıyla anlaşılması özellikle kolay olan bir tasarım desenidir. Bir senaryo hayal edebiliriz. Bir süpermarkette yazarkasa sistemi veya bir e-ticaret web sitesi yapıyoruz. Her halükârda emtia varlığı vardır. Emtia varlığı, bir GetPrice (FiyatAl) yöntemine sahiptir ve şimdi ürüne bir indirim işlevi eklemelisiniz. Malın sınıf kategorisinin tek sorumluluk prensibine uygun olduğunu varsayarsak, şu anda üç seçenek vardır:
1. Şimdi ihtiyacınız olan işlevselliği sağlamak için arayüze bir yöntem ekleyin.
2. Sınıfın uygulamasını modifiye edin ve yeni özellikler imzalamak ve sağlamak için önceki yöntemi kullanın.
3. Bir sınıfı tekrar oluşturun, önceki uygulama sınıfını kalıt alın (inherit), önceki yönteme bir katman yerleştirin ve yeni bir uygulama sağlayın.
Açık Kapalı Prensibi, üçüncü çözümü seçmemizi önerir. Bunun nedeni arayüzü modifiye ederek, bu arayüzü uygulamak isteyen diğer sınıfların uygulamayı eklemesi gerekir. Bu yeni uygulamanın gereksiz olması muhtemeldir, çünkü tüm ürünler için aynı gün içerisinde indirim yapılması muhtemel değildir. İkinci çözüm, sadece tehlikeli bir operasyondur. Kesinlikle birim testini etkileyecektir (eğer varsa) ve hataların ortaya çıkması gibi büyük bir olasılık vardır. Üçüncü seçenek en güvenli olanıdır. Önceki uygulama için bir dekoratör sağlayabiliriz ve ardından yeni işlevsellik sağlamak için dekoratör çağırabiliriz. Önceki örnekte önceki uygulama, ürünün orijinal fiyatını dekoratörün “indirim dekoratörü” olarak adlandırdığı şeyi döndürmektir ve daha sonra indirimli fiyatı döndürmek için aynı yöntemi çağırır. Önceki uygulama, yapıcı parametreleri vasıtasıyla dekoratöre enjekte edilir. Gerçek kullanım o kadar basit olmayabilir ancak bu, prensibin basit bir açıklamasıdır.
Daha sonra enjeksiyonun birleşik kökü hakkında konuştuğunuzda, tekrar Açık Kapalı Prensipten bahsedeceksiniz.
3) Liskov'un Yerine Geçme Prensibi (Liskov Substitution Principle - LSP)
> Temel sınıflara işaretçiler veya referanslar kullanan fonksiyonlar, türetilmiş sınıfların nesnelerini bilmeden kullanabilmelidir.
Bu en gözden kaçan prensiplerden biri gibi görünüyor.
Sorunlara neden olan “is a” tekniğinin klasik örneği daire-elips problemidir (ya da dikdörtgen kare problemdir). Ancak, ben penguen kullanacağım.
İlk önce, gökyüzünde uçan kuşları gösteren bir uygulama düşünün. Birden fazla kuş türü olacaktır ve bu nedenle geliştirici, yeni kuş türlerinin eklenmesiyle ilgili kodu “kapatmak” için Açık Kapalı Prensibini kullanmaya karar verir. Bunu yapmak için, aşağıdaki soyut Kuş temel sınıfı oluşturulur:
BirdsFlyingAroundApp sürümünün büyük bir başarıdır. İkinci versiyon, kolaylıkla başka 12 farklı kuş türünü ekler ve ayrıca bir başarıdır. Yaşasın Açık Kapalı Prensibi! Ancak, uygulamanın üçüncü sürümü penguenleri desteklemek için gereklidir. Geliştirici, Kuş sınıfından kalıt alan yeni bir Penguen sınıfı yapar, ancak bir sorun vardır:
Bir geçersiz kılma yöntemi hiçbir şey yapmazsa veya yalnızca bir istisna atarsa, muhtemelen LSP'yi ihlal ediyorsunuzdur.
Uygulama çalıştırıldığında tüm uçan modeller yanlış görünür, çünkü Penguen nesneleri setAltitude yöntemini önemsemez. Penguenler sadece yerde dolaşıyorlar. Geliştirici OCP'yi izlemeye çalışsa da başarısız oldu. Penguen sınıfına uyması için mevcut kodun modifiye edilmesi gerekir.
Bir penguen teknik olarak “kuş” iken, Kuş sınıfı tüm kuşların uçabileceğini varsaymaktadır. Penguen alt sınıfı uçuş varsayımını ihlal ettiğinden, Kuş sınıfı için Liskov'un Yerine Geçme Prensibi’ni karşılamıyor.
LSP'yi İhlal Etmek Neden Kötüdür?
Soyut bir temel sınıf kullanmanın amacı; gelecekte yeni bir alt sınıf yazıp mevcut, çalışan ve test edilmiş bir koda ekleyebilmenizdir. Açık Kapalı Prensibinin özü budur. Ancak, alt sınıflar soyut temel sınıfın arayüzüne uygun bir şekilde bağlanmadığında, mevcut alt kodu gözden geçirmeniz ve hatalı alt sınıfları içeren özel durumları hesaba katmanız gerekir. Bu, Açık Kapalı Prensibinin açıkça ihlalidir.
Örneğin, bu kod parçasına bir bakınız:
LSP, kodun Kuş nesnesinin gerçek sınıfını bilmeden çalışması gerektiğini söylüyor. Ya devekuşu gibi bir başka uçamayan kuş türü eklemek isterseniz? O zaman mevcut tüm kodunuzu gözden geçirmeli ve Kuş işaretçilerinin gerçekten Devekuşu işaretçisi olup olmadığını kontrol etmelisiniz.
İki Olası Çözüm
Mevcut kodu modifiye etmeden Penguen sınıfını ekleyebilmek isteyebiliriz. Bu, kötü kalıtım hiyerarşisini LSP'yi karşılayacak şekilde düzelterek sağlanabilir.
Sorunu çözmenin çok iyi olmayan bir yolu, isFlightless adlı Kuş sınıfına bir yöntem eklemektir. Bu şekilde, en azından OCP'yi ihlal etmeden uçamayan kuş sınıfları eklenebilir. Bu, şöyle bir kodla sonuçlanacaktır:
Bu gerçekten geçici bir çözümdür. Temel sorunu çözmedi. Sadece sorunun belirli bir nesne için var olup olmadığını kontrol etmenin bir yolunu sağlar.
Uçamayan kuş sınıflarının uçan sınıfları üst sınıflarından kalıt almadığından emin olmak daha iyi bir çözüm olacaktır. Bu böyle yapılabilir:
Kuş temel sınıfı, herhangi bir uçan işlevsellik içermez ve FlightfulBird alt sınıfı, bu işlevselliği ekler. Bu, bazı işlevlerin hem Kuş hem de UçanKuş nesnelerine uygulanmasına izin verir. Ancak, uçamayan olabilecek Kuş nesneleri, FlightfulBird nesnelerini alan işlevlere dönüştürülemez.
Bu serideki bir sonraki bölümde, Bağımlılık Enjeksiyonunun son bölümü daha ayrıntılı olarak tartışmadan önce kalan 2 prensip incelenecektir.
KAYNAK: https://medium.com/aelfblockchain/aelf-tech-talks-dependency-injection-part-2-c2525376e8e8