paint-brush
Kodunuzu Nasıl KATI TUTURSUNUZ?ile@oxymoron_31
4,000 okumalar
4,000 okumalar

Kodunuzu Nasıl KATI TUTURSUNUZ?

ile Nagarakshitha Ramu6m2023/03/15
Read on Terminal Reader
Read this story w/o Javascript

Çok uzun; Okumak

S.O.L.I.D ilkeleri, nesne yönelimli programlamada temiz kod yazmaya yönelik genel yönergelerdir. Bunlar Tek Sorumluluk İlkesini, Açık-Kapalı İlkesini, Arayüz Ayrışmasını, Bağımlılığın Tersine Dönmesini ve İkame İlkesini içerir. Bu ilkeler çeşitli programlama dillerine uygulanabilir.
featured image - Kodunuzu Nasıl KATI TUTURSUNUZ?
Nagarakshitha Ramu HackerNoon profile picture
0-item

Genel olarak, tüm programlama dilleri iki paradigmaya göre sınıflandırılabilir:

Zorunlu/ Nesne yönelimli programlama - Satır satır yürütülen talimatların sırasını takip eder.

Bildirimsel / İşlevsel programlama - Sıralı değildir ancak programın amacına daha çok ilgi gösterir. Programın tamamı, her biri belirli bir görevi yerine getiren alt işlevlere sahip bir işlev gibidir.

Kıdemsiz bir geliştirici olarak, bunun sadece işlevsel kod yazmakla ilgili olmadığını, aynı zamanda anlamsal olarak kolay ve esnek kod yazmakla ilgili olduğunu zor yoldan fark ettim (okudum: 1000'lerce kod satırına endişeyle bakıyordum).

Her iki paradigmada da temiz kod yazmak için birden fazla en iyi uygulama mevcut olsa da, nesne yönelimli programlama paradigmasına ilişkin SOLID tasarım ilkelerinden bahsedeceğim.

KATI nedir?

S — Tek Sorumluluk
O—Açık-Kapalı Prensibi
L - Liskov Değiştirme Prensibi
I - Arayüz Ayrımı
D — Bağımlılığın Tersine çevrilmesi

Bu kavramların anlaşılmasındaki herhangi bir zorluğun ana nedeni, teknik derinliklerinin anlaşılmaz olması değil, nesne yönelimli programlamada temiz kod yazmak için genelleştirilmiş soyut yönergeler olmalarıdır. Bu kavramları eve taşımak için bazı üst düzey sınıf diyagramlarına bakalım.

Bunlar doğru sınıf diyagramları değildir ancak bir sınıfta hangi yöntemlerin mevcut olduğunu anlamanıza yardımcı olacak temel planlardır.

Yazı boyunca bir kafe örneğini ele alalım.

Tek Sorumluluk İlkesi

Bir sınıfın değişmek için tek bir nedeni olmalıdır

Kafe tarafından alınan çevrimiçi siparişlerle ilgilenen bu sınıfı düşünün.

Bunun nesi var?
Bu tek sınıf birden fazla işlevden sorumludur. Başka ödeme yöntemleri eklemeniz gerekirse ne olur? Onay göndermenin birden fazla yolu varsa ne olur? Siparişlerin işlenmesinden sorumlu olan sınıfta ödeme mantığını değiştirmek pek iyi bir tasarım değil. Oldukça esnek olmayan kodlara yol açar.

Daha iyi bir yol, bu belirli işlevleri somut sınıflara ayırmak ve aşağıdaki çizimde gösterildiği gibi bunların bir örneğini çağırmak olacaktır.

Açık-Kapalı Prensibi

Varlıklar genişlemeye açık, değişiklik için kapalı olmalıdır.

Kafede kahvenizin çeşnilerini seçenekler listesinden seçmeniz gerekiyor ve bunu yapan bir sınıf var.

Kafe yeni bir çeşni olan tereyağını eklemeye karar verdi. Seçilen çeşniye göre fiyatın nasıl değişeceğine ve fiyat hesaplama mantığının Kahve sınıfında olduğuna dikkat edin. Her seferinde ana sınıfta olası kod değişiklikleri yaratan yeni bir çeşni sınıfı eklemekle kalmıyoruz, aynı zamanda mantığı her seferinde farklı şekilde ele alıyoruz.

Daha iyi bir yol, kendi yöntemlerini geçersiz kılan alt sınıflara sahip olabilecek bir çeşniler arayüzü oluşturmak olacaktır. Ve ana sınıf, parametreleri iletmek ve her sipariş için miktar ve fiyatı almak için çeşniler arayüzünü kullanabilir.

Bunun iki avantajı vardır:

1. Farklı ve hatta birden fazla çeşniye sahip olmak için siparişinizi dinamik olarak değiştirebilirsiniz (mocha ve çikolatalı kahve kulağa harika geliyor).

2. Çeşniler sınıfının Coffee sınıfıyla is-a yerine has-a ilişkisi olacak. Yani Kahveniz, Kahveniz mocha/tereyağı/sütlü kahve yerine mocha/tereyağı/süt içerebilir.

Liskov Değiştirme Prensibi

Her alt sınıf veya türetilmiş sınıf, temel veya üst sınıfının yerine geçebilmelidir.

Bu, alt sınıfın doğrudan ana sınıfın yerini alabilmesi gerektiği anlamına gelir; aynı işlevselliğe sahip olmalıdır. Bunu anlamakta zorlandım çünkü kulağa karmaşık bir matematik formülü gibi geliyor. Ancak bu yazımda bunu netleştirmeye çalışacağım.

Kafedeki personeli düşünün. Baristalar, yöneticiler ve sunucular var. Hepsinin benzer işlevleri var.

Dolayısıyla isim, pozisyon, getName, getPostion, takeOrder(), serve() ile temel bir Personel sınıfı oluşturabiliriz.

Garson, Barista ve Yönetici gibi somut sınıfların her biri bundan türetilebilir ve pozisyon için gerektiğinde bunları uygulamak için aynı yöntemleri geçersiz kılabilir.

Bu örnekte, Liskov Değiştirme Prensibi (LSP), türetilmiş herhangi bir Staff sınıfının, kodun doğruluğunu etkilemeden temel Staff sınıfıyla birbirinin yerine kullanılabilmesini sağlayarak kullanılır.

Örneğin, Garson sınıfı, Personel sınıfını genişletir ve bir garsonun rolüne özel ek işlevler eklemek için takeOrder ve serveOrder yöntemlerini geçersiz kılar. Ancak daha da önemlisi, işlevsellikteki farklılıklara rağmen Staff sınıfından bir nesne bekleyen herhangi bir kod, Waiter sınıfından bir nesneyle de doğru şekilde çalışabilir.

Bunu anlamak için bir örnek görelim

 public class Cafe {    public void serveCustomer (Staff staff) { staff.takeOrder(); staff.serveOrder(); } } public class Main {    public static void main (String[] args) { Cafe cafe = new Cafe(); Staff staff1 = new Staff( "John" , "Staff" ); Waiter waiter1 = new Waiter( "Jane" ); restaurant.serveCustomer(staff1); // Works correctly with Staff object
 restaurant.serveCustomer(waiter1); // Works correctly with Waiter object
 } }

Burada Cafe sınıfındaki serveCustomer() yöntemi, parametre olarak bir Staff nesnesini kullanır. serveCustomer() yöntemi, müşteriye hizmet vermek için Staff nesnesinin takeOrder() ve serveOrder() yöntemlerini çağırır.

Main sınıfında bir Staff nesnesi ve bir Waiter nesnesi oluşturuyoruz. Daha sonra Cafe sınıfının serveCustomer() yöntemini iki kez çağırırız; bir kez Staff nesnesiyle ve bir kez de Waiter nesnesiyle.

Garson sınıfı, Personel sınıfından türetildiği için, Personel sınıfının bir nesnesini bekleyen herhangi bir kod, Garson sınıfının bir nesnesiyle de doğru şekilde çalışabilir. Bu durumda, Garson nesnesi garsonun rolüne özel ek işlevlere sahip olmasına rağmen Cafe sınıfının serveCustomer() yöntemi hem Staff nesnesiyle hem de Waiter nesnesiyle doğru şekilde çalışır.

Arayüz Ayrımı

Sınıflar kullanmadıkları yöntemlere bağımlı olmaya zorlanmamalıdır.

Kafede kahve, çay, atıştırmalık ve soda dağıtabilen çok yönlü bir otomat var.

Bunda yanlış olan ne? Teknik olarak hiçbir şey yok. Arayüzü kahve dağıtımı gibi işlevlerden herhangi biri için uygulamanız gerekiyorsa çay, soda ve atıştırmalıklara yönelik diğer yöntemleri de uygulamanız gerekir. Bu gereksizdir ve bu işlevler birbirlerinin işlevleriyle ilişkili değildir. Bu işlevlerin her birinin arasında çok daha az uyum vardır.

Uyum nedir? Bir arayüzdeki yöntemlerin birbirleriyle ne kadar güçlü bir şekilde ilişkili olduğunu belirleyen bir faktördür.

Otomatik satış makinesi örneğinde ise yöntemler pek birbirine bağımlı değildir. Uyumları çok düşük olduğundan yöntemleri ayırabiliriz.

Artık, tek bir şeyi uygulamaya yönelik olan herhangi bir arayüz, yalnızca tüm işlevler için ortak olan takeMoney() işlevini uygulamalıdır. Bu, bir arayüzdeki ilgisiz fonksiyonları ayırır, böylece ilgisiz fonksiyonların bir arayüzde zorla uygulanmasından kaçınılır.

Bağımlılığın Tersine çevrilmesi

Yüksek seviyeli modüller düşük seviyeli modüllere bağlı olmamalıdır. Ayrıntılar soyutlamalara bağlı olmalıdır.

Kafedeki klimayı (soğutucuyu) düşünün. Ve eğer siz de benim gibiyseniz, orası her zaman donuyor. Uzaktan kumandaya ve AC sınıflarına bakalım.

Burada RemoteControl, düşük seviyeli bileşen olan AC'ye bağlı olan yüksek seviyeli modüldür. Eğer oy alırsam ısıtıcı da isterim :P Yani soğutucu yerine genel bir sıcaklık düzenlemesine sahip olmak için uzaktan kumanda ile sıcaklık kontrolünü birbirinden ayıralım. Ancak RemoteControl sınıfı, somut bir uygulama olan AC ile sıkı bir şekilde bağlantılıdır. Bağımlılığı ayrıştırmak için, örneğin 45-65 F aralığında sadece boostTemp() ve azalışTemp() fonksiyonlarına sahip bir arayüz yapabiliriz.

Son Düşünceler

Gördüğünüz gibi hem yüksek seviyeli hem de düşük seviyeli modüller, sıcaklığı artırma veya azaltma işlevini soyutlayan bir arayüze bağlıdır.

Beton sınıfı AC, uygulanabilir sıcaklık aralığına sahip yöntemleri uygular.

Artık muhtemelen ısıtıcı adı verilen farklı bir beton sınıfında farklı sıcaklık aralıklarını uygulamak istediğim ısıtıcıyı alabilirim.

Üst düzey modül olan RemoteControl'ün yalnızca çalışma süresi boyunca doğru yöntemi çağırma konusunda endişelenmesi gerekir.