SOLID tasarım ilkeleri, temiz kod yazmak için bilmeniz gereken en önemli tasarım ilkeleridir. SOLID ilkelerine sağlam bir hakimiyete sahip olmak her programcı için vazgeçilmez bir beceridir. Bunlar diğer tasarım modellerinin geliştirildiği temeldir. Bu yazıda SOLID tasarım ilkelerini gerçek hayattan örnekler kullanarak ele alacağız ve bunların önemini anlayacağız. Polimorfizm, Soyutlama ve Kalıtım ile birlikte SOLID İlkeleri, hedef odaklı programlamada iyi olmak için gerçekten önemlidir. SOLID İlkeleri Neden Önemlidir? SOLID ilkeleri birçok nedenden dolayı önemlidir: SOLID ilkeleri temiz ve bakımı kolay kod yazmamıza olanak tanır: Yeni bir projeye başladığımızda, başlangıçta kod kalitesi iyidir çünkü uyguladığımız sınırlı sayıda özellik vardır. Ancak, daha fazla özellik ekledikçe kod karmaşıklaşmaya başlar. SOLID İlkeleri Soyutlama, Polimorfizm ve Kalıtım temelleri üzerine kuruludur ve yaygın kullanım durumları için tasarım modellerine yol açar. Bu tasarım modellerini anlamak, programlamada yaygın kullanım durumlarının uygulanmasına yardımcı olur. SOLID İlkeleri, kodun test edilebilirliğini artıran temiz kod yazmamıza yardımcı olur. Bunun nedeni kodun modüler ve gevşek bir şekilde bağlanmış olmasıdır. Her modül bağımsız olarak geliştirilebilir ve bağımsız olarak test edilebilir. Şimdi SOLID ilkelerinin her birini gerçek dünyadan örneklerle ayrıntılı olarak inceleyelim. 1. Tek Sorumluluk İlkesi SOLID ilkelerindeki S, Tek Sorumluluk İlkesi anlamına gelir. Tek Sorumluluk İlkesi, bir sınıfın değişmek için yalnızca tek bir nedeni olması gerektiğini belirtir. Bu, projemize ek gereksinimler eklerken değişiklik yapmamız gereken yer sayısını sınırlar. Her sınıfın değişmek için tam olarak bir nedeni olmalıdır. Örneğin, Java'da, banka, kredi ve gibi temel işlemlere izin veren sınıfının bulunduğu bir bankacılık uygulaması tasarladığımızı varsayalım. yöntemi, adı verilen bir numaralandırmayı (E-posta, SMS vb. gibi) alır ve güncellemeyi uygun ortamla gönderir. Bunun kodunu aşağıda gösterildiği gibi yazacağız. sendUpdates SavingsAccount sendUpdate NotificationMedium public class SavingsAccount { public int balance; public String name; public SavingsAccount(int initialBalance, String name) { this.balance = initialBalance; this.name = name; System.out.println("Created a savings account with balance = " + initialBalance); } public void debit(int amountToDebit) { // debit business logic } public void credit(int amountToCredit) { // credit business logic } public void sendNotification(NotificationMedium medium) { if (medium == NotificationMedium.SMS) { // Send SMS here } else if (medium == NotificationMedium.EMAIL) { // Send Email here } } } public enum NotificationMedium { SMS, EMAIL } Şimdi, yukarıdaki sınıfına bakarsanız, bunun birçok nedenden dolayı değişebileceğini görürsünüz: SavingsAccount sınıfının temel mantığında herhangi bir değişiklik olup olmadığı ( , vb. gibi). SavingsAccount debit credit Banka yeni bir Bildirim ortamı sunmaya karar verirse (örneğin WhatsApp). Bu, SOLID İlkelerindeki Tek Sorumluluk İlkesinin ihlalidir. Düzeltmek için bildirimi gönderen ayrı bir sınıf oluşturacağız. Yukarıdaki kodu SOLID İlkelerine göre yeniden düzenleyelim public class SavingsAccount { public int balance; public String name; public SavingsAccount(int initialBalance, String name) { this.balance = initialBalance; this.name = name; System.out.println("Created a savings account with balance = " + initialBalance); } public void debit(int amountToDebit) { // debit business logic } public void credit(int amountToCredit) { // credit business logic } public void printBalance() { System.out.println("Name: " + name+ " Account Balance: " + balance); } public void sendNotification(Medium medium) { Sender.sendNotification(medium, this); } } public enum NotificationMedium { SMS, EMAIL } public class Sender { public static void sendNotification(NotificationMedium medium, SavingsAccount account) { // extract account data from the account object if (medium == NotificationMedium.SMS) { //logic to send SMS here } else if (medium == NotificationMedium.EMAIL) { // logic to send Email here } } } Artık kodu yeniden düzenlediğimiz için veya formatta herhangi bir değişiklik olursa sınıfını değiştireceğiz. Ancak çekirdek mantığında bir değişiklik olması durumunda sınıfında da değişiklikler olacaktır. NotificationMedium Sender SavingsAccount SavingsAccount Bu, ilk örnekte gözlemlediğimiz ihlali düzeltir. 2. Açma/Kapama Prensibi Open Close prensibi, sınıfları genişletmeye açık (ek özellikler eklenmesi durumunda) ancak değişiklik için kapalı olacak şekilde tasarlamamız gerektiğini belirtir. Değişikliklere kapalı olmak bize iki şekilde yardımcı olur: Çoğu zaman orijinal sınıf kaynağı mevcut bile olmayabilir. Projeniz tarafından tüketilen bir bağımlılık olabilir. Orijinal sınıfı değiştirmeden tutmak hata olasılığını azaltır. Çünkü değiştirmek istediğimiz sınıfa bağlı başka sınıflar da olabilir. Aç Kapat Prensibinin bir örneğini görmek için alışveriş sepeti örneğine (e-ticaret sitelerinde uygulanan örnek gibi) bakalım. Ekleyebileceğiniz listesini içeren adında bir sınıf oluşturacağım. Öğenin türüne ve vergilendirmesine bağlı olarak, sınıfı içindeki toplam sepet değerini hesaplayan bir yöntem oluşturmak istiyoruz. Item Cart Cart Sınıflar genişletilmeye açık, değiştirilmeye kapatılmalıdır. import java.util.ArrayList; import java.util.List; public class Cart { private List<Item> items; public Cart() { this.items = new ArrayList<>(); } public void addToCart(Item item) { items.add(item); } public double calculateCartValue() { double value = 0.0; for(Item item: items) { if (item.getItemType() == GIFT) { // 8% tax on gift + 2% gift wrap cost value += (item.getValue()*1.08) + item.getValue()*0.02 } else if (item.getItemType() == ItemType.ELECTRONIC_ITEM) { value += (item.getValue()*1.11); } else { value += item.getValue()*1.10; } } return value; } } @Getter @Setter public abstract class Item { protected double price; private ItemType itemType; public double getValue() { return price; } } public enum ItemType { ELECTRONIC, GIFT } Yukarıdaki örnekte, yöntemi, sepet içindeki tüm öğeleri yineleyerek ve öğenin türüne göre mantığı çağırarak sepet değerini hesaplar. calculateCartValue Bu kod doğru görünse de SOLID İlkelerini ihlal ediyor. Diyelim ki sepet değerini hesaplarken farklı bir ürün türü (mesela Bakkal) için yeni bir kural eklememiz gerekiyor. Bu durumda, orijinal sınıfını değiştirmemiz ve içine türündeki öğeleri kontrol eden başka bir koşulu yazmamız gerekir. Cart Grocery else if Ancak, küçük bir yeniden düzenlemeyle kodun açma/kapama ilkesine uymasını sağlayabiliriz. Bakalım nasıl olacak. Öncelikle aşağıda gösterildiği gibi sınıfını soyut hale getireceğiz ve farklı (ler) türleri için somut sınıflar yapacağız. Item Item public abstract class Item { protected double price; public double getValue() { return price; } } public class ElectronicItem extends Item { public ElectronicItem(double price) { super.price = price; } @Override public double getValue() { return super.getValue()*1.11; } } public class GiftItem extends Item { public GiftItem(double price) { super.price = price; } @Override public double getValue() { return super.getValue()*1.08 + super.getValue()*0.02; } } public class GroceryItem extends Item { public GroceryItem(double price) { super.price = price; } @Override public double getValue() { return super.getValue()*1.03; } } , ve gibi her somut Öğe sınıfının içinde vergilendirme ve değer hesaplamasına yönelik iş mantığını içeren yöntemi uygulanır. GroceryItem GiftItem ElectronicItem getValue() Şimdi, sınıfını soyut sınıfına bağlı hale getireceğiz ve aşağıda gösterildiği gibi her bir öğe için yöntemini çağıracağız. Cart Item getValue() import java.util.ArrayList; import java.util.List; public class Cart { private List<Item> items; public Cart(Payment paymentOption) { this.items = new ArrayList<>(); } public void addToCart(Item item) { items.add(item); } public double calculateCartValue() { double value = 0.0; for(Item item: items) { value += item.getValue(); } return value; } } Şimdi, bu yeniden düzenlenmiş kodda, yeni türleri tanıtılsa bile, sınıfı değişmeden kalır. Polimorfizm nedeniyle, içindeki gerçek türü ne olursa olsun, o sınıfın yöntemi çağrılacaktır. Item Cart ArrayList items Item getValue() 3. Liskov'un Değiştirme Prensibi Liskov'un ikame ilkesi, belirli bir kodda, bir üst sınıfın nesnesini alt sınıfın bir nesnesiyle değiştirsek bile kodun bozulmaması gerektiğini belirtir. Başka bir deyişle, bir alt sınıf bir üst sınıfı miras aldığında ve onun yöntemlerini geçersiz kıldığında, üst sınıftaki yöntemin davranışıyla tutarlılığı korumalıdır. Örnek olarak aşağıdaki sınıfları ve iki sınıfı da ve sınıfını yaparsak. Şimdi Araç sınıfında adında bir yöntem oluşturduğumuzu varsayalım, sınıfında override edilebilir ancak sınıfında motoru olmadığı için desteklenmeyecektir (aşağıdaki kod örneğine bakın) Vehicle Car Bicycle startEngine() Car Bicycle Bicycle Alt sınıf, yöntemleri geçersiz kılarken üst sınıfın davranışıyla tutarlılığı korumalıdır. class Vehicle { public void startEngine() { // start engine of the vehicle } } class Car extends Vehicle { @Override public void startEngine() { // Start Engine } } class Bicycle extends Vehicle { @Override public void startEngine(){ throw new UnsupportedOperationException("Bicycle doesn't have engine"); } } Şimdi, araç türünde bir nesne bekleyen ve yöntemine dayanan bir kod olduğunu varsayalım. Bu kod parçasını çağırırken tipi bir nesneyi iletmek yerine bir nesnesini geçersek, bu kodda sorunlara yol açacaktır. (ler) sınıfının yöntemi yöntemi çağrıldığında bir istisna oluşturacağından. Bu, SOLID İlkelerinin (Liskov'un ikame ilkesi) ihlali anlamına gelir. startEngine() Vehicle Bicycle Bicycle startEngine() Bu sorunu çözmek için, ve üzere iki sınıf oluşturabilir ve sınıfından miras almasını ve sınıfından miras almasını sağlayabiliriz. MotorizedVehicle NonMotorizedVehicle Car MotorizedVehicle Bicycle NonMotorizedVehicle class Vehicle { } class MotorizedVehicle extends Vehicle { public void startEngine() { // start engine here } } class Car extends MotorizedVehicle { @Override public void startEngine() { // Start Engine } } class NonMotorizedVehicle extends Vehicle { public void startRiding() { // Start without engine } } class Bicycle extends NonMotorizedVehicle { @Override public void startRiding(){ // Start riding without the engine. } } 4. Arayüz Ayırma Prensibi SOLID İlkelerindeki “I”, Arayüz Ayrıştırma İlkesini temsil eder. Arayüz ayırma ilkesi, uygulama sınıflarını kullanılmayan yöntemleri uygulamaya zorlayan daha büyük arayüzlere sahip olmak yerine, daha küçük arayüzlere sahip olmamız ve sınıfların uygulanmasını sağlamamız gerektiğini belirtir. Bu şekilde sınıflar yalnızca ilgili yöntemleri uygular ve temiz kalır. Arayüzlerinizi tek bir büyük arayüz yerine birden fazla küçük arayüze bölün. Örneğin, Java'daki yerleşik Koleksiyonlar çerçevesine bakalım. Java, diğer veri yapılarının yanı sıra ve veri yapısını da sağlar. LinkedList ArrayList sınıfı şu arayüzleri uygular: , , , , ve . ArrayList Serializable Cloneable Iterable Collection List RandomAccess sınıfı , , , , , ve uygular. LinkedList Serializable Cloneable Iterable Collection Deque List Queue Bu oldukça fazla arayüz demek! Java geliştiricileri bu kadar çok arayüze sahip olmak yerine , , , , ve tek bir arayüzde, diyelim ki arayüzünde birleştirebilirlerdi. Artık hem hem de sınıfları bu yeni arayüzünü uygulayabilirdi. Serializable Cloneable Iterable Collecton List RandomAccess IList ArrayList LinkedList IList Ancak rastgele erişimi desteklemediğinden, arayüzündeki yöntemleri uygulayabilir ve birisi onu çağırmaya çalıştığında atabilir. LinkedList RandomAccess UnsupportedOperationException Ancak bu, LinkedList sınıfını gerekli olmasa bile arayüzü içindeki yöntemleri uygulamaya "zorlayacağı" için SOLID İlkelerindeki Arayüz Ayrımı ilkesinin ihlali anlamına gelir. RandomAccess Bu nedenle, arayüzü ortak davranışa göre bölmek ve her sınıfın tek bir büyük arayüz yerine birçok arayüzü uygulamasına izin vermek daha iyidir. Bağımlılığı Ters Çevirme Prensibi Bağımlılığı Ters Çevirme İlkesi, üst seviyedeki sınıfların doğrudan alt seviyedeki sınıflara bağlı olmaması gerektiğini belirtir. Bu, iki seviye arasında sıkı bir bağlantıya neden olur. Bunun yerine alt sınıflar, üst düzey sınıfların bağlı olması gereken bir arayüz sağlamalıdır. Sınıflar yerine arayüzlere bağlı olun Örneğin yukarıda gördüğümüz örneğine devam edelim ve onu bazı ödeme seçenekleri ekleyecek şekilde geliştirelim. ve iki tür ödeme seçeneğimiz olduğunu varsayalım. Şimdi, sınıfında, , sepet değerini hesaplayacak ve sağlanan ödemeye göre ödemeyi başlatacak bir yöntem eklemek istiyoruz. yöntem. Cart DebitCard Paypal Cart placeOrder Bunu yapmak için yukarıdaki örneğinde, iki ödeme seçeneğini sınıfının içindeki alanlar olarak ekleyerek bağımlılığı ekleyebilirdik. Ancak bu, sınıfını ve sınıfıyla sıkı bir şekilde birleştirecektir. Cart Cart Cart DebitCard Paypal Bunun yerine bir arayüzü oluşturacağız ve hem hem de sınıflarının arayüzlerini uygulamasını sağlayacağız. Artık sınıfı bireysel ödeme türlerine değil, arayüzüne bağlı olacaktır. Bu, sınıfların gevşek bir şekilde bağlı kalmasını sağlar. Payment DebitCard Paypal Payment Cart Payment Aşağıdaki koda bakın. public interface Payment { void doPayment(double amount); } public class PaypalPayment implements Payment { @Override public void doPayment(double amount) { // logic to initiate paypal payment } } public class DebitCardPayment implements Payment { @Override public void doPayment(double amount) { // logic to initiate payment via debit card } } import java.util.ArrayList; import java.util.List; public class Cart { private List<Item> items; private Payment paymentOption; public Cart(Payment paymentOption) { this.items = new ArrayList<>(); this.paymentOption = paymentOption; } public void addToCart(Item item) { items.add(item); } public double calculateCartValue() { double value = 0.0; for(Item item: items) { value += item.getValue(); } return value; } public void placeOrder() { this.paymentOption.doPayment(calculateCartValue()); } } Nesne yönelimli programlama, GoF tasarım kalıpları ve düşük düzeyli tasarım röportajları hakkında daha fazla bilgi edinmek istiyorsanız yüksek puan alan kursuma göz atın Nesne Yönelimli Programlama + Java Tasarım Desenleri