Bağımlılık Enjeksiyonu Nedir?
Giriş
Üç çocuğum var. Artık yaşlandılar ama küçükken onlarla yemek pişirmek eğlenceliydi. Daha sonra yemekten keyif alacakları bir pasta, kurabiye veya başka şekerlemeler pişirirdik. Ancak bir sorun vardı; pişirirken büyük bir karışıklık yarattık. Her yerde ve her tarafımızda un, şeker ve her türlü şey vardı. Pişirme süreci bize büyük bir temizlik işi bıraktı. Pek çok işe yaradı. Elbette eğlendik ama karmaşa yine de oradaydı.
Karışıklıktan nasıl kaçınılır? Sanırım daha dikkatli olabiliriz ama çocuklar çocuk olacaktır. Çoğu zaman doğum günü pastası pişirirdik. Elbette, pasta pişirmenin tüm zorluğuna ve karmaşasına bir alternatif, mağazaya gidip önceden hazırlanmış bir pasta satın almak olacaktır. Daha da iyi bir çözüm, sipariş üzerine doğum günü pastası teslim edecek ve doğum günü pastanız için tam olarak istediğiniz şeyi kapınıza bırakacak bir fırın bulmak olacaktır.
Şimdi burada dikkate alınması gereken başka bir şey var. Diyelim ki teslimatçı geldi ve çamurda yürüyordu, çizmeleri kirliydi ve şiddetli bir öksürüğü vardı. Onu evine mi alacaksın? Hayır tabii değil. Muhtemelen kapı aralığından ona bir kez bakar ve ona pasta kutusunu verandaya bırakmasını ve çimenlikten defolup gitmesini söylersiniz. Sanırım pasta konusunda da son derece dikkatli olursunuz, ancak tartışma adına pastanın iyi sarıldığını ve teslimatçının yaydığı balgamdan korunduğunu varsayacağız. Başka bir deyişle, teslimatçıyla etkileşiminizin mümkün olduğunca az olmasını istiyorsunuz ama yine de pastayı istiyorsunuz. Çocuğunuz doğum günü pastasının mumlarını üflemezse oldukça üzülecektir. Pastayı karışıklık olmadan ve teslimatçıyla en ince etkileşimlerle istiyorsunuz.
Hatta doğum günü pastasına bağımlılığınız olduğu ve fırının pastayı size teslim ederek bu bağımlılığı enjekte ettiği bile söylenebilir. Hmm.
Veya diyelim ki süpermarkettesiniz ve yiyecek dolu bir alışveriş sepetiniz var. Onları kasa görevlisine götürürsün, o da hepsini arar. “Bu 123,45 dolar olacak” diyor. Ee ne yapıyorsun? Ona cüzdanınızı verip nakit ya da kredi kartı bulmasını mı istiyorsunuz? Tabii ki hayır - cüzdanınızla etkileşimi mümkün olduğunca minimumda tutmak istiyorsunuz - onun etrafta dolaşmasını istemiyorsunuz. Ne olacağını kim bilir. Bunun yerine adama nakit verirsiniz ya da kredi kartınızı çıkarıp ona verirsiniz. Ya da daha iyisi, kartı kendiniz kaydırırsınız, böylece kasa görevlisi kartınıza asla dokunmaz. Yine, bu etkileşimin mümkün olduğunca az olmasını istiyorsunuz. Görevliyle aranızdaki iletişimi minimumda tutmak istediğinizi söyleyebilirsiniz.
Bir tane daha. Bir ev almayı düşündüğünüzü söyleyin. Şehrin her yerini ararsınız ve sonunda gerçekten beğendiğiniz bir yer bulursunuz. Konumu iyi, okul bölgesi iyi, mahalle iyi. Tek bir sorun var; tüm elektrikli cihazlar evin içine kabloyla bağlı. Tüm lambalar, ekmek kızartma makinesi, saç kurutma makinesi, her şey kablolu. Evde priz yok. Her şey doğrudan elektrik sistemine bağlanmıştır, dolayısıyla hiçbir şeyi kolayca değiştiremezsiniz. Elektrikli cihazlar ile elektrik sistemi arasındaki normal arayüzler (elektrik fişleri) mevcut değildir. Ekmek kızartma makinenizi elektrikçi çağırmadan değiştiremezsiniz. Arayüz eksikliğinin işleri çok zorlaştırdığı söylenebilir ve bu nedenle evi satın almamaya karar veriyorsunuz.
Burada bir model (şablon) görüyor musun?
Tamam, bu kadar hikaye yeterli. Sadece söyleyeceğim. Kodunuzda nesneler arasındaki etkileşimin mümkün olduğunca ince ve temiz olması gerekir. Tam olarak ihtiyacınız olanı istemelisiniz, daha fazlasını değil. Bir şeye ihtiyacınız varsa, onu kendiniz yaratmaya çalışmak yerine, onu istemelisiniz, size “teslim edilmesini” sağlamalısınız. Bu şekilde tıpkı pasta, kasiyer ve evde olduğu gibi her şey düzenli, temiz ve esnek kalır. Düzgün, temiz ve esnek bir kodun kulağa oldukça hoş geldiğini düşünüyorum.
Bağımlılık Enjeksiyonunun özü budur. Gerçekten bundan başka bir şey değil. Bağımlılık Enjeksiyonu, on sentlik bir konsept için yirmi beş dolarlık bir terimdir: Bir şeye ihtiyacınız varsa, onu isteyin. Bir şeyleri kendiniz yaratmayın; bunu başkasına ittirin. Herhangi bir maddenin her sınıfı muhtemelen diğer sınıfların yardımına ihtiyaç duyacaktır. Sınıfınızın yardıma ihtiyacı varsa isteyin; "kendisi yapsın" diye uğraşmayın. Unutmayın, her "kendi başına yapsın" diye çalıştığınızda, sabit, katı şekilde kodlanmış bir bağımlılık yaratırsınız. Ve yine küresel (global) değişkenler gibi bunlardan kaçınılmalıdır.
Göreceğimiz gibi aslında bu kadar basit.
Dikkat edilmesi gereken bir nokta -ki bu kitabın ilk bölümünde üzerinde duracağım bir noktadır- henüz "Konteyner" kelimesini kullanmamış olmamızdır. Aslında işte benim bir tweetim:
Bu biraz tuhaf gelebilir ama önemli bir noktaya işaret ediyor: "Bağımlılık Enjeksiyonu Yapmak" ve "Bağımlılık Enjeksiyonu konteyneri kullanmak" gerçekten iki farklı şeydir. Bunlar birbiriyle ilişkilidir (ikincisi birincinin ölçeklendirilmesini kolaylaştırır), ancak aynı şey değildirler. Aslında DI Konteyneri hakkında tekrar konuşmadan önce çok fazla alana değineceğim.Peki Bağımlılık Enjeksiyonu Tam Olarak Nedir?
Şu ana kadar muhtemelen DI'nin tam olarak ne olduğunu merak ediyorsunuz. Harika “.Net'te Dependency Injection” kitabının yazarı Mark Seemann'a göre Dependency Injection, “gevşek bağlı kod geliştirmemize olanak tanıyan bir dizi yazılım tasarım ilkesi ve modelidir.” Bu iyi bir tanım ama biraz antiseptik. Biraz daha derinlemesine inceleyelim.
Eğer bir bağımlılık enjekte edecekseniz, bağımlılığın ne olduğunu bilmeniz gerekir. Bağımlılık, belirli bir sınıfın işini yapmak için ihtiyaç duyduğu herhangi bir şeydir (ör. alanlar, diğer sınıflar vb.). TClassA'yı derlemek için TClassB'nin mevcut olması gerekiyorsa, o zaman TClassA, TClassB'ye bağımlıdır. Veya başka bir deyişle TClassB, TClassA'nın bağımlılığıdır. Bağımlılıklar, siz bir tane oluşturduğunuzda yaratılır. İşte bir örnek:
typeTClassB = classend;TClassA = classprivateFClassB: TClassB;publicconstructor Create;end;constructor TClassA.Create;beginFClassB := TClassB.Create;end;
Yukarıdaki kodda, TClassA'da TClassB'ye sabit kodlanmış bir bağımlılık oluşturduk. Tıpkı daha önce tartıştığımız saç kurutma makinesi veya ekmek kızartma makinesi gibi; sınıfa kablolarla bağlı. TClassA tamamen TClassB'ye bağımlıdır. TClassB, TClassA'ya "bağlanmıştır". Sıkıca birleştirilmiş. Gerçekten alabildiğiniz kadar sıkı bir şekilde birleşmişsiniz. Ve sıkı bağlaşım kötüdür.
Bağlaşım (coupling) Hakkında Birkaç Kelime
Yukarıda gördüğümüz gibi bağlaşım, bir şeyin diğerine bağımlı olması kavramıdır. Sıkı bağlaşım, her şeyin gerçekten birbirine bağlı olduğu ve sıkıca bağlanmış oldukları zamandır. B sınıfının tam tanımı olmadan A sınıfını derleyemezsiniz. B kalıcı olarak A'ya yapışmıştır. Sıkı bağlaşım kötüdür; esnek olmayan kod oluşturur. Başka birine kelepçelenseydiniz hareket etmenin ne kadar zor olacağını bir düşünün. Sabit kodlanmış bağımlılıklar oluşturduğunuzda bir sınıf böyle hisseder.
İstediğiniz şey, sınıflarınız ve modülleriniz arasındaki bağlaşımı mümkün olduğunca "gevşek" tutmaktır. Yani, bağımlılıklarınızın mümkün olduğu kadar zayıf olmasını istiyorsunuz. Müşteri adına ihtiyaç duyan bir sınıfınız varsa yalnızca müşteri adını girin. Müşterinin tamamını aktarmayın. Ve göreceğimiz gibi, sınıfınızın başka bir sınıfa ihtiyacı varsa, o sınıfın bir soyutlamasını (genellikle bir arayüzü) aktarın. Arayüz bir duman bulutu gibidir; orada bir şey vardır ama onu gerçekten kavrayamazsınız.Bu kavram o kadar önemlidir ki onu bir sonraki bölümde daha ayrıntılı olarak tartışacağız.
Bu sabit kodlanmış, sıkı bir şekilde birleştirilmiş bağımlılıkları nasıl yaratırsınız? Bunları diğer sınıfların içinde bir şeyler yaratarak yaratırsınız. Yukarıdaki örneğe bakın. TClassA'nın yapıcısı, TClassB'nin bir örneğini oluşturur ve onu dahili olarak saklar. Bu Create çağrısı bağımlılığı yaratır. TClassA'yı derlemek için TClassB'ye ihtiyacınız var.
Ne yapmalı?
Yapılacak ilk şey TClassB'yi TClassA'nın içinde yaratmamak. Bunun yerine, bağımlılığı yapılandırıcı (constructor) aracılığıyla "enjekte etmek":
typeTClassB = classend;TClassA = classprivateFClassB: TClassB;publicconstructor Create(aClassB: TClassB);end;constructor TClassA.Create(aClassB: TClassB);beginFClassB := aClassB;end;
Bunu yaparak bağlaşımı biraz gevşetmiş olursunuz. Öncelikle artık istediğiniz TClassB örneğini aktarabileceğinizi unutmayın. Eğer mantıklıysa, soyundan bile geçebilirsiniz. Hala TClassB'ye bağlısınız ve TClassA hala TClassB olmadan derlenemiyor, ancak bağlaşımı bir dokunuşla gevşeterek biraz esneklik eklediniz. TClassA ve TClassB hala bağlaşıktır ancak daha gevşek bir şekilde bağlıdırlar.
Yani bu noktada iki tür bağlaşımımız var: birinci sınıfın aslında ikinci sınıfın bir örneğini oluşturduğu yer ve birinci sınıfın ikinci sınıfa ihtiyaç duyduğu, yani enjekte edildiği yer. İkincisi, daha az (daha gevşek) bağlaşım içermesi nedeniyle birinciye tercih edilir. Bir sonraki bölümde eşleşme hiyerarşisine, yani "uyum-connassence" adı verilen bir kavrama bakacağız.
Ve bu, arkadaşlar, DI'nin özüdür. Bağımlılıklarınızı oluşturmak yerine enjekte edin. Bu kadar. Bu Bağımlılık Enjeksiyonu. Tam burada durabilirim ve eğer size öğrettiğim tek şey bu olsaydı, kodunuz bugün olduğundan daha iyi durumda olurdu (çünkü kabul edin, kod tabanınız sabit kodlanmış bağımlılıklarla dolu, değil mi?). Kitabı burada bitirebilirim ve alet çantanızda işleri gerçekten geliştirecek yeni bir araç olur.
Ama elbette, aslında bu kadar basit değil. Bundan daha fazlası var ve projeler büyüdükçe işler karmaşıklaşıyor, ancak ilk örnek olarak bu, DI'nın ne olduğunu ve nasıl çalıştığını açıklama konusunda uzun bir yol kat ediyor. Önümüzdeki bölümlerde bunu daha derinlemesine inceleyeceğiz ancak bu basit tekniğin gücünü anladıysanız ve gördüyseniz, daha iyi, daha temiz, bakımı daha kolay ve daha esnek kod yazma yolundasınız demektir.
Uyulması Gereken Temel İlkeler
Bu kitaba hakim olacak ve Bağımlılık Enjeksiyonu konusuyla ilgili olacak birkaç temel prensip vardır. Bunlar aşağıdaki gibidir:
Uygulamalara(implementations) Değil, Soyutlamalara(abstractions) Karşı Kod
"Dörtlü Çete"den ("Tasarım Desenleri" kitabının yazarları) Erich Gamma'nın bu ifadeyi icat ettiği düşünülmektedir ve bu güçlü ve önemli bir fikirdir. Yeni geliştiricilere yalnız tek bir şey öğretecek olsanız, o da bu aforizma olmalıdır. Soyutlamalar - genellikle arayüzlerdir (interfaces), ancak her zaman değil (aşağıya bakın) - esnektir. Arayüzler (veya soyut sınıflar) birçok yolla uygulanabilir. Arayüzler, uygulama tamamlanmadan önce bile kodlanabilir. Bir uygulamaya kod yazarsanız sıkı sıkıya bağlı ve esnek olmayan bir sistem yaratırsınız. Kendinizi tek bir uygulamaya (implementation'a) kilitlemeyin. Bunun yerine soyutlamalar kullanın ve kodunuzun esnek, yeniden kullanılabilir ve esnek olmasına izin verin.
Asla Yaratılmaması Gereken Şeyleri Yaratmayın
Sınıflarınız, bir sınıfın yalnızca tek bir şey yapması gerektiği fikri olan Tek Sorumluluk İlkesine uymalıdır. Eğer bunu yaparlarsa, o zaman bir şeyler yaratmamaları gerekir çünkü bunu yaptıklarında iki şey yapmış olurlar. Bunun yerine, ihtiyaç duydukları işlevselliği istemeli ve başka bir şeyin bu işlevselliği yaratmasına ve sağlamasına izin vermelidirler.
Yaratılabilirler ve Enjekte Edilebilirler
Peki ne yaratılmalı? Aslında ilgilenmemiz gereken iki farklı nesne türü var: "Yaratılabilirler" ve "Enjekte Edilebilirler."
Yaratılabilirler, devam edilmesi ve yaratması gereken sınıflardır. Bunlar yaygın ve iyi bilinen RTL veya yardımcı program sınıflarıdır. Delphi geliştiricileri için bunlar TStringList ve TList<T> gibi şeylerdir. Genel olarak Delphi çalışma zamanındaki sınıflar Yaratılabilirler olarak değerlendirilmelidir. Bunun gibi sınıflar enjekte edilmemeli, sınıflarınız tarafından oluşturulmalıdır. Çoğunlukla kısa ömürleri vardır ve sıklıkla tek bir yöntemin süresinden daha uzun yaşamazlar. Eğer sınıfın tamamı için gerekliyse yapılandırıcıda oluşturulabilirler. Bir Yaratılabilir'in yapılandırıcısına yalnızca diğer Yaratılabilirler aktarılmalıdır.
Enjekte edilebilirler ise asla doğrudan oluşturmak istemediğimiz sınıflardır. Bunlar asla bir bağımlılığı sabit kodlamak istemediğimiz ve her zaman Bağımlılık Enjeksiyonu yoluyla aktarılması gereken sınıf türleridir. Normalde bir yapılandırıcı içerisine bağımlılıklar olarak istenecektir. Yukarıdaki kurala uygun olarak, enjekte edilebilir öğelere, bir örneğe doğrudan referanslar değil, arayüzler aracılığıyla referans verilmelidir. Enjekte edilebilirler çoğunlukla iş mantığınızın (business logic) bir parçası olarak yazdığınız sınıflar olacaktır. Bunların daima bir soyutlamanın, genellikle bir arayüzün arkasına gizlenmeleri gerekir. Enjekte edilebilirlerin yapıcılarında başka enjekte edilebilirler isteyebileceğini de unutmayın.
Yapıcıları Basit Tutun
Yapıcılar basit tutulmalıdır. Bir sınıfın yapılandırıcısı herhangi bir "iş" yapmamalıdır; yani sıfır olup olmadığını kontrol etmek, Yaratılabilir Öğeler oluşturmak ve bağımlılıkları daha sonra kullanmak üzere depolamak dışında hiçbir şey yapmamalıdır. Herhangi bir kodlama mantığı içermemelidirler. Bir sınıfın yapıcısında sıfır(nil) olup olmadığını kontrol etmeyen bir 'if' cümlesi, o sınıfın iki sınıfa bölünmesi için bir çığlıktır. (if ifadesini içermeyen, sıfır(nil) değerli parametreleri kontrol etmenin yolları vardır. Daha sonraki bir bölümde "Asla sıfırı kabul etme" kavramını ele alacağız). Karmaşık bir yapılandırıcı, sınıfınızın çok fazla şey yaptığını gösteren açık bir işarettir. Yapıcıları kısa, basit ve her türlü mantıktan (logic) uzak tutun.
Uygulama(implementation) Hakkında Hiçbir Şey Varsaymayın
Arayüzler elbette bir uygulama olmadan işe yaramaz. Ancak bir geliştirici olarak siz, bu uygulamanın ne olduğu konusunda hiçbir zaman varsayımda bulunmamalısınız. Yalnızca arayüzün yaptığı sözleşmeye göre kodlama yapmalısınız. Uygulamayı yazmış olabilirsiniz, ancak bu uygulamayı göz önünde bulundurarak arayüze karşı kodlama yapmamalısınız. Başka bir deyişle, sanki arayüzün tamamen yeni ve daha iyi bir uygulaması çok yakındaymış gibi arayüzünüze göre kodlayın. İyi tasarlanmış bir arayüz size ne yapmanız gerektiğini ve nasıl kullanmanız gerektiğini anlatacaktır. Bu arayüzün uygulanması, arayüzü kullanımınız açısından önemsiz olmalıdır.
Bir Arayüzün Soyutlama Olduğunu Düşünmeyin
Arayüzler güzel ve kesinlikle her zaman onları övüyorum. Ancak her arayüzün bir soyutlama olmadığının farkına varmak önemlidir. Örneğin, arayüzünüz sınıfınızın genel kısmının tam bir temsiliyse, gerçekten hiçbir şeyi "soyutlamıyorsunuz", değil mi? (Bu tür arayüzlere C++ başlık dosyalarına benzedikleri için “başlık arayüzleri” adı verilir). Sınıflardan çıkarılan arayüzler yalnızca o sınıfa kolayca sıkı bir şekilde bağlanabilir, bu da arayüzü bir soyutlama olarak işe yaramaz hale getirir. Son olarak, soyutlamalar "sızdıran" olabilir, yani uygulamalarına ilişkin belirli uygulama ayrıntılarını ortaya çıkarabilirler. Sızdıran soyutlamalar da normalde belirli bir uygulamaya bağlıdır. (Bu kavram hakkında daha fazla bilgiyi Mark Seemann'ın [http://bit.ly/2awOhmn]http://bit.ly/2awOhmn] adresindeki mükemmel blog yazısında okuyabilirsiniz.)
Sonuç
Tamam, bu Bağımlılık Enjeksiyonu fikrine temel bir giriş görevi görmelidir. Bağımlılık Enjeksiyonu belirli bir amaca yönelik bir araçtır ve bu amaç gevşek bir şekilde bağlanmış koddur.
Açıkçası bundan daha fazlası var, dolayısıyla bu kitabın geri kalanı. Ancak soyutlamalara karşı kodlama yapmanız ve ihtiyacınız olan işlevselliği istemeniz gerektiği kavramlarını anlarsanız, Dependency Injection'ı anlama ve daha iyi kod yazma yolunda iyi bir yoldasınız demektir.
Bağımlılık Enjeksiyonunun Faydaları
Bütün bunları neden yapmalıyız? Kodumuzu Dependency Injection ilkelerinin gerektirdiği şekilde düzenlemek için neden bu kadar zahmete girelim ki? Çünkü faydaları var. Bağımlılık Enjeksiyonunun faydalarından biraz bahsedelim çünkü bunlar çok sayıda ve ilgi çekicidir.
Sürdürülebilirlik – Bağımlılık Enjeksiyonunun muhtemelen temel faydası sürdürülebilirliktir. Sınıflarınız gevşek bir şekilde bağlıysa ve tek sorumluluk ilkesini (DI kullanmanın doğal sonucu) izliyorsa kodunuzun bakımı daha kolay olacaktır. Basit, bağımsız sınıfların düzeltilmesi, karmaşık, sıkı bir şekilde birleştirilmiş sınıflardan daha kolaydır. Bakımı yapılabilen kodun toplam sahip olma maliyeti daha düşüktür. Bakım maliyetleri genellikle ilk etapta kodu oluşturma maliyetini aşar; dolayısıyla kodunuzun sürdürülebilirliğini artıran her şey iyi bir şeydir. Hepimiz zamandan ve paradan tasarruf etmek istiyoruz, değil mi?
Test Edilebilirlik – Sürdürülebilirlik ile aynı doğrultuda test edilebilirlik de vardır. Test edilmesi kolay olan kod daha sık test edilir. Daha fazla test, daha yüksek kalite anlamına gelir. Yalnızca tek bir şey yapan (yine DI kullanmanın doğal sonucu olan) gevşek bağlı sınıfların birim testini yapmak çok kolaydır. Dependency Injection'ı kullanarak test çiftleri (genellikle "sahte" olarak adlandırılır) oluşturmayı çok daha kolay hale getirirsiniz. Bağımlılıklar sınıflara aktarılırsa, test çift uygulamasını geçmek oldukça basittir. Bağımlılıklar sabit kodlanmışsa bu bağımlılıklar için test çiftleri oluşturmak imkansızdır. Gerçekte test edilen test edilebilir kod, kalite kodudur. Veya en azından test edilmemiş koddan daha kalitelidir. Birim testlerinin zaman kaybı olduğu iddiasını kabul etmekte zorlanıyorum; onlar benim için her zaman zaman ayırmaya değer. (Elbette bunun tartışmalı olmasını garip bulan tek kişi ben değilim?)
Okunabilirlik – DI kullanan kod daha basittir. Tek Sorumluluk İlkesini takip eder ve böylece daha küçük, daha kompakt ve noktasal sınıflarla sonuçlanır. Yapıcılar o kadar karmaşık ve mantıkla dolu değiller. Sınıflar daha net bir şekilde tanımlanmış olup neye ihtiyaçları olduğu açıkça bildirilmektedir. Tüm bunlardan dolayı DI tabanlı kod daha okunabilirdir. Ve daha okunabilir olan kod daha kolay korunur-sürdürülür.
Esneklik – Gevşek bağlı kod – yine DI kullanmanın sonucu – daha esnektir ve farklı şekillerde kullanılabilir. Tek bir şey yapan küçük sınıflar daha kolay bir şekilde yeniden bir araya getirilebilir ve farklı durumlarda yeniden kullanılabilir. Küçük sınıflar Legolar(tm) gibidir; daha hacimli ve daha az esnek olan Duplo(tm) bloklarının aksine, çok sayıda şey oluşturmak için kolaylıkla bir araya getirilebilirler. Kodu yeniden kullanabilmek zamandan ve paradan tasarruf sağlar. Tüm yazılımların değişebilmesi ve yeni gereksinimlere uyum sağlayabilmesi gerekiyor. Bağımlılık Enjeksiyonu kullanan gevşek bağlı kod esnektir ve bu değişikliklere uyum sağlayabilir.
Genişletilebilirlik – Bağımlılık Enjeksiyonunu kullanan kod, daha genişletilebilir bir sınıf yapısıyla sonuçlanır. Kod, uygulamalar (implementation) yerine soyutlamalara güvenerek belirli bir uygulamayı kolayca değiştirebilir. Soyutlamalara karşı kod yazdığınızda, yaptığınız işin çok daha iyi bir uygulamasının çok yakında olduğu fikriyle kodlayabilirsiniz. Küçük, esnek sınıflar miras veya bileşim yoluyla kolayca genişletilebilir. Bir uygulamanın kod tabanı hiçbir zaman statik kalmaz ve kod tabanınız büyüdükçe ve yeni gereksinimler ortaya çıktıkça büyük olasılıkla yeni özellikler eklemeniz gerekecektir. Genişletilebilir kod bu zorluğun üstesinden gelebilir.
Ekip Geliştirme – Bir ekipteyseniz ve bu ekibin bir proje üzerinde birlikte çalışması gerekiyorsa (bu ne zaman doğru değildir?), Dependency Injection ekip gelişimini kolaylaştıracaktır. (Yalnız çalışıyor olsanız bile, ileride işinizin birilerine devredilmesi ihtimali çok yüksektir). Dependency Injection sizi uygulamalara(impementation'lara) değil soyutlamalara göre kodlamaya çağırır. Birlikte çalışan ve her biri diğerinin çalışmasına ihtiyaç duyan iki ekibiniz varsa, uygulamaları yapmadan önce soyutlamaları tanımlayabilirsiniz ve ardından her takım, uygulamalar yazılmadan önce bile soyutlamaları kullanarak kendi kodunu yazabilir. Ayrıca kod gevşek bir şekilde bağlı olduğundan bu uygulamalar birbirine bağlı olmayacak ve bu nedenle ekipler arasında kolaylıkla bölünebilir.
İşte işte buradasın. Bağımlılık Enjeksiyonu, ekip üyeleri arasında kolayca dağıtılabilen, bakımı yapılabilir (sürdürülebilir), test edilebilir, okunabilir, esnek ve genişletilebilir kodla sonuçlanır. Herhangi bir geliştiricinin tüm bunları istemeyeceğini hayal etmek zor görünüyor.
Bağlaşıma Daha Yakından Bir Bakış: Uyum (connassence)
Giriş
Dikkat ettiyseniz bağlılığı sevmediğimi fark etmişsinizdir. Sıkıca bağlanmış kod beni rahatsız ediyor. Blog yazılarımda, kitaplarımda, her yerde bundan bahsediyorum. Sıkı bağlantı kötüdür. Gevşek bağlantı istediğimizi ve sıkı bağlantıdan kaçınmamız gerektiğini biliyoruz. Bu bir bakıma verilmiş bir şey ama tüm bunların tam olarak ne anlama geldiğine dair çok az değerli tartışma oldu. Bunu tarif etmenin oldukça zor olduğu biliniyor. “Gördüğümde anlarım” türünden bir şeydi bu. Bağımlılık Enjeksiyonu tamamen bağlamayı azaltmakla ilgili olduğundan, bağlamanın ne olduğu hakkında biraz daha fazla bilgi içeren bu bölümü eklemenin iyi bir fikir olacağını düşündüm.
Bağlılık, iki modül arasındaki ilişkilerin ve bağlantıların bir ölçüsüdür. Kodun bir şekilde bağlanması gerekiyor, yoksa hiçbir şey yapamaz. Peki bu ölçümler nasıl yapılıyor? Tam olarak ne ölçülüyor? Bunlar zor sorular. Sıkı bağlantının kötü olduğu göz önüne alındığında, bunu mümkün olduğunca sınırlamak istiyoruz. Peki bunu tam olarak nasıl yapacağız? Bağlantı tam olarak ne halt ediyor?
Uyum (Connascence)
Neyse ki bağlılığı ölçmenin bir yolu var. Buna "uyum" (connascence) denir. ("Cuh-NAY-since" diye telaffuz edildiğini duydum) Yazılım geliştirme alanında, sistemin genel doğruluğunu korumak için iki modülden birini değiştirmek diğerinde de değişiklik gerektiriyorsa iki modülün "connascent" (uyumlu) olduğu söylenir. . Birleşmeyi düşündüğümüzde aklımıza gelen hemen hemen budur. Terim ilk kez Meilir Page-Jones tarafından eşleşmenin tam olarak ne olduğunu ölçebilmek ve nitelendirebilmek amacıyla kullanıldı. Bunu ilk olarak “Nesneye Yönelik Tasarım Hakkında Her Programcının Bilmesi Gerekenler” adlı kitabında tartıştı. Kitap 1995 yılında yayınlandı, dolayısıyla bu fikir yeni bir şey değil.
Ancak çoğu zaman olduğu gibi yirmi yıllık bu fikir ancak şimdilerde gündeme geliyor. Connascence, kodunuzdaki bağımlılığı ölçmenin bir yoludur.
Connascence iki boyutta ele alınmaktadır. Birincisi, dokuz farklı uyum düzeyi vardır. İkincisi, bu seviyelerin hepsinin belirli nitelikleri vardır. Önce bu niteliklerden bahsedeceğim, sonra da uyum düzeyleri hakkında konuşmaya geçeceğiz. Uyum düzeyleri, bir eşleşme taksonomisi oluşturmamıza ve bunun hakkında konuşmak için bize bir kelime dağarcığı vermemize olanak tanır. Bu gerçekten faydalıdır, çünkü daha önceki eşleşme tartışmaları genellikle "sıkı mı gevşek mi" konusunda çok şekilsiz bir tartışmaya dönüşüyordu; bu da olaylara pek bilimsel bir bakış açısı getirmiyordu.
Connascence'nin Nitelikleri
Connascence'ın üç niteliği vardır: Güç, Derece ve Yerellik.
Connascence'nin Gücü
Eğer onu düzeltmek daha derinlemesine ve daha zor değişiklikler gerektiriyorsa, bir Connascence (uyumluluk-uzlaşma) düzeyinin diğerinden daha güçlü olduğu söylenir. Örneğin, iki varlık arasındaki bağlaşımın azaltılması kolay ve basit bir değişiklik gerektiriyorsa, bu durumda connascence'nin, karmaşık bir değişiklik gerektiren connascence'den daha az güçlü olduğu söylenir. Bir düzeyde connascence'nin yeniden düzenlenmesi (refactoring) zorsa, bunun güçlü düzeyde bir uyum olduğu söylenir. Aşağıda açıklanan Uyum Düzeyleri artan güce göre listelenmiştir. Bu sıralama, yeniden düzenleme (refactoring) işleminize nasıl öncelik vereceğiniz konusunda size bir fikir verir. Yani, en güçlü bağlantıları daha zayıf bağlantı seviyelerine kadar yeniden düzenlemeye (refactor etmeye) çalışmalısınız.
Connascence Derecesi
Connascence derecesi, uyumluluğun meydana geldiği seviyenin bir ölçüsüdür. Connassence küçük derecede veya büyük derecede meydana gelebilir. Örneğin, iki modül tek bir referans yerine birden fazla referansla bağlanabilir. Çoklu referansların bağlantısının yüksek derecede yakınlığa sahip olduğu söylenir. Belirli bir yöntem, onu birçok dış sınıfa bağlayan birçok parametreye sahip olabilir. Böyle bir metodun-yöntemin yüksek derecede connascence'si (uyumu) vardır.
Connascence'nin Bölgesi
Bazen bağlaşım(coupling) birbirine yakın gerçekleşir; aynı ünitede (Unit) birbirine bağlı iki sınıfınız vardır. Connascence tek bir metotla ortaya çıkabilir. Ancak bazen bu bağlaşım birbirinden çok uzakta olan iki birimde (Unit'te) meydana gelir. Bunu hepimiz gördük; uygulamanızın “sol alt” kısmında bir değişiklik yapıyorsunuz ve bu, programın “sağ üst” kısmında çok uzakta bir etki yaratıyor. Birbirine yakın olan connascence, uzak olandan daha iyidir-hayırlıdır.
Connascence'nin Düzeyleri
Bağlaşımın bazısı gereklidir. Bir uygulamada o olmadan hiçbir şey olamaz. Bununla birlikte, bağlaşımın gücünü mümkün olduğu kadar zayıf, derecesini mümkün olduğu kadar küçük ve lokalitesini mümkün olduğu kadar yakın tutmak istiyoruz. Eğer bağlaşımı ölçebilseydik, bunu yaptığımızı bilebilirdik, değil mi? Page-Jones, her biri bir öncekinden daha güçlü, daha yüksek derecede ve/veya daha uzak bir bölgede olan dokuz connascence düzeyini tanımladı. Bu bağlantı seviyelerini fark ettiğimizde, bunları daha düşük bir seviyeye indirecek şeyler yapabiliriz. Bir göz atalım ve her şeyin nasıl çalıştığını görelim.
Statik-Durağan Connascence'ler
Connascense'ın ilk beş seviyesi statiktir denilir, çünkü kodunuzu görsel olarak inceleyerek bulunabilirler.
İsim Connascence'si
İsim connascense'i, iki şeyin bir şeyin adı üzerinde anlaşmaya varması gerektiğinde ortaya çıkar. Bu, connascence'nin en zayıf biçimidir ve kendimizi sınırlamaya çalışmamız gereken biçimdir. Bu neredeyse apaçıktır ve kaçınılamaz. Bir prosedür bildirirseniz:
procedure TMyClass.DoSomething;
onu DoSomething adını kullanarak çağırmanız gerekir. Bir şeyin adını değiştirmek başka yerde değişiklik yapılmasını gerektirir. Prosedürün adını değiştirmek istiyorsanız, onu çağırdığınız her yerde değiştirmeniz gerekir. Bu açık görünüyor ve elbette bu connascence düzeyi kaçınılmazdır. Aslında bu arzu edilen bir şeydir. Bu, sahip olabileceğimiz en düşük düzeydeki bağlaşmadır ve bu yüzden onu aramalı ve en çok kullanmalıyız. Eğer birleşmemizi İsmin Uzlaşması ile sınırlandırabilirsek, çok iyi durumda oluruz.
Type (tip) Connascence'si
Tip connascence'si, iki varlığın bir şeyin türü üzerinde anlaşmaya varması gerektiğinde ortaya çıkar. En bariz örnek bir metodun parametreleridir. Bir işlevi(fonksiyonu) aşağıdaki gibi bildirirseniz:
function TSomeClass.ProcessWidget(aWidget: TWidget; aAction: TWidgetActionType): Boolean;
bu durumda herhangi bir çağıran kod, ProcessWidget işlevinin parametreleri olarak bir TWidget ve bir TWidgetActionType iletmeli ve sonuç türü olarak bir Boolean kabul etmelidir. Delphi güçlü bir şekilde typed olarak yazılmıştır, dolayısıyla bu tür bir connascence neredeyse her zaman derleyici tarafından yakalanır.(MÖ'nün notu : typed'ı tanımlamak için untyped'ı tanımlamak gerekir. Pointer kullanıyorsanız programınız untyped'dır ve çalışma zamanında hata vermesi çok muhtemeldir. Örneğin eski TList sınıfına bir Type'ı değil yalnızca bir pointeri eklersiniz. Tamsayı, reel sayı, çeşitli bilgiler içeren bir record ve bir sınıf olabilir bunlar ve siz bunu ancak runtime'de kontrol edebilirsiniz. Oysa yeni generic'ler-soysallar kullanılarak bu tür hatalardan da kaçınılabilir duruma geldi Delphi.). Type connascence'si, İsim Connscence'si kadar zayıf değildir, ancak yine de zayıf ve kabul edilebilir bir connscence düzeyi olarak kabul edilir. Gerçekten de, o olmadan gerçekten idare edemezsiniz, değil mi? Herhangi bir şeyi yapmak için bir sınıfın yöntemini çağırabilmeniz gerekir ve türlerinizin eşleştiğinden emin olmak çok da külfetli değildir. Aslında Delphi'de kodunuzu derlemek için bile Connascence of Type aracılığıyla kodu bağlaştırmanız gerekir.
Anlam Connascence'si
Anlam Connascence'si, bileşenlerin belirli değerlerin anlamı üzerinde anlaşmaya varması gerektiğinde ortaya çıkar. Anlam Connascence'siçoğunlukla “sihirli sayıları”, yani anlamı olan ve birden fazla yerde kullanılan belirli bir değeri kullandığımızda ortaya çıkar. Aşağıdaki kodu ele alalım, inceleyelim:
function GetWidgetType(aWidget: TWidget): integer;beginif aWidget.Status = 'Working' thenbeginResult := 1;endelsebeginif aWidget.Status = 'Broken' thenbeginResult := 2;endelsebeginif aWidget.State = 'Missing' thenbeginResult := 3;endelsebeginResult := 0;end;end;end;end;
Yukarıdaki kodu kullanmak istiyorsanız GetWidgetType işlevine ait sonuç kodunun anlamını bilmeniz gerekir. Sonuç türlerinden birini değiştirirseniz veya yenisini eklerseniz, bu işlevi kullanan kodu, kullanıldığı her yerde değiştirmeniz gerekir. Bu değişikliği yapmak için her sonuç kodunun anlamını bilmeniz gerekir. Buradaki bariz çözüm, sonuç kodu için sabit adlar veya daha iyisi, sonuç kodlarını tanımlayan numaralandırılmış bir tür kullanacak şekilde kodu yeniden düzenlemektir. Bu, sizin connascence'nizi, arzu edilen bir sonuç olarak, Anlam Connascence'sinden İsim Connascence'sine doğru azaltır. Unutmayın, daha yüksek bir düzeyden daha düşük bir connascence düzeyine doğru yeniden düzenleme (refactoring) yaptığınızda, bağlaşmayı azaltmış ve dolayısıyla kodunuzu geliştirmiş olursunuz. Anlam Uzlaşmasının bir başka örneği de sıfırın bir sinyal olarak kullanılmasıdır. Geliştiriciler genellikle nil kelimesini "değer yok" veya "Üzgünüm, bunu yapamadım/bulamadım/tamamlayamadım" anlamında kullanırlar ve kodunuzun bunu halletmesi gerekir. Daha sonraki bölümlerde göreceğimiz gibi, Anlam Connascence'si yoluyla bağlaşma yarattığı için nil'in bu kullanımından kaçınılmalıdır.
Konum Connascence'si
Konum Connascence'si, iki farklı yerdeki kodun nesnelerin konumu üzerinde anlaşmaya varması gerektiğinde ortaya çıkar. Bu en yaygın olarak, bir metodun parametre listesindeki parametrelerin sırasının bu sırayı korumak için gerekli olduğu parametre listelerinde meydana gelir. Mevcut bir parametre listesinin ortasına bir parametre eklerseniz, bu yöntemin tüm kullanımlarında yeni parametrenin doğru konuma eklenmesi gerekir.
Bazı diller, parametrelerinizi herhangi bir sıraya dahil edilebilecek şekilde adlandırmanıza izin verir, ancak Delphi buna izin vermez. Bu nedenle Delphi kodu yazarken Konum Connascence'si kullanarak kodu bağlaştırmak gerekir.
Artık, herhangi bir rutindeki parametre sayısını sınırlayarak Konum Connascence derecesi azaltılabilir. Konum Connascence'sini sınırlamak için parametre listesini tek bir türe indirgeyebilir, böylece Konum Connascence'sinden Tür Connascence'sine geçiş yapmış olursunuz. Tip Connascence'si daha zayıf bir bağlaşımdır ve bu, yapmaya çalışmanız gereken bir şeydir.
İşte bir örnek. Bu rutini ele alalım:
procedure TUserManager.AddUser(aFirstName: string;aLastName: string; aAge: integer; aBirthdate: TDateTime; aAddress: TAddress; aPrivileges: TPrivileges);
AddUser'ı kullanmak için tüm parametreleri tam olarak doğru konuma aldığınızdan emin olmalısınız. Ortaya bir parametre eklerseniz AddUser kullanımının bu yeni değeri tam olarak doğru yere koyduğundan emin olmanız gerekir.
Bu Konum Connascence örneğini, bunun yerine Tip Connascence'si kullanacak şekilde yeniden düzenleyerek (refactoring) azaltabiliriz. Örneğin:
typeTUserRecord = recordFirstName: string;LastName: string;Age: integer;Birthday: TDateTime;Address: TAddress;Privileges: TPrivileges;end;procedure TUserManager.AddUser(aUser: TUserRecord);
Artık bir tür oluşturarak ve AddUser prosedürünün uzun bir parametre listesinin konumu yerine parametrenin türüne bağlı olmasını sağlayarak bağlaşımı azalttık. Bağlaşımın gücünü azaltarak kodu geliştirdik. Bu tekniğe aynı zamanda “Parametre Nesnesi” de denir. (Bkz.Parametre Nesnesi)
Algoritmanın Connascence'si
Algoritma Connascence'si, iki modülün birlikte çalışabilmesi için belirli bir algoritma üzerinde anlaşması gerektiğinde ortaya çıkar.
Delphi istemcisi tarafından kullanılacak C# tabanlı API'ye sahip bir sisteminiz olduğunu hayal edin. Bu iki modül arasında gönderilen bilgiler hassastır ve şifrelenmesi gerekir. Böylece, bu iki modül Algoritma Connascence'si ile birleştirilir çünkü her ikisinin de kullanılacak şifreleme algoritması üzerinde anlaşması gerekir. Gönderici şifreleme algoritmasını değiştirirse, alıcının da aynı algoritmaya geçmesi gerekir.
Algoritmanın Connascence'sini (Uzlaşısını) azaltmak zordur, çünkü çoğu zaman yüksek derecede bir yerelliğe sahiptir (yani, bağlaşım çok uzakta gerçekleşir). Bir çözüm, algoritmanın bulunduğu tek yer haline gelen tek bir modül oluşturmak ve ardından her iki tüketen modülün de bu tek modülü kullanmasını sağlamak olabilir.
Dinamik Connascence'ler
Sonraki dört Connascence düzeyinin "dinamik" olduğu söylenir çünkü bunlar yalnızca kodunuzu çalıştırarak keşfedilebilir. Bu seviyeler statik olanlardan daha güçlüdür çünkü kendilerini yalnızca çalışma zamanında ortaya çıkarırlar, bu da onları tespit etmeyi ve çoğu zaman düzeltmeyi zorlaştırır.
Yürütme(execution) Connascence'si
Yürütme Uyumu, sistemin doğru olması için kodun yürütme sırasının gerekli olduğu durumlarda ortaya çıkar. Genellikle "Geçici Bağlaşım" olarak anılır.
Yukarıdaki kodu kullanan bir örnek:
UserRecord.FirstName := 'Alicia';UserRecord.LastName := 'Florrick';UserRecord.Age = 47;UserManager.AddUser(UserRecord);UserRecord.Birthday := EncodeDate(1968, 12, 3);
Bu kod, kullanıcı eklendikten sonra Doğum Günü değerini ekler. Bu açıkça işe yaramayacak. Açıkçası, kodu incelediğinizde bu fark edilebilir, ancak fark edilmesi daha zor olan daha karmaşık bir senaryo hayal edebilirsiniz. Bu koda göz atalım:
SprocketProcesser.AddSprocket(SomeSprocket);SprocketProcessor.ValidateSprocket;
Bu iki ifadenin sırası önemli mi? Dişlinin eklenmesi ve ardından doğrulanması mı gerekiyor, yoksa eklenmeden önce doğrulanması mı gerekiyor? Bunu söylemek zor ve sistemde iyi bilgi sahibi olmayan biri bunları yanlış sıraya koyma hatasına düşebilir. Bu, Yürütme Connascense'sidir.
İşte başka bir örnek. Mesajları tutan bir kuyruk hayal edin. İlk mesajda “Listeyi başlat” yazıyor. Daha sonra sonraki iki mesajın içinde listeye öğe eklemek için liste öğeleri bulunur. Ardından son olarak kuyrukta “Listeyi sonlandır” yazan bir mesaj bulunur. Öğeleri kuyruktan çeken tek bir çalışan iş parçacığınız varsa, bu harika çalışır. Tüm eşyalar sırayla çıkarılacaktır. Ancak, öğeleri kuyruktan çeken birden fazla iş parçacığınız varsa ve iş parçacıklarından biri diğerlerinden biraz daha hızlı çalıştıysa ve sonra, son "İşte başka bir öğe" mesajı işlenmeden önce "Listeyi sonlandır" mesajını kuyruktan kaldırdı mı? Bu kötü olurdu. Bu aynı zamanda Connascence of Execution'ın neden olduğu bir hata olacaktır.
Zamanlama Connascence'si
Zamanlama Connascence'si, yürütme zamanlamasının uygulamanın ürettiği çıktıda bir fark yaratması durumunda meydana gelir. Bunun en belirgin örneği, iki iş parçacığının aynı kaynağı takip ettiği ve iş parçacıklarından yalnızca birinin yarışı kazanabildiği iş parçacıklı bir yarış durumudur. Zamanlamanın Connascence'ını bulmak ve teşhis etmek oldukça zordur ve kendisini tahmin edilemeyecek şekillerde ortaya çıkarabilir.
Değer Connascence'si
Değer Connascence'si, çeşitli değerlerin modüller arasında uygun şekilde koordine edilmesi gerektiğinde ortaya çıkar. Örneğin, şuna benzeyen bir birim testiniz olduğunu hayal edin:
[Test]procedure TestCheckoutValue;varPriceScanner: IPriceScanner;beginPriceScanner := TPriceScanner.Create;PriceScanner.Scan('Frosted Sugar Bombs');Assert.Equals(50, PriceScanner.CurrentBalance);end;
Bu yüzden testi yazdık. Şimdi, Test Odaklı Geliştirme ruhuna uygun olarak, testin mümkün olduğunca kolay ve basit bir şekilde geçmesini sağlayacağım.
procedure TPriceScanner.Scan(aItem: string);
begin
CurrentBalance := 50;
end;
Artık TPriceScanner ile testimiz arasında sıkı bir bağlaşım var. Açıkçası Connascense of Name'e sahibiz çünkü her iki sınıf da CurrentBalance ismine dayanıyor. Ancak bu nispeten düşük bir seviyedir ve tamamen kabul edilebilirdir. Tip Connascence'ımız var, çünkü her ikisinin de TPriceScanner türü üzerinde anlaşması gerekiyor, ancak yine de bu iyi huylu. Anlam Uyumuna sahibiz, çünkü her iki rutinin de 50 sayısına sabit kodlanmış bir bağımlılığı var. Bunun yeniden düzenlenmesi gerekiyor. Ancak asıl sorun, her iki sınıfın da Buzlu Şeker Bombalarının fiyatını, yani “Değerini” bilmesi nedeniyle ortaya çıkan Değer Connascence'sidir. Fiyat değişirse bizim çok basit testimiz bile bozulur.
Çözüm, daha düşük bir Connascence düzeyine yeniden düzenleme (refactoring) yapmaktır. Yapabileceğiniz ilk şey, Buzlu Şeker Bombalarının fiyatı (değer) bilgisinin yalnızca tek bir yerde muhafaza edilmesini sağlayacak şekilde yeniden düzenleme yapmaktır:
procedure TPriceScanner.Scan(aItem: string; aPrice: integer);begin CurrentBalance := aPrice; end;
ve şimdi testimiz aşağıdaki gibi okunabilir:
[Test]procedure TestCheckoutValue; var PriceScanner: IPriceScanner; begin PriceScanner := TPriceScanner.Create; PriceScanner.Scan('Frosted Sugar Bombs', 50); Assert.Equals(50, PriceScanner.CurrentBalance); end;
Ve artık iki modül arasında Değer Connascence'miz yok ve testimiz hâlâ başarılı. Harika.
Kimlik Connascence'si
Kimlik Uyumu, iki bileşenin aynı nesneye gönderme yapması gerektiğinde ortaya çıkar. İki modül aynı şeye atıfta bulunuyorsa ve ardından biri bu referansı değiştirirse, diğer nesnenin de aynı referansa değişmesi gerekir. Genellikle incelikli ve tespit edilmesi zor bir connascence şeklidir. Sonuç olarak bu, connascence'nin en karmaşık biçimidir.
Aşağıdaki kodu ele alalım:
program Identity;{$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; type TReportInfo = class private FReportStuff: string; procedure SetReportStuff(const Value: string); public property ReportStuff: string read FReportStuff write SetReportStuff; end; procedure TReportInfo.SetReportStuff(const Value: string); begin FReportStuff := Value; end;type TInventoryReport = classprivate FReportInfo: TReportInfo; public constructor Create(aReportInfo: TReportInfo); property ReportInfo: TReportInfo read FReportInfo write FReportInfo; end;TSalesReport = class private FReportInfo: TReportInfo; public constructor Create(aReportInfo: TReportInfo); property ReportInfo: TReportInfo read FReportInfo write FReportInfo; end;constructor TInventoryReport.Create(aReportInfo: TReportInfo); begin FReportInfo := aReportInfo; end; constructor TSalesReport.Create(aReportInfo: TReportInfo); begin FReportInfo := aReportInfo; end; var ReportInfo: TReportInfo; NewReportInfo: TReportInfo; InventoryReport: TInventoryReport; SalesReport: TSalesReport; begin try ReportInfo := TReportInfo.Create; InventoryReport := TInventoryReport.Create(ReportInfo); SalesReport := TSalesReport.Create(ReportInfo); try // Do Stuff with reports NewReportInfo := TReportInfo.Create; try InventoryReport.ReportInfo := NewReportInfo; // Do stuff with report // But the reports now point to different ReportInfos. // This is Connascence of Identity finally NewReportInfo.Free; end; finally ReportInfo.Free; InventoryReport.Free; SalesReport.Free; end; excepton E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.
Burada iki raporumuz var: bir envanter raporu ve bir satış raporu. Etki alanı, iki raporun her zaman aynı TReportInfo örneğine başvurmasını gerektirir. Ancak yukarıda da görebileceğiniz gibi raporlama sürecinin ortasında Envanter Raporu yeni bir ReportInfo örneğine kavuşuyor. Bu sorun değil, ancak Satış Raporunun da bu yeni TReportInfo örneğine atıfta bulunması gerekiyor. Başka bir deyişle, bir rapordaki referansı değiştirirseniz diğer raporun da aynı referansla değişmesi gerekir. Buna Kimlik Connascence'si denir, çünkü sistemin doğru çalışmaya devam etmesi için iki sınıfın da referanslarının kimliğini değiştirmesi gerekir.
Connascence Hakkında Ne Yapmalı?
Artık dokuz Connascence Düzeyini tanımladığımıza göre, kodumuzdaki bağlaşım konusunda ne yapmalıyız?
Bir miktar bağlaşımın gerçekleşmesi gerekirken, connascence'nizi mümkün olan en düşük seviyede tutmaya çalışmalısınız. Yani kodunuzdaki Connascence Derecesini azaltmalısınız. Çok temiz bir uygulama genellikle İsim ve Tür Connascence'sine sahip olacak ve Anlam ve Konum Connascence'sini mümkün olduğu kadar sınırlamaya çalışacaktır. Diğer tüm Connascence türleri gerçekten yeniden düzenlenmelidir.
Ayrıca kodunuzdaki Connascence Yerelliğini de artırmalısınız. Kodunuzdaki tüm tanımlayıcıların kapsamını azaltmak için çalışmalısınız. Bir türün kapsamını mümkün olduğu kadar düşük bir kapsam ile sınırlandırmalısınız. Birbirine ait olan şeyler bir arada tutulmalı, ait olmadıkları yerlere gösterilmemelidir. DRY ilkesi – “Don't Repeat Yourself-Kendini Tekrarlama” – artan yerelliğin bir örneğidir. Tek Sorumluluk İlkesi (SRP-Single Responsibility Principle) de öyle.
Son olarak istikrarı tercih etmelisiniz. Connascence aslında sadece değişim ihtiyacının ölçüsüdür ve bir şeyi ne kadar az değiştirmeniz gerekiyorsa, sıkı bağlaşımın bir sonucu olarak hatalar o kadar az sıklıkla ortaya çıkar. Kararlı olan şeylerin bağlaşım hatalarına neden olma olasılığı çok daha düşük olacaktır
Sonuç
Hepimiz sıkı bağlaşımın kötü olduğu konusunda hemfikiriz (en azından öyle umuyoruz!). Ancak bağlaşım kavramı genellikle oldukça kötü tanımlanmıştır. Umarız bu kritik (bir yerde bir şey değişirse, başka bir yerde başka bir şeyin değişmesi gerektiği fikri) connascence'nin ne olduğu hakkında biraz daha spesifik konuşmanıza olanak tanır. Ayrıca, Connascence Düzeylerinin, yüksek düzeyde bağlaşıma sahip kodu bulmanıza ve bağlaşımınızı azaltmak için bu alanları yeniden düzenlemenize olanak sağlayacağını umuyorum. Kodunuzdaki genel Connascence düzeyini azaltmaya çalışırsanız, daha temiz ve bakımı daha kolay bir kod tabanına sahip olursunuz.