SOLID Prensipleri

Yazılım Geliştirmede SOLID prensipleri

Yazar: Adnan Kaya

İletişim: Linkedin , Github , Github Page , Upwork

Yazılım Geliştirmede SOLID prensipleri

1. Single Responsibility Principle

  • Bir sınıf veya modülün ancak bir sorumluluğu olmalıdır. Yalnızca bir iş yapmalıdır.

  • Bu prensibi daha iyi anlayabilmek için bir ev inşa edilme örneğini düşünelim. Evin mimarı, elektrikçisi, tesisatçısı, duvar ustası vs. her biri kendi işinden sorumludur. Hiçbiri diğerinin işine müdahale etmez.

  • Yazılımda da bu prensip geçerlidir. Bir sınıf veya modül yalnıca bir sorumluluk alır ve onu en iyi şekilde yapar. Ekstradan alakalı olmayan sorumluluklar almaz.

  • Bu standart takip edildiğinde kod daha iyi anlaşılır, test edilebilir ve sürdürülebilir olmaktadır.

  • Bu prensibi anlamak için 2 örnek inceleyeceğiz. Birinci örnek bu prensibi ihlal eden ikinci örnek bu prensibi uygulayan bir örnek olacaktır.

1. SRP ihlal 2. SRP uygulama

Employee sınıfı hem maaş hesaplama hem de veritabanına kayıt fonksiyonlarını gerçekleştirecek şekilde tanımlandığı yani birden fazla sorumluluğu olduğu için SRP ihlali yapmaktadır. Employee sınıfı elbette başka fonksiyonlara sahip olabilir ancak burada önemli olan birbirinden bağımsız işlerin aynı sınıf tarafından yapılıp yapılmadığıdır. Maaş hesaplamada yapılacak değişiklikler veritabanına kayıt işlemlerinde de değişikliğe neden olabilir. Employee ve EmployeeDatabase sınıfları kendilerine has işlerden sorumludur. Maaş hesaplamada yapılacak değişiklik EmployeeDatabase sınıfının fonksiyonlarına etki etmez. Sorumlulukları birbirinden ayırmak, izole etmek değişim için esnek ve sürdürülebilir bir kod yapısı sağlar.

2. Open-Closed Principle

  • Yazılım mühendisliğinde temel bir konsept olan OCP sınıf, modül, fonksyionların genişletilebilir(ilave edilebilir) olmasını ancak değiştirilebilir olmamasını tanımlar.

  • OCP mevcut olan kodu değiştirmeden yeni fonksiyonelite eklememize olanak sağlar.

  • Bu standart takip edildiğinde kod daha iyi anlaşılır, test edilebilir ve sürdürülebilir olmaktadır.

  • Kredi kartı ödemeleri ve PayPal ödemeleri gibi farklı ödeme türlerini işleyen bir sistemimiz olduğunu hayal edin. Başlangıçta sistem sadece kredi kartı ödemelerini desteklemektedir. Ancak gelecekte yeni ödeme yöntemlerinin eklenmesini bekliyoruz ve OCP'yi takip etmek istiyoruz.

1. OCP ihlal 2. OCP uygulama

Bu örnekte, PaymentProcessor sınıfı, yalnızca kredi kartı ödemelerini desteklediği ve yeni bir ödeme yöntemi (PayPal) eklemek için mevcut sınıfın değiştirilmesini gerektirdiği için OCP'yi ihlal etmektedir.

OCP'yi nasıl ihlal edeceğimizi göstermek için, mevcut sınıfı genişletmek yerine yeni ödeme yöntemini (PayPal) işlemek için doğrudan mevcut sınıfın dışına kod ekliyoruz. Mevcut sınıf değiştirildiği için de bu ilke ihlal ediliyor.

PaymentProcessor sınıfını her bir ödeme yöntemi için belirli alt sınıflarla genişleterek, mevcut kodu(kredi kartı sınıfını) değiştirmeden yeni ödeme yöntemleri (PayPal gibi) ekleyebiliriz. Bu şekilde, mevcut işlevselliğe dokunulmaz ve temel sınıf genişletilerek yeni işlevler eklenebilir.

OCP'ye bağlı kalarak, mevcut ödeme işleme mantığını değiştirmeden yeni ödeme yöntemlerinin eklenmesine izin veren daha esnek ve sürdürülebilir bir kod elde ediyoruz.

3. Liskov Substitution Principle

  • LSP, bir üst sınıfın nesnelerinin, programın doğruluğunu etkilemeden kendi alt sınıflarının nesneleriyle değiştirilebilmesi gerektiğini belirten, nesne yönelimli programlamada bir ilkedir.

  • Başka bir deyişle, alt sınıflar, temel sınıflarıyla birbirinin yerine kullanılabilir olmalıdır.

  • Bu standart takip edildiğinde kod daha iyi anlaşılır, test edilebilir ve sürdürülebilir olmaktadır.

  • Ödeme sistemi örneği üzerinden LSP'yi görelim.

1. LSP ihlal 2. LSP uygulama

PayPalPayment alt sınıfı, güvenlik kodu yerine bir e-posta adresini (güvenlik_kodu) kabul ederek make_payment yönteminin davranışını değiştirdiği için LSP'yi ihlal eder. Davranıştaki bu tutarsızlık, security_code parametresinin Payment'ın tüm alt sınıflarında aynı anlama ve kullanıma sahip olduğunu varsaydığından ilkeyi bozar. Payment üst sınıfındaki ve alt sınıflarındaki (CreditCardPayment ve PayPalPayment) make_payment yöntemi, beklenen davranışı koruyarak aynı davranışa sahiptir.

PaymentProcessor sınıfının process_payment yöntemi artık herhangi bir ek parametre olmadan bir Payment nesnesini kabul ediyor. Belirli alt sınıf örneğinden bağımsız olarak Payment nesnesinin make_payment yöntemini çağırır. Herhangi bir alt sınıf örneği, beklenen davranışı bozmadan üst sınıf örneğini değiştirebileceğinden, bu LSP'ye uygundur.

Ek olarak, CreditCardPayment ve PayPalPayment alt sınıfları artık ilgili __init__ metodlarında başlatılan kendi özel özniteliklerine(attributes) (sırasıyla security_code ve email) sahiptir. Her alt sınıftaki make_payment yöntemi, ödeme yöntemine özgü ödeme mantığını işler ancak yöntemin davranışını değiştirmez.

4. Interface Segregation Principle

  • ISP, basit bir ifadeyle, bir sınıfın gereksiz yöntemlerle uğraşmak yerine yalnızca gerçekten ihtiyaç duyduğu yöntemleri bilmesi gerektiğini öne sürer.

  • Bu prensibi sade bir dil ile açıklamak için "Arayüzleri(Interface) küçük ve odaklı tutun" şeklinde düşünün.

  • Çok çeşitli yöntemler sağlayan büyük bir arabirime sahip olmak yerine, belirli ihtiyaçları karşılayan daha küçük arabirimlere sahip olmak daha iyidir.

  • İstemciler(Clients) yalnızca gereksinimleriyle ilgili arabirimlere bağlı olabileceğinden, bu, kodunuzdaki modülerliği ve esnekliği destekler.

  • Gerçek dünyadan bir Ödeme sistemi örneğini ele alalım. process_payment, refund_payment ve get_payment_status gibi fonksiyonlar sağlayan PaymentProcessor adlı bir interface olduğunu varsayalım. Ancak, sistemin farklı bölümlerinin farklı ihtiyaçları vardır. Örneğin, müşteriye yönelik bir modül, gelen ödemeleri işlemek için yalnızca process_payment yöntemine ihtiyaç duyarken, bir yönetici modülü, geri ödemeleri işlemek ve ödemelerin durumunu kontrol etmek için refund_payment ve get_payment_status fonksiyonlarına ihtiyaç duyabilir.

  • ISP'ye bağlı kalmak için arayüzü(interface) her bir modülün ihtiyaçlarına özel daha küçük arayüzlere ayırırız. Örneğin, müşteriler için yalnızca process_payment yöntemiyle bir PaymentProcessor arabirimi ve refund_payment ve get_payment_status yöntemleriyle yöneticiler için ayrı bir RefundProcessor arabirimi oluşturabiliriz. Her modül daha sonra gereksinimlerine uyan arabirime bağlı olabilir.

1. ISP ihlal 2. ISP uygulama

Yukarıdaki örnekte, tüm metodlar tek bir arayüzün(interface) parçasıdır. Bir modülün yalnızca ödemeleri işlemesi gerekiyorsa, refund_payment veya get_payment_status yöntemlerini kullanmasa bile içe aktarması ve tüm PaymentProcessor sınıfına bağlı olması gerekir. Bu, ISP'yi ihlal eder çünkü istemciler ihtiyaç duymadıkları yöntemlere bağımlı olmaya zorlanırlar. Bu örnekte arayüzü iki ayrı sınıfa ayırdık: PaymentProcessor ve RefundProcessor. İstemci modülleri artık ihtiyaç duydukları belirli arayüze bağlı olabilir. Örneğin, müşteri modülü PaymentProcessor sınıfını içe aktarabilir ve kullanabilirken yönetici modülü, RefundProcessor sınıfını içe aktarabilir ve kullanabilir. İstemciler yalnızca ihtiyaç duydukları yöntemlere bağlı olduğundan, bu ISP'ye bağlıdır.

5. Dependency Inversion Principle

  • DIP, Yüksek seviyeli modüllerin düşük seviyeli modüllere bağlı olmaması gerektiğini belirtir. Bunun yerine, her ikisi de soyutlamalara bağlı olmalıdır. Başka bir deyişle, bu prensip bizi somut uygulamalardan çok soyutlamalara bağlı kalmaya teşvik eder.

  • Bir ödeme sistemi örneğini ele alalım. Ödemeleri işleme mantığını işleyen PaymentProcessor adlı üst düzey bir modülünüz olduğunu hayal edin. DIP uygulanmayan geleneksel bir yaklaşımda, PaymentProcessor doğrudan CreditCardPayment, PayPalPayment ve BankTransferPayment gibi düşük seviyeli modüllere bağlı olacaktır.

1. DIP ihlal 2. DIP uygulama

Yukarıdaki kodda, PaymentProcessor doğrudan CreditCardPayment, PayPalPayment ve BankTransferPayment gibi ödeme türlerinin somut uygulamalarına bağlıdır. Üst düzey modül (PaymentProcessor) doğrudan alt düzey modüllere bağlı olduğundan, bu, Bağımlılığı Tersine Çevirme İlkesini ihlal eder.

Bağımlılığı Tersine Çevirme İlkesini (Dependency Inversion Principle) uygulamak için, soyutlamalar sunmamız ve hem yüksek seviyeli hem de düşük seviyeli modüllerin soyutlamalara bağlı olmasını sağlamamız gerekir. Bu durumda, herhangi bir ödeme yöntemini temsil eden PaymentMethod adında soyut bir sınıf veya arayüz tanıtabiliriz.

Yukarıdaki kodda PaymentProcessor, belirli ödeme türleri yerine PaymentMethod soyutlamasına bağlıdır. PaymentMethod arayüzü uygulanarak farklı ödeme yöntemleri kolayca eklenebildiğinden, bu esneklik sağlar. PaymentProcessor'ın her bir ödeme yönteminin ayrıntılarını bilmesi gerekmez. Aldığı PaymentMethod nesnesinde process() metodunu çağırır.

Bağımlılık Tersine Çevirme İlkesini uygulayarak, daha esnek ve sürdürülebilir bağlı bir tasarım elde ediyoruz. Üst düzey modül (PaymentProcessor), daha kolay bakım, genişletilebilirlik ve test edilebilirlik sağlayan somut uygulamalardan ziyade bir soyutlamaya (PaymentMethod) bağlıdır.

Yorumlar