Turing Bütünlüğü(Tamlığı) bilgisayar bilimlerinde çok önemli bir notasyondur. Bir programlama dili Turing Bütünlüğünü sağladığını söylüyorsa Turing Makinası ile tasarlanabilen tüm hesaplama işlemlerini yapabilir demektir. Zaten Turing Makinasının kendisi herhangi bir şeyin “hesaplanabilir” olup olmadığı gösterebilmek için icat edildi. Makina, çeşitli emirleri okuma, yazma, bellekte saklama gibi ilkel işleri yapar. Zaman içinde bir çok çalışma Turing makinasının gerçek dünya hesaplamaları için hemen hemen aynı modeller ortaya koydu. İlk yaklaşıma göre, eğer hesaplamanın Turing makinası ile yapılamayacağı kanıtlanırsa hiçbir hesaplama cihazının bu işlemi yapamayacağını varsayabiliriz. Diğer taraftan sistem basit Turing makinası emirleri ile tanımlanabiliyorsa bu işlem “Turing Bütünlüğü” içeriyor deriz ve bilgisayarlar ile bu hesaplamayı yapabiliriz.
Bugün en yaygın tasarım patternlerinden olan Singleton Pattern’inden bahsedeceğim. Singleton yazılım patterni Creational Design Patterns(Yaratıcı Tasarım Kalıpları) kategorisinde yer alır. Oluşturulan sınıfın sadece bir adet instance’ı olmasını garantiler. Tanımı kolay olmasına rağmen konu implementasyona gelince bir çok yöntem bulunmaktadır ve her zaman yazılımcılar arasında hangi yöntemin kullanılması gerektiği tartışmalı bir konudur. Burada singleton tasarım patterninin ilkelerinin neler olduğunu ve bu modeli uygulamak için kullanılan yöntemleri teker teker inceleyeceğiz.
Singleton tasarım patternine tüm örnek Java kodlarını aşağıdaki github reposunda bulabilirsiniz:
Bill Pugh Singleton Yöntemi (Bill Pugh Singleton Implementation)
1.Sabırsız Gerçekleme (Eager Initialization)
Bu yöntemde sınıfın instance’ı sınıfın yüklenme anında oluşur. Singleton patterninin en kolay implementasyonlarından biridir. Ancak instance, sınıfın yükleme anında oluştuğundan sınıf kullanılmasa bile instance oluşacağından gereksiz yer kaplar.
Bu yöntemle aşağıdaki gibi singleton patternini oluşturabiliriz:
package codegenius;
/**
*
* @author codegeni.us
*/
public class EagerInitialization {
// define private instance.
private static final EagerInitialization instance = new EagerInitialization();
//Restrict to create new instance.
private EagerInitialization(){
}
// use only created instance via public method.
public static EagerInitialization getInstance(){
return instance;
}
}
Eğer oluşturğunuz sınıf çok fazla kaynak tarafından kullanılmıyorsa bu yöntemi kullanabilirsiniz. Ancak genelde singleton sınıfları dosya sistemlerine erişim, loglama, veritabanı bağlantıları vs, kullanıldığı için sınıf çağrılmadan instance’ı oluşturmaktan kaçınmalıyız. Yukarıdaki yöntemde oluşacak bir exception’ı da ele almak imkansızdır.
2. Statik Blok Yöntemi (Static Block Initialization)
Statik blok yöntemi de aynı şekilde Eager yönteme benzer ancak aradaki fark oluşacak exceptionları ele alabiliriz. Yine sınıfın instance’ı sınıfın yüklenme anında oluşur. Ve bu az öncede söylediğimiz gibi çokta istediğimiz bir şey değil.
Örnek kod aşağıdaki gibidir:
package codegenius;
/**
*
* @author codegeni.us
*/
public class StaticBlock {
// define private static instance
private static StaticBlock instance;
// avoid create instance of class
private StaticBlock(){}
//static block that supports exception handling
static{
try{
instance = new StaticBlock();
}catch(Exception e){
// exception occured handle me.
throw new RuntimeException("Exceptions can be handled here." + e );
}
}
// global accessor method for instance.
public static StaticBlock getInstance(){
return instance;
}
}
3. Tembel Gerçekleme (Lazy Initialization)
Bu yöntem sınıfın instance’ı o sınıfa erişmek istenildiği anda oluşturulur. Eğer bu sınıfa birden fazla thread erişmiyorsa sorunsuz olarak çalışır. Birden fazla thread instance’ın oluşturulduğu yere aynı anda girerse farklı instance lar oluşur bu da singleton ilkesine aykırıdır. Farklı threadler olmadığı zaman bu yöntemde instance sınıfın yüklenme anında değil çağırılma anında oluştuğu için faydalıdır.
Örnek kod aşağıdaki gibidir:
package codegenius;
/**
*
* @author codegeni.us
*/
public class LazyInitialization {
// define private instance but don't create it.
private static LazyInitialization instance;
// avoid to create new instance of class
private LazyInitialization(){}
// global accessor for instance.
public static LazyInitialization getInstance(){
/** !!! be aware. This is not thread safe.If two or more threads try to
* access and creates the instance of the class at the same time.
* That time the Singleton pattern will be destroyed. */
if(instance == null){
instance = new LazyInitialization();
}
return instance;
}
}
Bu yöntemi kullanırken birden fazla thread aynı anda ulaşırsa sorun olacağından bahsetmiştik. Aşağıdaki resimde iki thread singleton instance’ı oluşturuyor ve instance lar birbirinden farklı.
4. Thread Safe Singleton
Az önceki örneğimizde sınıf instance’ı, sınıfın çağırılma anında oluşturularak implement edilen singleton patterninin nasıl thread safe olmadığını kanıtlamıştık. Şimdi lazy initialization yöntemini nasıl thread safe yapabileceğimize göz atalım.
Bunun en kolay yolu sınıfın global erişim metoduna synchronized koymaktır. Böylece metod herhangi bir thread tarafından çağırıldığı anda diğer threadler bu sınıfa erişemez ve birden fazla instance oluşturmanın önüne geçilmiş olur.
Örnek Java kodu:
package codegenius;
/**
*
* @author codegeni.us
*/
public class ThreadSafeSyncronized {
// define private instance
private static ThreadSafeSyncronized instance;
// avoid create instance of class from outside
private ThreadSafeSyncronized(){}
/** syncronize class method for avoid multiple access */
public synchronized static ThreadSafeSyncronized getInstance(){
if(instance == null){
instance = new ThreadSafeSyncronized();
}
return instance;
}
public void sampleMethodA(){
//do awesome stuff here.
}
public void sampleMethodB(){
// do awesome things here.
}
}
Yukarıdaki metodta birden fazla erişim engellenir ancak synchronized kelimesi sınıfın tamamını kitlediği için performans sorunu yaratır. Örneğin aşağıdaki gibi bir kullanımda sampleMethodA() ve sampleMethodB() fonksiyonları farklı metodlar olmasına rağmen farklı threadler tarafından aynı anda çağırılamaz. Çünkü getInstance’a koyduğumuz synchronized anahtar kelimesi tüm sınıfı kitler ve tek threadin kullanımına sunar. Bu da singleton yapısının bozulmamasını garantiler ancak performanssorunlarına sebep olur.
new Thread(new Runnable() {
@Override
public void run() {
ThreadSafeSyncronized.getInstance().sampleMethodA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
ThreadSafeSyncronized.getInstance().sampleMethodB();
}
}).start();
Yukarıdaki şekilde çağırdığımızda resimde görüleceği gibi threadlerin birbirini blokladığını görüyoruz.
Thread safe singleton’a bir diğer yaklaşım ise double check yöntemidir. Bu yöntemde sadece instance oluşturulurken sınıf senkronize edileceğinden performans sorununa da yol açmaz.
package codegenius;
/**
*
* @author codegeni.us
*/
public class ThreadSafeDoubleCheck {
private static ThreadSafeDoubleCheck instance;
private ThreadSafeDoubleCheck() {
}
public static ThreadSafeDoubleCheck getInstance() {
if (instance == null) {
synchronized (ThreadSafeDoubleCheck.class) {
if (instance == null) {
instance = new ThreadSafeDoubleCheck();
}
}
}
return instance;
}
}
5. Bill Pugh Singleton Yöntemi
Java 5 ve öncesi bellek modelindeki problemden dolayı singleton sınıfına eş zamanlı erişilmeye çalışıldığında sorunlar ortaya çıkıyordu. Bu neden Bill Pugh yardımcı nested sınıf kullanarak singleton yapısı oluşturdu. Böylece sınıfın instance’ı yardımcı sınıfta tutuluyor. Sadece sınıf çağırıldığı zaman yardımcı sınıftan faydalanılarak instance çağrılıyordu. Böylece sınıfın yüklenme zamanında instance oluşmuyor sadece erişilmeye çalışıldığında oluşturuluyordu. Bu yöntemle geliştirilmiş singleton patterni aşağıdaki gibidir:
package codegenius;
/**
*
* @author codegeni.us
*/
public class BillPugh {
private BillPugh() {
}
private static class SingletonHelper {
private static final BillPugh instance = new BillPugh();
}
public static BillPugh getInstance() {
return SingletonHelper.instance;
}
}
Singleton sınıfı belleğe yüklendiğinde yardımcı inner sınıf belleğe yüklenmez. Sadece birisi getInstance metodunu çağırdığında yüklenir. Böylece senkronizasyon yapmaya da gerek kalmaz. Genelde en çok kullanılan java singleton patterni budur.
Bugün yazılım geliştirme modellerinden olan Adapter patterninden bahsedeceğim.
Client : Geliştirdiğiniz yazılımı arayüzünü kullanarak yazılım geliştiren kişi.
Bazı durumlarda client sunduğunuz kodda kalıbı bozmadan ufak değişiklikler yaparak yazılımını geliştirmesi gerekebilir. Bazen ise yazılım tamamemn sunduğunuz arayüzden bağımsız olarak geliştirilebilir. Örneğin bir roket simulasyon programını düşünelim. Bu program sizin sağladığınız roket bilgilerine göre geliştirilebileceği gibi client roketin nasıl olması gerektiğini hangi durumlarda nasıl davranması gerektiğini de programlamak isteyebilir. Client’ın sunduğunuz servislerini direk olarak kullanmasını sağlamak veya metod isimlerini kendine göre özelleştirip servis etmek istediniz durumlarda Adapter modelini kullanabilirsiniz.
Adapter’in amacı : Client’ın beklediği servisleri, farklı bir arayüz ile o sınıfa ait servisleri kullanabileceği yapıyı oluşturmaktır.
Resimdeki “NewClass” sınıfı adapter’a örnektir. Bu sınıftan üretilen nesne aynı zamanda “RequiredInterface” arayüzününde nesnesi olacaktır. Daha somut bir örnek verelim :
Roketlerin uçuş ve zamanlamalarını simule eden bir paket üzerinde çalışıyorsunuz. Elimizdeki pakette roketin davranışına göre olayları simule eden bir olay simulatörü olduğunu varsayalım. Elinizde ise fiziksel özellikleri PhysicalRocket sınıfında belirlenmiş bir roketiniz var. Simulasyon paketini kullanarak roketi simule etmek istiyorsunuz. Bu durumda PhysicalRocket sınıfının alt sınıfını oluşturup RocketSimInterface arayüzünü implement ederek Adapter modelini kullanabilirsiniz.
Class diyagramının implementi ise aşağıdaki gibi olacaktır :
package com.oozinoz.firework;
import com.oozinoz.simulation.*;
public class OozinozRocket extends PhysicalRocket implements RocketSim {
private double time;
public OozinozRocket(
double burnArea,
double burnRate,
double fuelMass,
double totalMass){
super(burnArea, burnRate, fuelMass, totalMass);
}
public double getMass() {
return getMass(time);
}
public double getThrust() {
return getThrust(time);
}
public void setSimTime(double time) {
this.time = time;
}
}
Şekil 1. Yazılım müh. kavramlarını içeren örnek UML diyagramı
Yazılım geliştirenler için proje,aktiviteler(activity) dizisidir. Her aktivite bir veya birden fazla görevden(task) oluşur. Görev, kaynakları(Resources) tüketir ve İşÜrünü(WorkProduct) oluşturur. Oluşan ürün Sistem, Model veya Döküman olabilir. Kaynakları, katılımcı(participants), zaman ve ekipman oluşturur. Aradaki çizgiler farklı kavramlar arasındaki ilişkileri ifade eder. Örneğin elmas şekli toplama anlamına gelir. Bir Proje, aktivitelerin toplamıdır. Üçgen şekli ise genelleştirme anlamına gelir. Örneğin Sistem, Model, Döküman İşÜrünü’nün alt türleridir.