Nesneye dayalı programlama ile geliştirilen yazılımlar oldukça karmaşık mimari tasarımlardan oluşurlar. Tecrübeli yazılımcılar, bu karmaşık yapılardaki tasarımları oluştururken ileride geliştirilebilme ihtimalini her zaman göz önünde bulundurarak esnek yapılar hazırlarlar. Bu esnek yapıların oluşturulmasında da bazı prensiplere uyulur. Bunlar, nesneye yönelik programlamanın temel prensipleridir. Yazılımın tasarlama aşamasında kullanılması önerilen tasarım prensipleri ve tasarım kalıpları aşağıdadır.
Tasarım kalıplarının ve genel tasarım prensiplerinin gerekliliği
Mükemmel yazılım ürünü sadece tasarımcının aklında bir görüntü olarak var olur. Bu aşamada yazılım saf ve hatasızdır. Ancak tasarım, yazılım (tasarım dökümanları ve kaynak kod) haline dönüşmeye başladıkça en baştaki hatasız yazılımda küçük problemler ortaya çıkmaya başlar. İlk problemler kolayca çözülebilir ancak program geliştirildikçe, modül sayısı çoğaldıkça ortaya çıkan problemlerin çözümü zorlaşır. Bu aşamada yapılması gereken bir satırlık değişiklikler birden fazla modülde değişikliğe yol açmaya başlar.
Sadece problemlerin çözülmesi değil, aynı zamanda değişiklik isteklerin de karşılanması zorlaşır. Bu durumda yapılan bir değişikliğin zincirleme değişiklikler yapılması durumunu ortaya çıkarması belki de en başta yapılan tasarımın değiştirilmesine, o zamana kadar yapılan tüm çalışmaların boşa gitmesine yol açabilir.
Yazılım geliştirme sürecinde oluşan değişiklik istekleri
Problemler sadece hataların fark edilmesi üzerine ortaya çıkmaz. İstekler belgesinin (requirements document) yazılım geliştirme sürecinde en fazla güncellenen belgeler arasında olduğu bilinmektedir. Dolayısıyla geliştirilmekte olan bir yazılıma zaman içinde eklentiler olmasını beklemek doğaldır. Orjinal tasarımın eklentilere (güncelleme ve genişlemelere) açık olması yazılım açısından önemli bir özelliktir. Bu sebepler göz önüne alındığında yazılımcıların ürünleri üzerinde istenilen değişikliklere zamanında cevap verebilmesi gerekmektedir.
Yazılımın başarısız olarak değerlendirilmesine yol açan faktörler - Genişlemeye kapalı olmak (Rigidity)
Yazılım üzerinde küçük bir eklentinin yapılması sırasında bile birden fazla sayıda problem çıkar. Çoğu değişiklik veya eklenti birçok modülde zincirleme değişikliklerin yapılmasını zorunlu kılar. Böyle bir yazılımda yapılacak değişiklikler için gereken süreyi hesaplamak zordur. Bazı durumlarda yapılması istenen değişikliğin yapılmamasına karar verilebilir. Bu problemin esas sebebi de birbirinden yeterince ayrıştırılmamış modüllerin oluşturulmasıdır.
- Kırılganlık (Fragility)
Kırılganlık, genişlemeye kapalı olma problemi ile iç içedir. Küçük bir değişiklik zincirleme değişiklik gereksinimlerini doğurur. Bu zincirleme değişikliklerinin gerekliliği yazılım geliştiricilerin yazılım üzerindeki kontrollerinin kaybolmasına yol açar.
- Taşınamazlık, tekrar kullanılamama (Immobility)
Yazılım ürünlerinin tekrar kullanılamayacak biçimde tasarlanması problemidir. Birbirlerine çok benzeyen modüllerin bile tekrar yazılması gerekir. Kopyala-yapıştır işlemi çok sık kullanılır. Bu problemin ortaya çıkmasındaki ana sebep hazırlanmış olan bir modülün aslında birden fazla küçük modüller şeklinde hazırlanmış olması gerekliliğidir.
- Akışkanlığın az olması (Viscosity)
Bir değişiklik isteğinin karşılanması için birden fazla çözüm yöntemi ortaya çıkabilir. Bazı yöntemler tasarımın, bazıları ise; önceden yazılmış olan kaynak kodun değişmesini önerir. İyi tasarlanmış yazılım ürünlerinde değişikliklerin orjinal tasarımı değiştirmeden yapılabilmesi daha kolaydır.
Başarılı yazılıma ulaşmak için uygulanması önerilen temel prensipler - Açık Kapalı Prensibi (Open Closed Principle)
Yazılmış bir modülün genişlemeye açık ama değişikliğe kapalı olması gerekliliğini vurgular. Yani yazılan kod üzerine bir eklenti yapılmak istendiğinde önceden yazılmış kodlarda değişiklik gerekliliği olmamalıdır. Gerekli eklentiler sadece eklenti için gereken yeni özelliğin kodlanması ile sonuçlanmalıdır.
- Liskov Ayrışabilme Prensibi (Liskov Substitution Principle)
"Türemiş sınıflar, türedikleri temel sınıfların yerini herhangi bir problem çıkmadan alabilmelidir." prensibidir. Yani temel sınıftan yaratılmış bir nesneyi kullanan bir fonksiyon, temel sınıf yerine bu temel sınıftan türemiş bir sınıftan yaratılmış bir nesneyi de aynı yerde kullanabilmelidir.
- Bağımlılığın Azaltılması Prensibi (Dependency Inversion Principle)
Kullanıcı ile sınıflar arasındaki ilişkinin olabildiğince soyutlanmış yapılar üzerinden yapılmasını önerir. Yani tasarımda ilişkilerin gerçek sınıflardan türemiş nesneler ile değil, ilgili arayüzler(interface) ve soyut sınıflar kullanılarak gerçeklenmesi gerekir.
- Tek Sorumluluk Prensibi (Single Responsibility Principle)
"Bir sınıf sadece tek bir sorumluluğu yerine getirmelidir ve yerine getirdiği sorumluluğu iyi yapmalıdır." prensibidir. Her sınıf sadece kendisi ile ilgili tek bir sorumluluğu yerine getirir. Her sınıfın sorumluluğu farklı olduğu zaman, değişmesi için tek bir sebep olur,o da ihtiyaçların değişmesidir. Sınıfların birden fazla sorumluluğunun olması bağımlılığın artmasına neden olur.
- Arayüz Ayırma Prensibi (Interface Segregation Principle)
Kullanılacak methodların ve özelliklerin gruplandırılarak her biri ayrı işlevi tanımlayan farklı arayüzlere bölünmesini savunur. Sonuç olarak, bir sınıfın kullanmadığı method ve özellikler programın içeriğine alınmamış olur.
Tasarım Kalıplarına Genel Bakış
Tasarım işlemleri sırasında kullanılan ve yukarıda anlatılan temel tasarım prensiplerini destekleyen daha özel yapılar da geliştirilmiştir. Bu yapılara Tasarım Kalıpları (Design Patterns) ismi verilmiştir. Nesneye dayalı programlamanın temel prensiplerine uyulması beraberinde birtakım tasarım sorunları getirir. Bu sorunların çözümü için bu tasarım kalıplarına başvurulur. Tasarım kalıpları genel olarak karşımıza tekrar tekrar çıkan problemlerin temeline bir çözüm sağlayan yöntemlerdir. Tasarım kalıplarının temel felsefesi tüm tasarım işlemlerinde kullanılan tekrar kullanılabilirlik mantığına dayanır.
Tasarım Kalıpları, genel anlamda üç temel problem bölümüne ayrılmıştır.
- Yaratılış Kalıpları (Creational Patterns): Nesnelerin nasıl yaratılacağı hakkında alternatifler sunar. Farklı durumlarda yaratılması gereken nesnelere karar verir.
- Yapısal Kalıplar (Structural Patterns): Nesneleri ve sınıfları birleştirerek karmaşık kullanıcı arayüzleri gibi daha geniş yapılar oluşturur.
- Davranışsal Kalıplar (Behavioral Patterns): Nesne grupları arasındaki iletişimin tanımlanmasında kullanılır ve daha karmaşık programlarda akış kontrolünü sağlar. Nesnelere işlevsel sorumluluklarını atar.