SOLID 2 – Açık/Kapalı Prensibi

Image result for solid principles

Merhabalar arkadaşlar, yaz yazılarımıza devam ediyoruz. Bu yazımda bir süre önce başladığım SOLID prensiplerine ilişkin yazı serisinin ikincisini sizler ile paylaşacağım (yazıda sonraki hafta demişim ama bu birkaç ayı buldu, o sebeple kusura bakmayın :)). 

İlk yazıya aşağıdaki adresten ulaşabilirsiniz. Aynı zamanda SOLID prensipler genel anlamda neye hizmet ediyor ve genel motivasyon için de ilk yazıya başvurabilirsiniz:

  1. SOLID 1 – Tek Sorumluluk Prensibi
  2. SOLID 2 – Açık/Kapalı Prensibi
  3. SOLID 3 – “Liskov Substitution” Prensibi
  4. SOLID 4 – Arayüz Ayrıştırma Prensibi

Bu yazımızın konusu Açık/Kapalı Prensibi (“Open-Closed Principle”- OCP). Öncelikle sizlere bu prensibe ilişkin kısaca bilgi vermeye çalışacağım, daha sonra da örnek bir kod parçası üzerinden bunu nasıl uygulayabileceğimize birlikte bakacağız.

Açık/Kapalı Prensibi (Open/Closed Principle)

Prensibinin amacını tek cümle ile aşağıdaki gibi özetleyebiliriz:

 “Yazılım birimleri (ki bunlar sınıflar, modüller veya fonksiyonlar olabilir) genişlemeye açık ama modifikasyona kapalı olmalıdır.” (Bertrand Meyer). 

Bu prensip, SOLID prensiplerin arasında en önemli olanlarından birisidir (Hatta Robert C. Martin tarafından nesne yönelimli tasarımın en önemli prensibi olduğu ifade edilmiştir) ve Nesne Yönelimli Tasarım yaklaşımının da tam kalbinde bulunmaktadır. 

Basit bir şekilde ifade etmek gerekirse, her gereksinim değiştiğinde, yazmış olduğumuz kabiliyete ilişkin kodları değiştirmek zorunda kalmamalıyız ya da yazılımlarımızı bunu göz önüne alarak geliştirmeliyiz.

Peki yapmazsak ne olur? Bir uygulama tasarladınız ve bu uygulamaya ilişkin değişiklikler gerekmekte, eğer bu değişiklikler, ilgili modül dahil birçok alt modülde de değişikliğe neden olup istenmeyen sonuçlara sebep oluyor ise, bu bize aslında çok da doğru bir tasarım yapılmadığının ipucunu verebilir. Sonuç olarak bu şekilde programlar, idamesi zor, kırılgan ve karmaşık bir hale geliyorlar. Ayrıca bu tarz kabiliyetin kendisine ilişkin yapılan değişiklikler, test eforlarını arttırıp, yazılım modüllerinin tekrar oluşturulması ve dahi konuşlandırma (deployment) maliyetlerini artırır.

İşte açık/kapalı prensibi tam olarak bu tarz sonradan ortaya çıkabilecek sorunları önlemek için ortaya konulmuştur. Bu prensibin doğru bir şekilde uygulanması ile birlikte, olası değişikliklerde, mevcut yazılımınızda gerçekleştirilecek olan değişikliklerin minimum seviyede tutulmasını sağlıyor olacaksınız. 

Peki bu prensibe riayet eden modülleri nasıl ifade edebiliriz:

  • Genişlemelere açıktırlar:
    • Modülü sergilediği kabiliyet, gereksinim değişiklikleri veya yeni gereksinimler ışığında, genişleyebilir. Burada, yeni kabiliyetler için modülün genişletilmesi zaten onun kodları değiştirilmesi anlamına gelmez mi diye düşünebilirsiniz ama mevcut sınıfı değiştirmeden de bunu yapabilmenin yöntemleri mevut.
  • Modifikasyonlara ise kapalıdırlar:
    • Modülün kendisine ilişkin kaynak kodların değişikliğine ise izin verilmez/gerek kalmaz. Örneğin, DLL veya .jar kütüphanelerinde bir değişiklik yapılmasına gerek kalmaz. Tabi burada hata düzeltmelerini ve dile ilişkin ya da benzeri güncellemeleri göz ardı etmemiz gerekiyor.

Bu konu ile ilgili ifade edilen bir diğer analoji de şu şekilde ki “Palto giymek için açık göğüs ameliyatı gerekmez”, aslı ise “Open chest surgery is not required when putting on a coat”. Bizim için anlamı, yeni bir kabiliyet ekleyeceğim diye temek kabiliyetleri ve bütünlüğü patlatma riskini göze almamalıyız.

Peki bu prensipleri nasıl sağlayacağız? Bu konuya değinen ilk yazarlar, genelde bu prensibin, miras yolu ile karşılanabileceğini ifade etmişlerdi ki, artık ata sınıfın geliştirme detaylarına dayanan bir miras bize daha fazla bağımlılık getirmekte ve genelde kaçınılmaktadır. Bunun yerine, soyut sınıflar/metotlar ya da bir diğer ifade ile arayüzleri (C++’larda “pure abstract” sınıflar ve Java’lar daki Inteface sınıflar bu kategoriye dahil edilebilir) kullanan yaklaşım kabul görmektedir. Bu arayüzler modifikasyona kapalı, fakat genişlemeye açıktırlar. 

Bu sayede elde ettiğimiz en önemli kazanç, bu arayüzler sayesinde ek bir soyutlama seviyesi ve gevşek bağlaşım (türkçesi biraz garip oldu farkındayım. Bahsettiğim kavram “loose coupling”) elde edilmesidir. Bu arayüzleri gerçekleyen sınıfların birbirleri arasında herhangi bir kod paylaşımına da ihtiyaç olmayacaktır.

Şimdi basit bir çizim kabiliyeti geliştirdiğimizi düşünelim. Öncelikle OCP’yi göz önünde bulundurmadığımız duruma bakalım. Bu bağlamda, kabiliyetimizi geliştirirken, sadece çember ve kare çizeceğini düşündük ve aşağıdaki gibi sınıfları geliştirdik.

Yazılımımızı geliştirdik ve her şey güzel giderken, müşteri dikdörtgeni de şekiller arasında çizmemizi istedi, ne yapacağız? Öncelikle bir tane dikdörtgen sınıfı ekledik, şu ana kadar bir sıkıntı yok ama Painter sınıfımızı da değiştirmemiz gerekecek, işte burada OCP prensibimizin closed kısmı tarumar oldu. Peki bu kabiliyeti, Painter sınıfını değiştirmeden nasıl ekleyebilirdik? Interface desem, aklınıza ne gelir? Evet yanılmadınız, bir IShape ata sınıfı ile sanki daha kolay uyarlanabilir bir tasarım elde edeceğiz gibi görünüyor, tabi Painter sınıfına da bir dokunmamız gerekecek. Öncelikle mevcut sınıflarımızı bu bağlamda güncelleyelim:

Şimdi bu noktada dikdörtgen sınıfını eklediğimizde neler değişecekti? Aslında bakarsanız, aşağıdaki gibi Painter sınıfına artık dokunmamıza gerek bile kalmayacak. Hemen bakalım:

Gördüğünüz üzere ufak dokunuşlar ile ilk yazdığımız kodu OCP prensibine uygun ve daha genişleyebilir bir hale getirmiş olduk; Painter sınıfında bir değişiklik yapmadık ama kabiliyet anlamında genişlettik.

Burada değinmek istediğim bir diğer nokta da şu, yazılımların yaşam döngüsü süresince bazı değişikliklerin olabileceği her zaman göz önünde bulundurmalıyız. Yani bir diğer ifade ile gereksinimlerin veya ihtiyaçların hiçbir zaman değişmeyeceğini kabul ederek, yazılım tasarlayıp, geliştirmenin, günümüz şartları için pek bir geçerliliği olmadığını ifade etmemiz lazım (elbette kapsama/projeye göre bu değişebilir). Yazılımlarımızı asgari değişikliklere etkin bir şekilde cevap verecek şekilde geliştirmeliyiz. Ama bu mevzuyu da çok ileri bir noktaya taşıyıp, gereğinden karmaşık yazılım tasarımlarından da kaçınmalıyız. Tabi buradaki anahtar kelime “agari” neye göre, kime göre? İşte bu noktada, yazılım ürün hatları (inşallah ileride bu konuya da değineceğim) için önemli bir adım olan alan analizinin uygulanmasını tavsiye edeceğim. Bu analiz ile birlikte ilgili kabiliyete ilişkin aslında kapsam ve olası ortak ve değişkenlik gösteren nitelikler ve özellikler belirlenmektedir. Bu sayede de bu alan içerisinde, olası gelebilecek değişiklikleri belirleyerek ve bunları göz önünde bulundurabilirsiniz.

Sonuç olarak, bu prensibin başarılı bir şekilde uygulanması ile birlikte yazılımlarınıza değişiklikleri daha kolay ve kontrollü bir şekilde yapabileceksiniz. Aynı zamanda yeni kabiliyet eklemelerini de mevcut kod üzerinde minimum değişiklik ile gerçekleştirebileceksiniz. Son olarak, soyutlamanın OCP anlamında kritik olduğunu ve yazılımları geliştirirken yukarıdakilere ek olarak çok değişebilecek kısımlar ile değişmeyecek kısımların ayrılmasının da önemli olduğunu ifade etmek istiyorum. Kaynaklarda, OCP prensibine ilişkin daha detaylı bilgi ve farklı kullanım örneklerini bulabilirsiniz. Bir sonraki SOLID prensibi yazısında görüşmek dileğiyle bol kod’lu günler 🙂

Kaynaklar:

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.