SOLİD Prensipleri #2

Faruk Aydemir
Aug 29, 2018 · 6 min read

Tekrardan merhabalar; SOLİD Prensiplerine kaldığımız yerden devam ediyoruz…

Yazının 1. kısmına SOLİD Prensipleri #1 yazımdan ulaşa bilirsiniz.


3. Liskov’s Substitution Principle (LSP)

T cinsinden parametre alan tüm programlar (fonksiyonlar) P olacak şekilde, S tipinde o1 nesnesi ve T tipinde o2 nesnesi olsun. Eğer o1 ile o2 nesneleri yer değiştirdiğinde P’nin davranışı değişmiyorsa S tipi T tipinin alt tipidir!

Evet, sanırım yolu yarılamayı başardık eğlence daha yeni başlıyor. 3 numaralı prensibimiz Liskov’un yerine geçebilme prensibidir. Prensibimize adını veren tatlımı tatlı, zekimi zeki ablamız Barbara Liskov tarafından ki kendisi MIT profesör ve çok yetkili bir ablamız 1988 yılında Data Abstraction and Hierarchy adlı kitabında ilk defa bu kendi isimi taşıyan prensibi ortaya koymuş ve yukarıdaki son derece anlaşılması zor bir şekilde açıklamıştır.

Yukarıdaki açıklamayı anlayan beri gelsin…

Sonra bir babayiğit ortaya çıkıp, “ben bunu matematiksel olarak formüle ettim daha anlaşılır oldu” demiş ve aşağıdaki açıklamayı yapmış.

“q(x) fonksiyonu T tipindeki x nesnesi için kanıtlanabilirdir. O halde T tipinin alt tipi olan S tipindeki y nesnesi için de q(y) fonksiyonu kanıtlanabilir olmalıdır.”

Yine olmadı değil mi?

Sonrasında bizim SOLİD prensiplerinin giriş kısmında kendisinden bahsettiğimizi Robert Martin abi bu böyle olmaz yazılımcıların anlayacağı şekilde açıklayın demiş ve nam-ı diğer Bob amca şöyle bir açıklama yapmış:

“Temel (base) sınıfın işaretçisini (pointer) ya da referansını kullanan fonksiyonlar, bu sınıftan türemiş olan (derived) sınıfları da ekstra bilgiye ihtiyaç duymaksızın kullanabilmelidir.”

Akademik bir dilde yazılan makaleyi anlamak oldukça zor gelebilir hatta içinizden “Liskov abla acaba burada ne demek istedi?” Sorusunu geçirmiş olabilirsiniz. Merak etmeyin bu açıklamayı bende tam olarak anlamadım. Ancak yazılımcı gözüyle tanımı tekrar yaparak ve Bob amcanın açıklamasını biraz daha anlaşılır hale getirerek örneklendirebiliriz.

Tanım: Alt sınıflar miras aldığı üst sınıfın bütün özelliklerini kullanmalı, alt sınıflarda oluşturulan nesneler üst sınıfların nesneleriyle yer değiştirdiklerinde aynı davranışı göstermeli ve herhangi bir kullanılmayan özellik olmamalı.

Bu şekilde açıklandığı zaman biraz daha anlaşılabilir oldu. Ancak hala tam olarak kafanızda canlanmadıysa aşağıdaki kodlar üzerinden açıklayarak daha anlaşılır bir hal almasını sağlayalım.

Hadi bu durumu kod içerisinde inceleyelim;

Senaryomuz şu şekilde, 2 adet yazıcımız olsun ve bunlar bir abstract(soyut) yazıcı sınıfından kalıtılarak özelliklerini alsınlar. Liskov prensibine göre kalıtıldıkları üst sınıfın tüm özelliklerini kullanmak zorundan olan bu iki sınıftan biri ya bu özellikleri kullanmazsa? Hadi kod üzerinde bakalım;

Bir adet abstract BasePrinter class ve bunun içerisinde yine abstract Print ve Scan olmak üzere iki özellik için metotlar oluşturduk. Abstract classımızı kalıtan bütün classlar bu üzellikleri sağlamak zorundalar.

Peki, sağlamazlarsa ne olacak? NotImplementedException !

Görüldüğü üzere biri Hp bir diğeri Canon olmak üzere iki farklı marka yazıcıya sahip olduğumuzu var sayıyoruz ve ikisi de BasePrinter sınıfından kalıtılıyor. Yazıcılarımızdan ikisi de yazma özelliğine sahip ikine tarama özelliğine sadece Canon sahip, Hp de ise bu özellik kullanılmadığından metod mevcut ama yukarıda belirttiğimiz gibi NotImplementedExp hatası döndürmekte.

Peki, bir üst sınıfta bu alt sınıfların nasılsa BasePrinter sınıfından kalıtıldığını ve iki özelliğinde olduğunu düşünen bir yazılımcı iki özelliği de her iki marka için kullanmaya kalkarsa?

İşte bu durumda çalışma zamanında bizim bahsettiğimiz hata ile karşılaşır.

Peki, doğru kullanımı nasıl olmalıydı?

Doğru kullanımı BasePrinter içerisinde sadece Print metodu olmalı ve IScan adından bir ara yüz içerisinde ise Scan metodu bunulmalı Hp Sadece BasePrinti kullanır iken Canon aynı zamanda IScan ara yüzüde kalıtmalıdır. Hadi kodda görelim.

Görüldüğü üzere artık Hp sınıfımız sadece Print özelliğine, Canon ise hem Print hem de Scan özelliğine sahip duruma geldi. Böyle kullanıldığında iki sınıfta, kalıtıldığı abstract(soyut) BasePrint sınıfının bütün özelliklerine sahip bir duruma geldi.

Kullanımımızı da bu şekilde göstere biliriz.

Umarım anlaşılmıştır elimden geldiğince açık şekilde anlatmaya çalıştım ama anlaşılması bakımından bana göre en zor olan prensiplerden biriydi.


4. İnterface Segregation Principle

“Ara yüzlerin ayrılması prensibi

Bana göre prensipler arasında en kolay anlaşılabilir olanı ISP, temel amacı ara yüz implementation sonucunda oluşacak gereksiz kodları önlemek ve kodumuzun daha amaca yönelik hale gelmesini sağlamaktır.

Biraz daha açıklamak gerekirse, ara yüzler içinde sadece metotların imzaları bulunur. Bir ara yüz bir sınıfa implemente edildiği zaman, ara yüz’ün barındırdığı metotları barındırmak veya oluşturmak zorundadır. Zaten bu durumun aksi olduğundan hata alırız.

İşte bu durumda prensibimiz devreye girer ve derki “eğer class içerisinde gerçekten ihtiyaç duyulmayan ve kullanılmayan metotlar ara yüz aracılığı ile implemente edilmiş ise bu kodlar dummy kod olur, bu yüzden ara yüzler ayrılmalı ve classlar açısından işlevsel olmayan metotlar barındırması engellenmelidir.”

Hadi daha anlaşılır olması açısından durumu kodlar üzerinden inceleyelim;

Senaryomuz şu şekilde olsun bizim bir BaseAPI ara yüzümüz ve bunun içerisinde get, put, post ve delete metodlarımız olsun. Ardından bunu service katmanlarındaki news, video gibi katmanlara implemente edelim.

Şimdilik bir sıkıntı yok peki sadece Get yaptığımız bir servisimiz olursa örneğin bildirimleri çektiğimiz Notification servisimiz olduğu durumda buna BaseApı implemente edilir ise gereksiz bir şekilde Hiç kullanılmayacak olan put, pot ve delete metodları da implemente edilecek. Aşağıdaki kod bloğunda göründüğü gibi.

Peki, bu durumda doğru yaklaşım nasıl olmalıdır? Doğru yaklaşım Sadece içerisinde Get metodunu barındıran bir ara yüz oluşturulması ve Notification servisi bundan kalıtılması olmalıdır. Aynı zamanda IBaseApi içerisinden Get metodu çıkartılarak yeni oluşturulan ara yüz IBaseApi tarafından kalıtılmalıdır.

Böylelikle IBaseApi den kalıtılan News ve Video servicelerinde bir değişiklik yapmak zorunda kalmadığımız gibi, aynı zamanda Notification servicesinide IGet arayüzsinden kalıtarak gereksiz metodlardan kurtulmuş oluruz aşağıda olduğu gibi.


5. Dependency Inversion Principle

“Katmanlı mimarilerde üst seviye modüller alt seviyedeki modüllere doğruda bağımlı olmamalıdır.”

Prensibin Türkçeye çevrilmiş hali bağımlılıkların tersine çevrilmesi şeklindedir. Pek anlaşılır gelmediğinin farkındayım biz bunun yerine açıklamada belirtilen ifadeyi biraz daha açarak “üst seviyeli katmanlar kesinlikle alt seviyedeki katmanlara bağlı olmamalı, bağımlılıklar sadece abstract (soyut) kavramlara olmalıdır” şeklinde ifade edebiliriz.

Burada amaç üst seviyedeki modüllerin alt seviyelere bağımlı olmasından dolayı çıkabilecek sorunları ortadan kaldırmaktır. Yani alt seviyede yapılan herhangi bir değişikliğin üst seviyede kod değişikliğine veya onun bağlılıklarının etkilenmesine engel olmaktır amaç.

Daha kaba bir anlatım ile üst seviyedeki modül alt seviyedeki işin nasıl yürüdüğünü bilmemeli ve ilgilenmemelidir. Ben üst seviyedeki modül olarak diyorum ki; alt taraftan x verisinin nasıl geldiği ile ilgilenmem, bana gelen x verisi ile ilgili işlemimi yapar, gerekli yerlere gönderirim. Ama x verisi bir veri tabanından mı geldi, bir API den mi geldi yoksa bir text dosyasından okunarak mı geldi burası beni ilgilendirmez. Zaten bu gibi isteklerinde beni etkilememesi gerekmektedir.

Kafamızda kavram biraz daha canlandığını düşünüyorum peki bunu kodda nasıl uygulayacağız.

Hadi kod üzerinden bu durumu inceleyelim;

OCP de incelediğimiz örnek üzerinden giderek bir adet Logger Servisimiz ve bunun arka tarafından XML log üreten bir sınıfımız olsun.

Yan taraftaki örnekte herhangi bir sıkıntı görüyor musunuz?

Logger sınıfımızda göründüğü üzere XlmLog sınıfı instance edilmiş. Bunun ne gibi bir sıkıntısı var diyecek oluyorsunuz, hemen cevaplıyayım bu durum prensibe aykırı. Neden mi? Çünkü Logger sınıfı XmlLog classını artık tanıyor ona ihtiyaç duyduğunu biliyor ve onu ile ilgili bilgilere sahip. Ne gibi bir sıkıntısı var bunun peki?

Biz bunu bir XmlLog sınıfını bir NugetPacket haline getirip sunduğumuzda da insanlar bu kodu görecekler ve bağımlılığı bilecekler. Ama bir ara yüz olsa idi, biz packetimizde bu ara yüzü kullandırtacak ve arkada neler döndüğünü umursamamalarını sağlayacaktık nasıl mı hemen inceleyelim.

Burada doğru kullanımı görüyorsunuz artık bağımlılığımız XmlLog sınıfına değil doğrudan ILogger ara yüzüne bunun biz sağladığı avantaj şu biz direk bir dll paketi yaparak bunu sunduğumuzda insanlar sadece ILogger a erişerek bizim loglama sistemimizi kullanacak ve arkada dönen işlerle bir ilgileri veya bilgileri olmayacaktır.


Böylelikle uzun ve yorucu bir seri olan SOLİD Prensipleri ile ilgili yazdığımız yazının sonuna geldik umarım sıkılmamışsınızdır.

Elimden geldiğince açık ve anlaşılır bir şekilde anlatmaya çalıştım faydalı olması dileğiyle.


Kod örneklerine buradan ulaşabilirsiniz : Git-Up


Bir sonraki yazımda görüşmek üzere…

PiriLabs

Piri Medya ve Albayrak Holding Yazılım Geliştirme Grubu

Faruk Aydemir

Written by

Software Architect at Ofix.com; linkedin.com/in/faydemir8; github.com/faydemir8

PiriLabs

PiriLabs

Piri Medya ve Albayrak Holding Yazılım Geliştirme Grubu

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade