Sayfalar

Nesne Yönelimli Yazılımda Dizayn Prensipleri

                                                                                                                                                    
    Uzunca bir süre önce yazdığım bir yazıyı güncelliğini koruduğunu düşündüğüm için aynen yayınlıyorum.

Esen Kalın .
  
     Sistem içerisinde  detaylara girildikçe olaylar gittikçe karmaşık bir hal almaya başlar ve kolaylıkla kontrolü kaybedilebilir. Walker Royce’un ”The Rational Edge”'de yayınlanan bir  makalesinde yer alan şu istatistikler ilginçtir :
  • Yazılım projelerinde geliştirme için harcanan her bir dolarlık maliyete karşılık bakıma iki dolar harcanmaktadır
  • Geliştirme eforunun sadece %15’i doğrudan programlamaya harcanmaktadır

     Bu istatistikler bize bakıma , geliştirmenin iki katı süre harcadığımızı söylemektedir.Açıkça bakım maliyetlerini düşürmek için bir çözüme ihtiyaç vardır.Nesne yönelimli programlama bu maliyeti azaltsa da ne yazıkki sıfırlayamamaktadır.Şu örnek olayı daha iyi kavramamızı sağlıyacaktır :

A ve B sınıfları tarafından yeniden kullanılan bir R sınıfı düşünelim.Eğer A sınıfı ; R sınıfının davranışlarında bir yenilik  yada değişiklik ihtiyacı duyarsa bu aynı zamanda B sınıfını da etkileyecektir.Bu durumdayken eğer B bu yeni davranışa ihtiyaç duymazsa ne olur ? Ya da değişiklik B için olumsuz bir durum yaratıyorsa ? Bu durum yeniden kullanımın olduğu ancak bakımın sorun olduğu bir örnek teşkil eder.

           Nesne yönelimli programlamanın en önemli avantajı sağladığı yeniden kullanım olanaklarıdır.Ancak sırf bu amaç uğruna körü körüne başlanılan projeler bir süre sonra rahatlıkla kördüğüm halini alabilir.Yukarıda bahsedilen problemin çözümü için pek çok yol öne sürülebilir , ancak kuşkusuz en doğru yaklaşım bunun bir dizayn sorunu olduğunu kabul etmektir.Doğru prensipler doğrultusunda geliştirilen dizaynlar bu tip problemleri en aza indirirler.

Sınıf Dizayn Prensipleri


   Dizayn prensiplerinin İlk bölümünde sınıf dizaynı ile ilgili prensipler ele alınmıştır.Ancak bu prensipler modul gibi diğer bileşenlere de uygulanabilir prensipler olarak algılanmalıdır.

Açık Kapalıdır Prensibi (The Open Closed Principle - OCP)

   Bu prensip kısaca “bir modul genişlemeye açık ancak değişikliğe kapalı olmalıdır” şeklinde özetlenebilir.Belki de tüm prensiplerin en önemlisi olan bu prensip “Bertrand Meyer”’in çalışmalarına dayanmaktadır.Prensip kısaca kodlarımızı değişikliğe gerek kalmadan genişletebilmemiz gerektiğini söyler.Bir başka deyişle modulümüzün yeteneklerini var olan kodları değiştirmeden geliştirebilmeliyiz.Bu ilk başta biraz karışık görünse de bu prensibi yerine getirebilmek için çeşitli tekniklere sahibiz.Bu tekniklerin tümü de aslında nesne yönelimli programlamanın temel prensiplerinden soyutlama’ya (abstraction) dayanmaktadır.Örneğin elimizde bulunan modem sınıfımız “Hayes” ve “Ernie” tipi modemler için kodlanmış olsun ve methodlarına dışarıdan belirtilen modem tipine göre davransın.

Bu dizaynda sisteme eklenecek her yeni modem tipi var olan sınıfın değişikliğini gerektirecektir.Ancak modem sınıfını soyut hale getirerek bu sorunu kolaylıkla aşabiliriz.Bu durumda  sisteme eklenecek her yeni modem tipi , modem soyut sınıfını gerçekleştirecek ve sadece kendi kodunu içerecektir.Ancak bu durum mevcut sınıfların hiçbirine dokunulmadan yerine getirilebilecektir.



 OCP prensibi mimari olarak bize değiştirmeden geliştirebileceğimiz moduller kurma olanağı sağlar.Bu erişilmesi oldukça güç bir hedeftir.

Liskov’un Yerine Koyma Prensibi(The Liskov Substitution Principle - LSP )


    Bu prensip Barbar Liskov tarafından ortaya konmuştur.Kısaca alt sınıfların üst sınıflara uygun olması gerektiğini söyler.İlk bakışta nesne yönelimli yazılımın bunu doğası gereği sağladığı düşünülse de durum biraz daha karmaşıktır.Bu prensip aynı zamanda temel sınıfın gösterdiği davranışların alt sınıflar tarafından da aynen yerine getirilmesi gerektiğini söyler.Durum bir örnekle daha kolay anlaşılabilecektir.Örnek olarak elips ve daire şekillerini ele alalım.Daire aslında elipsin bir cinsidir , tek farkı dairede ; elipsin iki merkezinin tek noktada buluşmasıdır.




Durum sınıfların özellikleri ve davranışları devreye girdiğinde biraz daha karışık bir hal alacaktır.Ekli şeklide görüldüğü üzere elipsin üç niteliği vardır.İlk ikisi merkezleri , üçüncü nitelik ise eksenin boyunu  gösterir.Eğer daire elipsten türerse bu üç  niteliğe de sahip olmak zorundadır.Ancak daire sadece merkez noktası ve çapa ihtiyaç duyar.



Elips’in merkezleri belirleyen setFoci metodu muhtemelen şu şekilde olacaktır :

Public void setFoci(Point a , Point b) {
 focusA = a;
 focusB = b;
}

Daire ise tek merkeze sahip olağından bu methodu şu şekilde değiştirebilir :

Public void setFoci(Point a , Point b) {
 focusA = a;
 focusB = a;
}

    Bu durumda daire fazladan gereksiz bir niteliğe sahip olsa da her iki sınıfta görevlerini tam olarak yerine getirebilecektir.Sistem sorunsuz gibi görülebilir.Ancak bu durum sadece elips ve daireden oluşan bir sistem söz konusu olsa geçerli olurdu.Ne yazıkki bu sınıflar başka sınıflar ile bir arada çalışmak zorundadırlar ve sundukları her davranış ya da nitelik dış dünya ile bir kontrat niteliği taşır.Ve bu kontrat hiç bir şekilde bozulmamalıdır.Örneğin dışarıda şu şekilde bir method bulunduğunu var sayalım.

void f(Ellipse e){
   Point a = new Point(1,0);
   Point b = new Point(-1,0);
    e.setFoci(a,b);
   e.setMajorAxis(3);
   assert (e.getFocusA() == a);
   assert (e.getFocusB() == b);
   assert (e.getMajorAxis() == 3);

}

Bu durumda method elipse ile çalıştığını varsayıyordur ve elips sınıfı kullanıldığı sürece bir hata üretmez.Ancak bu methoda elips yerine daire gönderildiğinde hataya yol açacaktır.Elips sınıfı setFoci() metoduyla açıkca bir kontrat imzalamıştır ve bu kontrat gönderilen belirtilen noktaların sınıfın iki noktası olarak belirleneceğini söyler.Ancak daire sınıfı bu kontratı açıkca ihlal etmektedir.
  Neyazıkki LSP ihlalleri sistem içerisinde oldukça geç farkedilir .Örneğin elips / daire örneğinde olduğu gibi ilgili  method daire ile deneninceye kadar bu durum ortaya çıkmaz.Durumun telaffisi eğer orjinal sınıfların değişikliği de zor bir durumda iken farkedilirse oldukça istenmeyen şekillerde çözülür.Sıklıkla rastlanılan bir şekilde if/else – switch/case gibi ifadelerle durum çözülmeye çalışılır.

void f(Ellipse e){
  if(typeID(e) == typeID(Ellipse) ){
     Point a = new Point(1,0);
     Point b = new Point(-1,0);
     e.setFoci(a,b);
     e.setMajorAxis(3);
     assert (e.getFocusA() == a);
     assert (e.getFocusB() == b);
     assert (e.getMajorAxis() == 3);
  }else{
     Point a = new Point(1,0);
     Point b = new Point(-1,0);
     e.setFoci(a,b);
     e.setMajorAxis(3);
     assert (e.getFocusA() == a);
     assert (e.getMajorAxis() == 3);
  }

}

  Ancak bu gibi çözümler de OCP genellikle prensibini  ihlal ederler.Yukarıdaki durumda yeni bir elips sınıfı türetildiğinde f fonksiyonunun da doğruluğu kontrol edilmeli yada değişikliğe uğratılmalıdır.
  Bu gibi ihlallerin ortaya çıkmaması için özellikle temel sınıfların
  • Kontratları iyice kontrol edilip alt sınıfların uyamıyacağı kontratları içermemesi sağlanmalı
  • Alt sınıfların üst sınıf kontratlarına harfiyen uyması sağlanmalı
  • Sistemde bir başka temel sınıfın varlığının gerekliliği iyice sorgulamalıdır.



Bağımlılığın Çevrimi Prensibi ( The Dependency Inversion Principle - DIP )


  Soyut eşleme (abstract coupling) olarak da adlandırılan bu prensip kısaca bileşenler arası eşlemelerin (ya da bağımlılıkların) somut sınıflar üzerinden değil soyut sınıflar üzerinden yapılması gerektiğini söyler.Eğer OCP prensibini nesne yönelimli mimarinin hedefi sayıcak olursak DIP prensibini bunu sağlıyacak  temel mekanizma olarak adlandırabiliriz. COM,CORBA,EJB gibi bileşen teknolojileri gücünü bu prensipten alırlar.

  Procedürel dizaynlar bir parça bağımlılık yapısı gösterirler.Üst seviye moduller uygulamanın üst seviye politikalarına bağımlıdırlar.Bu politikalar uygulamanın alt seviye modulleri , onların implemantasyonu ile ilgilenmezler.Ancak procedürel yapılar bunun tam tersi yukarıdan aşağıya doğru bir bağımlılık gösterir.(Örneğin C dilinde hazırlanmış bir grafik kütüphanesi , alt seviyelerde grafik objeleri içeren detay kütüphaneler , sistem kütüphaneleri vb. bir yapı düşünün).


  Nesne yönelimli mimariler ise bambaşka bir bağımlılık yapısı gösterirler.Üst seviye moduller soyut sınıflar ile politikaları içerirken alt seviyelere gittikçe bu politikaların gerçekleştirimleri elde edilir.Bağımlılık tam tersine dönmüş gibidir.


  
   DIP bize genişleyebilir ve esnek bir mimari kurma olanağı sağlar.Geliştirmenin ilk safhalarında gereksiz hatta külfet gibi görünen bu prensip ilerleyen yaşam evrelerinde hayat kurtarıcı olabilir.
  Örneğin C dilinde yer alan string.h kütüphanesi somut fakat esnek olamayan bir yapı sergiler.ANSI standarlarında oldukça iyi çalışan kütüphane projenin örneğin UNICODE standardına dönmek istemesiyle oldukça baş belası bir durum oluşur.
   DIP presinbinin uygulanmasında karşımıza çıkacak en büyük sorun nesne oluşturulmasıdır.Yaratılan soyut sınıflar üzerinden nesneler yaratılamaz.Bu durumun en güzel çözümü GOF paternleri arasında yer alan “Abstract Factory” paterninin uygulanması ile aşılabilir.

Arabirimin Ayrılması Prensibi ( The Interface Segregation Principle – ISP  )


  Bu prensip bize genel içerikli büyük arayüzler yerine , istemcilere yönelik pek çok arayüzün tercih edilmesini tavsiye eder.Bu prensibin özü oldukça basittir.Eğer farklı farklı istemciler için genel bir arayüz tasarlarsanız bu istemcilerin herhangi biri için gereken değişiklik pek çok yeri etkileyecektir.



    Yukarıdaki örnekte servis üç istemci için methodlar barındırmaktadır.Örneğin istemci A methodları üzerinde yapılacak bir değişiklik , hiçbir ilgileri bulunmadığı halde diğer iki sınıfıda ilgilendirecek ; bu iki sınıfında yeniden gözden geçirilimesi gerekecektir.
    Servisin direk parçalanması söz konusu olabileceği gibi aşağıdaki şekilde olduğu gibi servisin farklı arayüzlere bölünmesi de kullanışlı bir tekniktir.


  ISP her istemci için bir arabirim yapılmasını önermemektedir.Bu prensipten bu çıkarılmamalıdır.Aksine bu durum oldukça sağlıksız ve ağır bir sistem yaratır.Bunun yerine istemciler kategorilerine ayrılmalı ve arabirimler bu kategoriler doğrultusunda yaratılmalıdır.

Birleşik Yeniden Kullanım Prensibi ( The Composite Reuse Principle - CRP )

   Bu prensip diğer prensiplere göre daha tartışmalı ve kesin olmayan bir prensiptir.Alt sınıf methodlarının gurplar halinde birbirine benzeştiği durumlar için uygun bir taktik sayılabilir.Bu prensibin temel fikri nesne yönelimli programlamada polimorfizm’e kalıtım  yerine birleştirme ile de gidilebileceğidir. Polimorfizm alt sınıfların ana sınıftan farklı özellikler gösterebileceğini belirten deyimdir.
 
abstract class Animal {
  abstract void talk();
}
 
Class Dog extends Animal {
  public void talk() {
    System.out.println("Scooby dooby Doo");
  }
}
 
Class Cat extends Animal {
  public void talk() {
    System.out.println("Meow....");
  }
}

   Örnek kodda “Animal “ ana sınıfının alt sınıfları “Dog” ve “Cat” ana sınıftan farklı konuşma davranışları sergilemektedir , başka bir deyişle polimorfik yapıdadırlar.Yine bu örnekte kalıtım özelliği ile kolayca polmorifzm’e ulaşılmıştır.Ancak pratikte bazı durumlarda bu özellik esnek olmıyan ve etkisiz yapılar kurulmasına yol açabilir.
  Örneğin bir bordro sistemi tasarladığınızı varsayalım.Yılbaşına yakın bir zamanda göreviniz personelin ikramiyelerini hesaplıyacak bir sistem hazırlamak olsun.Şirketin üç tip personeli kalıcı ; geçici ve yarı-zamanlı olsun.
  

  
  Muhtemelen ilk dizaynınız yukarıdaki gibi olacaktır.Belli bir zaman sonra müdür yarı zamanlı personelin prim hesaplamasını değiştirmek istesin.Bu durumda tek yapacağınız yarı zamanlı personel sınıfında primHesapla  methodunu basitçe üzerini ezerek değiştirmek olacaktır.
  Bir süre sonra müdür danışman olarak çalışanların prim hesaplama şeklini  yarı zamanlı personel gibi değiştirmek istesin.Bu durumda :
  • Yarı zamanlı personelin bir alt sınıfı olarak danışman sınıfı yaratabilirsiniz.Ancak bu sınıf hiyerarşisinde bir sorun olacaktır.Bu dizayn olmadığı halde danışmanların ; yarı zamanlı personel olduğunu ilan eder.Örneğin ileride danışmanlara ; kalıcı personel oranında prim verilmek istendiğinde sınıf hiyerarşisi bunu zorlaştırır.
  • Danışman sınıfını direk çalışan sınıfının alt sınıfı olarak yaratabilir ve primHesapla metodunu yarı zamanlı personelden kopyalabilirsiniz.Ancak bu yeniden kullanım hedefimizi zedeler ve aynı kodun çift olarak yaratılmasına yol açar.
  Burada hata çalışan sınıfının tasarlanması sırasında ortaya çıkmıştır.Prim hesaplama metodunun sık değişmeyen ve herkeze uygun bir method olduğu düşünülmüştür.Ancak gerçekte bu method her çalışan tipine göre değişiklik gösterebilir ve oldukça sık değişebilir bir method’dur.Kalıtım  yalnızca alt sınıfların üst sınıfa tam olarak uygun olacağı durumlarda kullanışlı bir yöntemdir.Ana sınıfın methodlarının sürekli olarak üzerinin ezilmesi kalıtım ilişkisini anlamsız kılar.Bu durumda üst sınıfta ; alt sınıflar gibi özelleşmiş bir sınıf olmaktan öte gidemez.

   Bu durum soyut bir prim hesaplama sınıfı yaratılarak zarif bir şekilde halledilebilir.



  Bu çözümde PrimHesalayıcı tüm çalışan tipleri için birleştirilmiş (composite) bir yapı sunmaktadır ve bu yapı oldukça esnek bir polimorfik yapı sergilemektedir.
  Bu örnek CRP seçimi için oldukça uygun bir örnektir.Bu örnekte kalıtım sizi her alt sınıfta yeni bir geliştirme yapmaya zorlar.
 

En Az Bilgi Prensibi ( The Principle of Least Knowledge - PLK)


   Bu prensip bize kullandığımız bir nesnenin içinden direk olarak başka nesnelere ulaşmamamızı salık verir.Bunun yerine sadece bildiğimiz nesne bize tüm methodları sunmalı ; iç yapısındaki başka nesnelerle ilgilinememiz gereksiz kılınmalıdır. Aksi takdirde nesnenin karmaşık yapısına kendimizi bağlamış oluruz.Aynı zamanda nesne de bir başka nesneyi dışarıya sunduğu için bir sözleşme imzalamış olur . Ekli örnekte PLK prensibine uymayan bir method ve uygun hali görülmektedir :
Hatalı
Doğru
public void test (AClass o){
  AnotherClass ao = o.get();
  ao.doSomething();
}

public void test (AClass o){
  o.doSomething();
}


  PLK’nın ana dezavantajı sınıflara pek çok ek metodun yüklenmesidir.Örnekte PLK prensibine uymak için sınıf başka bir sınıfın metodunu kendine almak zorunda kalmıştır.Bu açıkça çok yüklü ve hantal sınıfların doğmasına yol açabilir.Ancak bu durum sınıfın dışarıya somut sınıf referansları değil de arabirim referasları sunmasıyla halledilebilir.Yani kısaca bir sınıftan dışarıya verilen referaslar soyut sınıflar üzerinden olmalıdır diyebiliriz.Bu şekilde sınıfımızı bir başka somut sınıf ile eşlemeden DIP ve OCP prensiplerine de uygun olarak çözümler geliştirebiliriz.Java örneğine bakıldığında bu şekilde hazırlanmış onlarca sınıf görülebilir.

Tek Sorumluluk Prensibi ( The Single Responsibilty Principle - SRP)


  Bu prensip aynı zamanda uyumluluk (cohesion) prensibi olarak da tanınır.Bu prensibi kısaca “bir sınıfın değiştirilmesi için birden fazla sebep olmamalı “  şeklinde özetleyebiliriz.Bu aynı zamanda bir sınıf birden fazla sorumluluk taşımamalı şeklinde de özetlenebilir.Çünki her sorumluluk aynı zamanda değişimin ana sebebidir.İstekler değiştiğinde o isteği yerine getiren sınıflar değiştirilerek istek yerine getirilir.Eğer bir sınıf birden fazla sorumluluk taşıyorsa , o sınıfın değişmesi için birden fazla sebep var demektir.



   Örneğin alan hesaplama ve çizim methodlarını içeren bir dikdörtgen sınıfını ele alalım.Grafik ve geometri hesap uygulaması olmak üzere İki değişik uygulama bu sınıfı kullansın.Bu durumda dikdörtgen sınıfı iki sorumluluk yüklenmiş olur .Birincisi alan hesabı yapmak , ikincisi ise dikdörtgenin grafik çizimini yapmak.Halbuki bu uygulamayı kullanan uygulamalar bu fonksiyonalitenin sadece birine ihtiyaç duymaktadırlar.Bu durum iki soruna yol açmaktadır :
  • Geometri hesap uygulaması hiç ihtiyacı olmadığı halde çalışma sırasında grafik paketine ihtiyaç duyacaktır.
  • İkincisi grafik uygulaması yüzünden dikdörtgen sınıfında olabilecek herhangi bir değişiklik geometri hesap uygulamasınında yeniden derlenmesi , sınanması vb. gereksiz yükler getirecektir.
  Nesne yönelimli dizayn içinde pek çok şekilde bu sorumluluklar birbirinden ayrılabilir.Ekli şekilde bir çözüm örneği sunulmuştur.





 

Paket Dizayn Prensipleri


  Paket dizayn prensipleri sistemin yapılandırılmasında sistem mimarının göz önünde bulundurması gereken temel prensipleridir.Sınıf bazında izlenecek prensipler algılandıktan sonra bunların nasıl organize edileceği , nasıl kategorize edileceği vb. sorunlar bu prensipler yardımıyla çözülürler.

Paket Uyumluluk Prensipleri ( Package Cohesion Principles )


  Bu prenipler sistem üzerinde yapılacak değişikliklerin en az etkiyi göstermesi için tasarlanmıştır.REP ve CRP prensipleri paketlere bağımlı olan kullanıcıların hayatını kolylaştırırken CCP prensibi daha çok değişiklik yapan geliştiricilerin hayatını kolaylaştırır.Bunu yanı sıra CCP paket içeriğini daha çok büyütürken CRP tam tersi bir işlev görür.Mimarinin ilk başlarında daha çok CCP uygulandığı görülürken ilerleyen safhalarda REP ve CRP prensipleri devreye girer.

Sürümün Yeniden Kullanım Eşitliği Prensibi ( The Release Reuse Equivalence Principle  - REP)

  
   Bu prensip bize yeniden kullanım için tasarlanan sınıfların her sürümde geriye doğru uyumlu olması gerektiğini söyler. Bir sınıf eğer yeniden kullanım için tasarlanmış ise doğal olarak pek çok kullananı vardır .Yeniden kullanım için tasarlanan sınıfların üzerinde yapılacak değişiklikler ; bağımlılıklardan dolayı pek çok başka sınıfı etkileyecektir.Dolayısıyla yeniden kullanılabilir olarak tasarlanan sınıflar doğru bir sürüm stratejisiyle geriye doğru uyum göstererek değişebilirler.Sık değişiklik gösteren sınıfların müşterileri bakım maliyetini karşılayamıyacaklarından kullanımdan vazgeçebilirler.
    Sistem de tasarlanan sınıflar her biri bir pakete ait olmak zorundadır.Yeniden kullanım için tasarlanan sınıflarda bu kurala uymak zorundadır.Eğer bir sınıfa bağımlılık var ise aynı zamanda pakete de bağımlılık vardır.Dolayısıyla yeniden kullanılabilir olarak tasarlanan sınıfın bulunduğu paket ve paketin diğer içeriği de buna uygun tasarlanmalıdır.

Genel Toplanma Prensibi ( The Common Closure Principle - CCP)

 
  Beraber değişen sınıflar aynı pakete konulmalıdır.Aksi takdirde paket bağımlılığından dolayı ilerleyen sürümlerde gereksiz sınama ,  derleme vb. yükler  ortaya çıkar.Bu prensip aynı zamanda paketlerin bağımsız sınıflardan daha çok yeniden kullanım parçası olduğu fikrine dayanır.Eğer daha fazla paket değişiklik gösterirse yazılımın daha büyük parçaları bu değişiklikten etkilenecektir.Bu etkiyi minimal tutmak için en iyi çare beraber değişen sınıfların aynı pakette toplanmasıdır.

Genel Yeniden Kullanım Prensibi ( The Common Reuse Principle - CRP)


  Beraber değişmeyen sınıflar aynı pakete konmamalıdır.Bir paket içinde bulunan sınıflar beraber kullanılıyorlar demektir.Bu her değişiklik ve yeni sürümde gereksiz olduğu halde değişmeyen sınıflarında yeniden gözden geçirilmesi , sınanması ve derlenmesi demektir.Bunun değişmeyen sınıflara bağımlı paketler üzerinde de aynı işlemlerin yapılması demek olduğu düşünülürse artan yük açıkça görülür.
  Örneğin bu kurala uymayan işletim sistemlerinde ki değişiklikler sık sık başımızı ağrıtır.İşletim sisteminin yeni bir sürümü çıktığında biz değişikliğin tamamını istesek de istemesek de getirdiği yükü karşılamak zorunda kalırız.





Paket Eşleme Prensipleri (Package Coupling Principles )


   Sonraki üç prensip paket eşleme prensipleri  olarak anılırlar ve paketler arasındaki ilişkilerin nasıl kurulması gerektiği üzerinde dururlar.

Doğrusal Bağımlılık Prensibi ( The Acyclic Dependency Principle - ADP)

  
 Bu prensipte, paketler arasındaki ilişki yapısında çevrimlerin bulunmamasıdır. Bir başka deyişle paketler arası ilişkiler tek yönlü ve doğrusal olmalıdır.




 Şekildeki  yapı oldukça modüler ve kendi içinde uyumlu olan bir mimaridir. Paketler arasındaki tüm ilişkiler tek yönlüdür.

Yukarıdaki şekilde görülen ”Comm Error” paketinde bir değişiklik yapıldığını varsayalım. Bu durumda “Protocol” “Comm” ve “GUI” paketleri dışında sistemin geri kalan kısmı bu değişiklikten etkilenmemiş olur. Ancak bir süre sonra uygulama da “Comm Error” paketinde çıkan hataların bir pencerede kullanıcıya gösterilmek istendiğini var sayalım.Bu değişiklik sonucu “Comm Error” paketinden sınıflar “GUI” paketinden ilgili sınıfları kullanarak bir pencere açıp hata mesajı versin.




    Şekilde de görüldüğü gibi bu mimaride bir tasarım sorunu bulunmaktadır. Buna göre örneğin “Protokol” paketinde yapılacak bir değişiklik bağımlılıklardan dolayı “GUI”,”Comm”,”Modem Control” ve “Comm Error” paketlerini de etkileyecektir. Bu bir döngüdür ve istenmeyen bir durum oluşturur. Bu durum hem değişikliklerden masum paketlerin de etkilenmesine yol açacak hem de değişikliğin daha yavaş olmasına yol açacaktır.(Örneğin derleme performansı düşecektir , test süresi uzayacaktır vb)

Döngünün kırılması :

Bu şekilde oluşan döngüleri kırmak için 2 yol vardır:

1.   Yeni bir paketin yaratılması. Bu durumda hem “GUI” paketinde bulunan “Comm Error” paketi için gerekli olan sınıflar ayrı bir pakete taşınmalıdır. Bu çözümün bir dezavantajı sistemde çok fazla paketin oluşabileceğidir.


2.   Bağımlılığın çevrimi prensibini (DIP) uygulayarak da döngüler kırılabilir. Aşağıdaki şekilde ilk durum iki paket arasında oluşan bir döngüyü ve bu döngüye yol açan sınıfları göstermektedir. Kısaca Y sınıfı üzerinden B sınıfına olan bağımlılık kaldırılmış , bunun yerine B sınıfının Y sınıfı için gerekli parçaları bir “BY” arabirimine konarak , Y sınıfının bağımlılığı bu ara birime kaydırılmıştır. B sınıfı ise bu arabirimi geliştirmek zorunda olduğundan bağımlılık tersine dönmüştür.





Durağan Bağımlılıklar Prensibi ( The Stable Dependencies Principle - SDP)


   Durağanlık yazılımda , değişiklik sıklığı ile ilgili bir kavramdır.Bir paket ne kadar az değişiklik görüyorsa o kadar durağandır demektir.Bu durağanlığı sağlıyan unsurlar içeriğin karmaşıklığı , büyüklüğü , sağlam yapılmış olması vb. sebepler olabilir .Ancak nesne yönelimli dizayn içerisinde paketin durağan olmasını gerektiren en önemli unsur pakete olan bağımlılık sayısıdır.Bir başka deyişle pakete ne kadar bağımlılık var ise paket o kadar durağan olmalıdır.



    Yukarıdaki örnekte görülen X paketine diğer üç paketten bağımlılık vardır.X paketinde yapılacak her değişiklik diğer paketleri de etkileyecektir.Bu yüzden X paketi durağan olmak zorundadır.Bu bağımlılıklardan dolayı X paketi diğer paketlerden sorumlu olarak da anılır.Aynı zamanda X paketinin başka bir yere bağımlılığı olmadığı ve başka yerlerden etkilenmiyeceği için bağımsız olarak da adlandırılır.

   Üstteki şekilde yer alan Y paketi bir önceki örneğin aksine durağan olmıyan bir görüntü sergilemektedir.Bu durumda Y paketi diğer paketlerden sorumlu olmıyan ve bağımlı olarak adlandırılır.
  SDP prensibi bize tüm yazılımın durağan olması gerektiğini söylememektedir.Aksine nesne yönelimli dizayn oldukça esnek ve değişikliklere açık olmalıdır.Ancak bu prensipten anlamamız gereken sistem içinde bağımlılığın fazla olduğu paketleri durağan ilan etmemiz ve öyle olması içinde gayret sarfetmemiz gerektiğidir.Bu durum dizayn sırasında durağan paketlerin dikkatli seçilmesi ve iyi dizayn edilmesini gerektirir.Aynı zamanda geliştirme esnasında da bu paketlere öncelik verilmesi ve bağımlı paketlerin öncesinde geliştirmenin de değişiklik gerektirmiyecek şekilde tamamlanması gerekir.


Durağan Soyutluk Prensibi ( Stable Abstractions Principle - SAP)

  
    Bu prensip bir önceki SDP prensibi ile yakın ilişkilidir.Kısaca durağan paketlerin soyut yapıda olmasını söyler.Bu prensibi OCP ve DIP prensiplerinin paket boyutunda uygulanması olarak adlandırabiliriz.
  Yukarıdaki örnekte durağan  bir paketin içeriğinin soyut yapılarak prensibin uygulanışı gösterilmiştir.Durağan paketin gerçekleştirimi bir başka pakette  yapılarak dizayn esnek bir hale getirilmiştir.



Diğer Dizayn Prensipleri

Kapsülleme Prensibi ( The Encapsulation Principle - EP )


   Nesne yönelimli yazılımda kapsulleme prensibi sınıfların dışarıya gereği kadar bilgi sunmasını , iç yapılarını dışarıya yansıtmamalarını söyler.Bu prensibin paketlere yansıtılmış hali bir paketin içindeki detayların dışa kapalı kalmasıdır. Buna göre bir paketin içindeki sınıflar ne kadar dışarıya kapalı ise kapsulleme o kadar fazladır.Bu prensibe göre paketin iç yapısının dışında dış kullanımda gerekli olmıyacak sınıflar dışarıya kapalı tutulmalıdır.


Sınırlı Boyut Prensibi ( The Limited Size Principle - LSP)


   Bu prensibe göre bir paketin içindeki alt paket ve üst seviye sınıf sayısı belli bir boyutu aşmamalıdır. Üst seviye sınıf kavramı yazılım içinde mimari açıdan önemli sayılan sınıfları ifade eder.Bir sınıfa uygulama içinden bağımlılık ne kadar fazla ise o sınıfın önem derecesi o kadar artacaktır.
   Paketlerin boyunun gereğinden fazla tutulması paketin sorumluluğunun artmasına ve bakımının gittikçe zorlaşmasına yol açacaktır.Paket üzerinde yapılan değişiklikler de buna paralel daha çok yeri etkileyecektir.İyi projelerde paket içeriğindeki alt paket ya da üst seviye sınıf sayısının 10-15 ‘i aşmamasına özen göstermek gerekir.


Kaynakça

 
  • Booch, G., Object Oriented Design with Applications, Redwood City, CA: Benjamin/Cummings, 1991.
  • Robert C. Martin ,”Design Principles and Design Patterns” , Object Mentor Inc.
  • Samudra Gupta , “Fine Tuning Abstraction” , http://javaboutique.internet.com
  • James Rumbaugh , “Object Oriented Modelling and Design “ , Prentice Hall-1991
  • Chidamber, S. R. & Kemerer, C. F., “A Metrics Suite for Object Oriented Design”, IEEE Transactions on Software Engineering, Vol. 20, #6, June 1994.
  • Martin Fowler , “Refactoring :Improving the design of existing code” , 1999

Yazılım Mimarisi (Software Arhitecture)


   Yazılım geliştirme sürecinin ilk adımlarında problem analizi yapılarak  sistemde nelerin yapılması gerektiği ortaya konur. İhtiyaçların nasıl karşılanacağı , nasıl çözümler geliştirileceği ise dizayn disiplinin konusudur. Dizayn geliştirme boyunca çözümler için alınan kararlar öncelikle üst seviyede alınır ve giderek detaylandırılır.
  
   Yazılım mimarisi ; dizayn disiplininin ilk aşamalarında ortaya çıkar.Yazılım Mimarisi deneyimli yazılımcılar tarafından kolaylıkla anlaşılabilse de , tarifi oldukça zor bir kavramdır.Yazılım dizaynı ile yazılım mimarisi sıklıkla birbirine karıştırılmaktadır.Dizayn sistemin tüm ayrıntılarını içerdiği halde mimari bize sistemin ne olduğunu hakkında bilgi verir.Başka bir deyişle mimari ; dizaynın belli özellikleri öne çıkarılmış bir alt kümesidir.
  
   Ancak bu tanımdan mimarinin , dizayndan sonra hazırlandığı gibi bir sonuca ulaşılmamalıdır.Aksine mimari öncelikle hazırlanan bir kavramdır.Dizayn daha sonra mimaride belirtilen parçaların detaylandırılmasını kapsar.

  Yazılım Mimarisi “ sistemi tanımlayan en üst seviye kavramlar”  olarak nitelendirilebilir.
Yazılım mimarisi şunları içerir :
  • Sistemin organizasyonu hakkında önemli kararlar
  • Sistemin önemli yapısal elementleri ve bunların arayüzleri ile birbirleri arasındaki etkileşim
  • Sistemin önemli yapısal ve davranışal elemanlarının altsistem’lere dağılımı

  Yazılım mimarisi yapı ve davranışların yanı sıra kullanılabilirlik , fonksiyonalite , performans , esnekllik , yeniden kullanım , anlaşılırlık , ekonomik ve teknolojik kısıtlar gibi özellikleri de yansıtır.
Tüm bu özellikleri ile “Yazılım Mimarisi” sistemin anayasasıdır.Tüm yazılım geliştirme sürecinin merkezinde durur ve her türlü faliyete kılavuzluk eder.

   Yazılım mimarisi pek çok sebepten dolayı önemlidir :
  • Sistemin karmaşıklığını yönetmek ve bütünlüğünü korumak için , kontrol edilebilir bir yapı sunar

   Karmaşık sistemler kendini oluşturan parçaların bütününden daha fazla şey ifade ederler.Böyle sistemler kendini oluşturan parçaların nasıl bir araya geldiğini ve çalıştıklarını düzenledikleri gibi ,  gerektiğinde sistemin nasıl büyüyeceğini de tariflemelidir.Mimarisi tarif edilmemiş sistemde yapılacak her türlü değişiklik tam bir kaos olacaktır.Mimari ortak bir referans ve sözlük sunarak sistemin anlaşılırlığını ve iletişimi iyileştirir.

   Bundan başka mimari tüm ana parçaları belirlediğinden bundan sonraki tüm çalışmalar için bir adresleme sağlar.Hangi parçanın nerede ele alınacağı , hangi kavramın nereye oturtulacağı mimari ile belirlenir.Tüm parçaların görevleri ve kapsamı belirlendiğinden kaos ortadan kalkar.

  • Yeniden kullanımı arttıran kullanışlı bir yapı sunar

   Sistem bileşenlerinin  ve bunların arabirimlerinin mimaride tariflenmesi ; bileşenlerin   hem sistem içinde hem de başka sistemler içerisinde yeniden kullanımını önemli ölçüde arttırır.Sistem içinde ortak bileşenlerin adresleri belirlendiğinden kullanımları artar .Örneğin aynı çözümün sistem içerisinde birden fazla geliştirilmesi gibi problemlerin önüne geçilmiş olur.Dış sistemler de (örneğin geliştirilen diğer projeler ) ise kendi çözümleri içerisinde bu bileşenleri kullanabilirler.

   Daha geniş ölçekte  mimarinin kendisi de  farklı çözümlerde yeniden kullanım olanağı bulabilecektir.

  • Proje yönetimi için temel teşkil eder
     
   Projenin planlama ve kaynak yönetimi organizasyonu ana bileşenler etrafında oluşturulurlar.Temel yapısal kararlar mimari takımı tarafından bütünlük içerisinde yaratılırlar.Geliştirme aktiviteleri küçük takımlar halinde bileşenler etrafında yapılırlar.

       Yazılım geliştirme süreci oldukça karmaşık ve  zor bir süreçtir.Bu süreçte dağılmamanızı sağlıyacak şey , sürecin merkezinde duran şey yazılımınızın mimarisi olmalıdır..

Altsistemler (Subsystems)


Bileşen sistem içerisinde bulunan , iyi tanımlanmış arabirim ve davranışlarıyla içeriği hakkında güçlü bir kapsulleme sunan , aynı zamanda değiştirilebilir , kaynak ya da çalışabilir durumda ki bir gurup koda verilen isimdir.

Sistem dizaynının ilk adımı sistemin ana bileşenlerinin tariflenmesidir.Bu ana bileşenler aynı zamanda altsistem olarak da adlandırılırlar.Altsistemler bir sınıf ya da fonksiyon değildirler.Altsistemler sınıflar , ilişkiler , operasyonlar , olaylar , kurallar gibi birbiriyle ilişkili pek çok kavramı barındıran paketlerdir.Altsistemler genellikle sundukları servis ile tanımlanırlar.Servis ortak bir amaca hizmet eden bir gurup fonksiyon olarak tanımlanabilir.Örneğin işletim sistemleri bünyelerinde  bellek yönetimi , dosya yönetimi gibi alt sistemler bulundururlar.

Her altsistem ; sistemin geri kalanı için bir arabirim sunar.Arabirim ; altsistemin tüm fonksiyonalitesini dışarıya sunar ve altsistemin fonksiyonalitesini belirler.Ancak işlemlerin altsistem içersinde nasıl gerçekleştiğini göstermez.Altsistemler sistemin geri kalanını etkilemeden bağımsız olarak dizayn edilebilirler.

Bir sistem sınırlı sayıda alt sisteme sahip olmalıdır.Örneğin 20 muhtemelen çok sayıda altsistemi işaret etmektedir.Altsistemler de kendi içlerinde altsistemlere ayrılabilirler. Altsistemlerin diğer altsistemlere olan bağımlılıkları mümkün olduğunca kısıtlı tutulmalıdır.Bağımlılıklar tercihan aynı seviyedeki altsistemler yerine barındırdığı altsistemler ile arasıda kurulmalıdır.En alt seviyedeki alt sistemlere modül adı verilmektedir.

İki altsistem arasında kurulacak ilişkiler iki şekilde sınıflandırılmaktadır:
  • İstemci – Destekçi (client-supplier) : Bu tür ilişkide istemci , destekçi’nin arabirimini bilmek zorundadır.Bu arabirim üzerinden istediği servisleri alır.Ancak bu tür bir ilişkide destekçi ; istemci hakkında bir bilgiye sahip olmak zorunda değildir.Tek taraflı bir bağımlılık söz konusudur.
  • Eşdüzeyli (peer-to-peer) : Bu tür bir ilişkide altsistemler birbirlerini kullanırlar.Bu tür ilişkiler daha karmaşıktır.Her iki altsistem de birbirinin arabirimini bilmek zorundadır.İletişimin dizaynı oldukça karmaşık ve hataya açıktır.
  
   Altsistemler arasında ilişkiler kurulurken mümkün mertebe istemci-destekçi tarzı ilişkiler kurulmaya çalışılmalıdır. Sistemin altsistemlere açılımı yatay olarak katmanlar , dikey olarak bölümler olarak organize edilebilir.

Katmanlar (Layers)


   Katmanlar , sistem içersinde birbiri ardına gelen sanal dünyalar olarak nitelendirilebilir.En üst seviye katmanlar uygulamaya has fonksiyonalite sunarken , alt seviyeler daha çok çalışma ortamına özel bileşenleri içerirler.Genel amaçlı servisler ve iş mantığı servisleri ise genellikle orta seviyeler yer alır. Katmanlar içerisinde birbiriyle benzeşen nesneler olabilir.

  Katmanlar arasında bilgi tek yönlüdür.Her katman daha alt seviyede ki  katmanlar hakkında bilgi sahibi olabilir ancak üst seviyeler hakkında bir bilgisi yoktur.Alt katmanlar ile üst katmanlar arasında istemci-destekçi tarzı ilişki söz konusudur.

  Katmanlanmış Mimariler’de açık ve kapalı olmak üzere iki form söz konusu olabilir.Kapalı mimaride katmanlar sadece takip eden katmanla iletişim kurabilir.Bu katmanlar arasında bağımlılıkları azaltır ve değişiklikler için daha esnek bir sistem sağlar.Çünki katman’ın arabirimleri üzerinde yapılacak değişiklik sadece bir katmanı etkiler.Açık mimaride katman ; alt seviyelerin tümüne erişebilir .Bu operasyonların her seviyede yeniden tanımlanma gereksinimini ortadan kaldırır ve daha verimli ; küçük kodlar sağlar.Ancak değişiklikler pek çok seviyeyi etkileyebilir.Bu yüzden açık mimari , kapalı mimariye göre daha az güvenli bir yapı sunar.



  Katmanların yalnızca en alt ve en üst seviyeleri problem durumuna göre belirlenir.En üst seviye talep edilen seviyedir , en alt seviye ise kaynakları (database , donanım , işletim sistemi vb) yönetir.
  Katmanların sayısı ve komposizyonu hem problemin hem çözümün karmaşıklığına bağlı olarak değişebilir.


Bölümler (Partititons)


     Bölümler sistemi dikey olarak bölen , her biri bir çeşit servis sunan ,  bağımsız ya da zayıf eşlemeli alt sistemlerdir.Örneğin bir işletim sisteminde dosyalama sistemi , sanal bellek kontrolü , aygıt kontrolü gibi bölümler bulunur.Bölümler birbirleri hakkında büyük bağımlılıklar yaratmayacak kadar bilgi sahibi olabilirler.

   Bir sistem çeşitli kombinasyonlarda hem katmanlara hem de bölümlere parçalanabilir.Katmanlar bölümlere ayrılabilir ya da bölümler katmanlara ayrılabilir.
  

Bileşenler (Components)


   Bileşenler sistem içerisinde modul , paket ya da altsistem gibi kesin sınırları olan ve sorumluluğu belirlenmiş elemanlar olarak da tanımlanabilir.Bileşenler sisteme entegre olabilir ve değiştirebilir yapılardır.Bu aynı zamanda  bileşenlerin başka sistemler içerisine de entegre edilebilir ve yeniden kullanılabilir olduğunu anlatır.

   Moduler bir mimaride bileşenleri bağımsız olarak tanımlar , izole eder , dizayn eder ve geliştirebilirsiniz edebilirsiniz.Daha sonra bileşenler  yine bağımsız olarak test edilip sisteme entegre edilebilirler. Bu bileşenlerden bir kısmı genel çözümler sundukları tesbit edilirse daha geniş alanlarda kullanılabilirler.Bu yeniden kullanılabilir bileşenler organizasyonun genel yazılım verimliliğini ve kalitesini arttırmanın bir yoludur.

    Corba , Active X , Java Bean gibi bileşen altyapıları tüm endüstriye yeniden kullanılabilir bileşenler geliştirmenin yolunu açmıştır.Bu sayede çözümünü beğendiğimiz bileşenleri alıp sistemlerimize entegre edebilmekteyiz.





Çalışma Alanı (Domain) Kavramı


   Sistemimizi yapısal olarak katmanlara ve bölümlere ayırabiliyoruz.Bir üçüncü boyut olarak gelen çalışma alanı (domain ) aslında sistem içerisinde adreslenen çözümlerin genelden özele doğru yeniden kullanılabilir parçalar halinde düzenlenmesidir diyebiliriz.Örneğin muhasebe alt sisteminin genel  , hazırlanan alana özgü (sigorta , bankacılık vb)  ve şirkete özgü parçaları.Çalışma alanlarından  katmanlar ve bölümler gibi yapısal bir parçalanma değil mantıksal bir parçalanma olarak söz edebiliriz.



   Örnek şekil bir uygulama parçalarının nasıl ele alındığını ve içindeki çalışma alanlarının nasıl tesbit edildiğini göstermektedir.Buna göre uygulama içerisindeki iş mantığı içermeyen yeniden kullanılabilir kodlar framework içerisinde toplanmış , işe özel ancak o iş içinde yeniden kullanılabilir kodlar bir başka parçya ayrıldıktan sonra geriye kalan kodlar uygulamaya özel olarak belirlenmiştir.

   Çalışma alanlarının tesbiti sistemdeki genellemelerin ve farklılıkların tesbitini gerektirir.Sistemin daha genel kullanım bulabilecek fonsiyonaliteleri genelden özele doğru parçalara ayrılmalıdır.Bunun en iyi etkisi yeni sistemlerin dizaynı sırasında , üzerinde özelleştirmelerin yapılabileceği sistemlerin hazır halde oluşmuş olmasıdır.

   Çalışma alanı tesbitine problem analizi sırasında başlanmalıdır ve problem bağlamında önceden hazırlanmış çözümler değerlendirilmelidir.Aksi takdirde hazır yapıların değerlendirilememesi ve gereksiz iş gücü kaybının yanı sıra hazırlanacak çözümlerinde yeniden kullanılamaması gibi sakıncalar oluşacaktır.Alttaki şekil uygulama süreçleri ile çalışma alanlarının oluşturulma süreçleri arasındaki ilişkiyi göstermektedir.



  
    
    Yazılım mühendisliğini istekler  doğrultusunda geliştirilen , tek sisteme yönelik bir çalışma olduğunu varsayarsak , çalışma alanı mühendisliği pek çok sisteme uyabilecek yeniden kullanılabilir çözümler sunan bir çalışmadır denilebilir.

   Çalışmalar yapılırken hazırlanan çözümlerin daha geniş alanlara uygulanabilip uygulanamıyacağı sorgulanmalı ve ilerideki projeler için yeniden kullanılabilir çözümler hazırlanmalıdır.Bu işlem bizi yavaşlatır görünse de tüm süreçler içindeki avantajı ve sağlıyacağı katkılar inanılmaz boyutlarda olabilir.   Alan çalışmaları sonucu elimizde yeniden kullanılabilir kod parçalarının yanı sıra çözüm tasarımları da  oluşacaktır.Bu tasarımlar farklı dillerde ve ortamlarda yeniden uyarlanabilir olduğu göz önüne alınırsa , kurumsal bir yapı oluşturulmuş ve farklı çözümler geliştiren firmalara olabilecek katkısı daha iyi anlaşılır.

      Alan çalışmalarına sürecin en başından başlanamasa bile hazır sistemler üzerinde çalışma alanı analizleri yapılarak yeniden kullanılabilir parçaların tesbiti mümkündür.