Kurumsal Java Yazılımı
|
|
|
Open Closed Principle (OCP) – Açık Kapalı Tasarım Prensibi Posted: 16 Oct 2009 01:38 AM PDT Yazılım disiplininde değişmeyen birşey varsa o da değişikliğin kendisidir. Birçok program müşteri gereksinimleri doğrultusunda ilk sürümden sonra değişikliğe uğrar. Bu doğal bir süreçtir ve müşteri programı kullandıkça ya yeni gereksinimlerini ya da mevcut fonksiyonlar üzerinde adaptasyonları gerekçe göstererek programın değiştirilmesini talep edecektir.
Tanınmış yazılım ustalarından Ivar Jacobson bu konuda şöyle bir açıklamada bulunmuştur: “All systems change during their life cycles. This must be born in mind when developing systems are excepted to last longer than the first version.” Şu şekilde tercüme edilebilir: “Her program görev süresince değişikliğe uğrar. Bu ilk sürümden ötesi düşünülen programların yazılımında göz önünde bulundurulmalıdır.” Değişiklik kaçınılmaz olduğuna göre, bir programı gelecekte yapılması gereken tüm değişiklikleri göz önünde bulundurarak, şimdiden buna hazır bir şekilde geliştirmek mümkün müdür? Öncelikle şunu belirtelim ki gelecekte meydana gelecek değişikliklerin hepsini kestirmemiz mümkün değildir. Ayrıca çevik süreçlerde ilerde belki kullanılabileceğini düşündüğümüz fonksiyonların implementasyonu kesinlikle tabudur (istenilmeyen bir durum anlamında). Bu durum bizi programcı olarak gelecekteki değişiklikleri göz önünde bulundurarak, bir nevi hazırlık yapmamızı engeller. Çevik süreç sadece müşteri tarafından dile getirilmiş, kullanıcı hikayesi haline dönüştürülmüş ve müşteri tarafından öncelik sırası belirlenmiş gereksinimleri göz önünde bulundurur ve implemente eder. Kısacası çevik süreç geleceği düşünmez ve şimdi kendisinden beklenenleri yerine getirir. Böylece müşteri tarafından kabul görmeyecek bir sistemin oluşması engellenmiş olur. Çevik süreç büyük çapta bir tasarım hazırlığı ile start almaz. Daha ziyade mümkün olan en basit şekilde yazılıma başlanır. Her iterasyon başlangıcında implemente edilmesi gereken kullanıcı hikayeleri seçildikten sonra, mevcut yapının yeni gereksinimlere cevap verip, veremeyeceği incelenir. Büyük bir ihtimalle mevcut yapı yeni kullanıcı hikayelerinin implementasyonu için yeterli olmayacaktır. Bu durumda programcı ekip refactoring yöntemleriyle programın yapısını yeni gereksinimleri kabul edecek şekilde modifike eder. Bu esnada tasarım yapısal değişikliğe uğrar. Bu gerekli bir işlemdir ve yapılmak zorundadır, aksi taktirde bir sonraki iterasyon beraberinde getirdiği değişikliklerle programcı ekibini yazılım esnasında zorlayacaktır. Çevik süreç gelecekte olabilecek değişikleri göz önünde bulundurmadığına göre, programı bu değişikliklerin olumsuz yan etkilerine karşı nasıl koruyabiliriz? Bunun yolu her zaman olduğu gibi yazılım esnasında uygun tasarım prensiplerini uygulamaktan geçmektedir. Eğer çevik süreç bize gelecekte olabilecek değişikliklere karşı destek sağlamıyorsa, bizimde tedbir alarak çevik süreci, çevik prensiplere ters düşmeden takviye etmemiz gerekiyor. Çevik süreci, çevik tasarım prensiplerini kullanarak istediğimiz bir yapıda, bakımı ve geliştirilmesi kolay program yazılımına destek verecek şekilde takviye edebiliriz. Tekrar bölüm başında sorduğum soruya dönelim ve sorunun cevabını bulmaya çalışalım. Şu şekilde bir soru sormuştum: “Değişiklik kaçınılmaz olduğuna göre, bir programı gelecekte yapılması gereken tüm değişiklikleri göz önünde bulundurarak, şimdiden buna hazır bir şekilde geliştirmek mümkün müdür?” Bu mümkün değil demiştik. Lakin esnek bir tasarım oluşturarak önü açık ve gelecek korkusu olmayan bir program yapısı oluşturabiliriz. Bunu gerçekleştirmek için kullanabileceğimiz prensiplerin başında Open Closed – Açık Kapalı prensibi (OCP) gelmektedir. Bertrand Meyer tarafından geliştirilen bu prensip kısaca şöyle açıklanabilir: “Programlar geliştirilmeye açık ama değiştirilmeye kapalı olmalıdır.” Programı geliştirmek, programa yeni bir davranış biçimi eklemek anlamına gelmektedir. OCP ye göre programlar geliştirmeye açık olmalıdır, yani programı oluşturan modüller yeni davranış biçimlerini sergileyecek şekilde genişletilebilmelidirler. Bir modüle yeni bir davranış biçimi kazandırılarak düşünülen değişiklik sağlanır. Bu yeni kod yazılarak gerçekleştirilir ( bu yüzden bu işleme değiştirme değil, genişletme denir), mevcut kodu değiştirerek değil! Eğer kendinizi bir müşteri gereksinimini mevcut kod üzerinde değişiklik yaparken bulursanız, biliniz ki OCP prensibine ters düşüyorsunuz. Kod üzerinde yapılan değişiklik, bir sonraki gereksinimlerinde ayni şekilde implemente edilmesini zorunlu kılacaktır. Bu durum, kodun zaman içinde içinden çıkılmaz ve çok karmaşık bir yapıya dönüşmesini çabuklaştırır. OCP prensibinin nasıl uygulanabileceğini bir önceki bölümde yer alan RemoteControl – TV örneği üzerinde inceleyelim. Resim 1 de görüldüğü gibi RemoteControl sınıfı TV sınıfını kullanarak işlevini yerine getirmektedir. Eğer RemoteControl sınıfını TV haricinde başka bir aleti kontrol etmek için kullanmak istersek, örneğin CDPlayer Resim 2 deki gibi değişiklik yapmamız gerekebilir. Bu noktada esnek bağ prensibini unutarak, resim 2 de yer alan çözümün bizim için yeterli olduğunu düşünelim.
package org.cevikjava.design.ocp;
/**
* TV ve CDPlayer sınıflarından
* nesneleri kontrol eder.
*
* @author Oezcan Acar
*
*/
public class RemoteControl
{
/**
* Aleti acmak
* için kullanilan metot.
*
*/
public void on(Object obj)
{
if(obj instanceof TV)
{
((TV)obj).tvOn();
}
else if(obj instanceof CDPlayer)
{
((CDPlayer)obj).cdOn();
}
}
/**
* Aleti kapatmak
* için kullanilan metot.
*/
public void off(Object obj)
{
if(obj instanceof TV)
{
((TV)obj).tvOff();
}
else if(obj instanceof CDPlayer)
{
((CDPlayer)obj).cdOff();
}
}
}
Bu şekilde oluşturulan bir tasarım OCP prensibine ters düşmektedir, çünkü her yeni eklenen alet için on() ve off() metotlarında değişiklik yapmamız gerekmektedir. OCP böyle bir modifikasyonu kabul etmez. OCP ye göre mevcut çalışır kod kesinlikle değiştirilmemelidir. Onlarca aletin bulunduğu bir sistemde on() ve off() metotlarının ne kadar kontrol edilemez ve bakımı zor bir yapıya bürüneceği çok net olarak bu örnekte görülmektedir. Bunun yanı sıra RemoteControl sınıfı TV ve CDPlayer gibi sınıflara bağımlı kalacak ve başka bir alanda kullanılması mümkün olmayacaktır. TV ve CDPlayer sınıflar üzerinde yapılan tüm değişiklikler RemoteControl sınıfını doğrudan etkileyecek ve yapısal değişikliğe sebep olacaktır. Resim 3 de yer alan çözüm OCP ye uygun yapıdadır, çünkü kod üzerinde değişiklik yapmadan programa yeni davranışlar eklemek mümkündür. OCP prensibi, esnek bağ prensibi kullanılarak uygulanabilir. OCP ye uygun RemoteControl sınıfının yapısı şu şekilde olmalıdır:
package org.cevikjava.design.loosecoupling.design;
/**
* RemotControlInterface sınıfını
* implemente eden sınıfları
* kontrol edebilen sınıf.
*
* @author Oezcan Acar
*
*/
public class RemoteControl
{
/**
* Delegasyon islemi için RemoteControlInterface
* tipinde bir sınıf degiskeni tanimliyoruz.
* Tüm islemler bu nesnenin metodlarina
* delege edilir.
*/
private RemoteControlInterface remote;
/**
* Sinif konstuktörü. Bir nesne oluşturma islemi
* esnasında kullanilacak RemoteControlInterface
* implementasyonu parametre olarak verilir.
*
* @param _remote RemoteControlInterface
*/
public RemoteControl(RemoteControlInterface _remote)
{
this.remote = _remote;
}
/**
* Aleti acmak
* için kullanilan metot.
*
*/
public void on()
{
remote.on();
}
/**
* Aleti kapatmak
* için kullanilan metot.
*/
public void off()
{
remote.off();
}
}
on() ve off() metotları sadece RemoteControlInterface tipinde olan bir sınıf değişkeni üzerinde işlem yapmaktadır. Bu sayede if/else yapısı kullanmadan RemoteControlInterface sınıfını implemente etmiş herhangi bir alet üzerinde gerekli işlem yapılabilmektedir. Bu örnekte on() ve off() metotları değişikliğe kapalı ve tüm program geliştirmeye açıktır, çünkü RemoteControlInterface interface sınıfını implemente ederek sisteme yeni aletleri eklemek mümkündür. Sisteme eklediğimiz her alet için on() ve off() metotları üzerinde değişiklik yapmak zorunluluğu ortadan kalkmaktadır. Uygulanan OCP ve esnek bağ prensibi ile RemoteControl başka bir alanda her tip aleti kontrol edebilecek şekilde kullanılır hale gelmiştir. Stratejik Kapama (Strategic Closure)Ne yazık ki bir yazılım sistemini OCP prensibini uygulayarak %100 değişikliklere karşı korumamız imkansızdır. OCP ye uygun olan bir metot müşterinin yeni istekleri doğrultusunda OCP’ye uygun olmayan bir hale gelebilir. Programcı metodu ilk implemente ettiği zaman, gelecekte olabilecek değişiklikler hakkında fikir sahibi olmayabilir. Metot OCP uyumlu implemente edilmiş olsa bile, bu metodun her zaman OCP uyumlu kalabileceği anlamına gelmez. Eğer kapama tam sağlanamıyorsa, kapamanın stratejik olarak implemente edilmesi gerekir. Programcı her zaman ne gibi değişikliklerin olabileceğini kestiremeyebilir. Bu durumda konu hakkında araştırma yaparak, oluşabilecek değişiklikleri tespit edebilir. Eğer olabilecek değişikliklerin tespiti mümkün değilse, beklenen değişiklikler meydana gelene kadar beklenir ve implementasyon yeni değişiklikleri de yansıtacak şekilde OCP uyumlu hale getirilir. Bu yazıyı PDF dosyası olarak aşağıdaki linkten edinebilirsiniz.
|
|
Loose Coupling (LC) – Esnek Bağ Tasarım Prensibi Posted: 16 Oct 2009 01:07 AM PDT Bir program bünyesinde, tanımlanan görevlerin yerine getirilebilmesi için birden fazla nesne görev alır. Bu nesneler birbirlerinin sundukları hizmetlerden faydalanarak kendi görevlerini yerine getirirler. Bu durumda nesneler arası bağımlılıklar oluşur. Bir nesne kullandığı diğer bir nesne hakkında ne kadar fazla detay bilgiye sahip ise, o nesneye olan bağımlılığı o oranda artar. Oluşan her bağımlılık bir sınıf için dolaylı olarak yapısal değiştirilme rizikosunu artırır, çünkü bağımlı olduğu sınıf üzerinde yapılan her değişiklik kendi yapısında değişikliğe neden olacaktır. Bu durum programın genel olarak kırılgan bir hale gelmesini kolaylaştıracaktır.
Buradan “Eğer bağımlılık varsa, sorun var, bu yüzden bağımlılıkların ortadan kaldırılması gerekmektedir” sonucunu çıkartabiliriz. Nesneye yönelik tarzda tasarlanmış edilmiş bir program içinde bağımlılıkları ortadan kaldırmak imkansızdır, çünkü nesnelerin olduğu yerde interaksiyon ve bağımlılık olmak zorundadır. Bağımlılıkları ortadan kaldıramıyorsak, o zaman onları kontrol altına almamız işimizi kolaylaştıracaktır. Eğer bana soracak olursanız, yazılım disiplininin özü de burada saklıdır: Yazılımcı olarak kullandığımız tüm metotların temelinde bağımlılıkların kontrolü ve yönetilmesi yatmaktadır. İyi bir tasarım oluşturmak için sarf ettiğimiz efor, bağımlılıklara hükmetmek isteyişimizden kaynaklanmaktadır, yani iyi bir tasarım , kontrol edilebilir bağımlılıkları beraberinde getirdiği için iyidir, kendimizi programcı olarak iyi hissetmemizi sağladığı için değil! Biliyoruz ki kötü bir tasarım nesneler arası yüksek derecede bağımlılığa sebep vereceği için programcı olarak hayatımızı zorlaştıracaktır. Bu yüzden sahip olduğumuz tüm teknik yeteneklerimizle bağımlılığa karşı bir savaş veririz. Onu yenmemiz mümkün olmasa da, bize zarar vermeyecek şekilde kontrol altına almamız mümkündür. Anladığımız kadarıyla bağımlılıkları mantıklı bir çerçevede ortadan kaldırmamız imkansız! Peki bağımlılıkları nasıl kontrol altına alabiliriz? Esnek bağımlılıklar oluşturarak! Esnek bağımlılık oluşturmak demek, nesneler arası bağların oluşmasına izin vermek, ama sınıflar üzerinde yapılan yapısal değişikliklerin bağımlı sınıflar üzerinde yapısal değişikliğe sebep vermesini engellemek demektir. Bunu bir örnek vererek açıklayalım. RemoteControl (tv uzaktan kumanda aleti) ve TV (televizyon) sınıflarının arasında tek yönlü (uni directional) bir bağ oluşmuştur, çünkü bir RemoteControl nesnesi görevini yerine getirebilmek için bir TV nesnesine ihtiyaç duymaktadır. Nesneler arası bağımlılıkların yönü vardır. Resim 1 de bu bağımlılığın yönünün RemoteControl sınıfından TV sınıfına doğru olduğunu görmekteyiz. Bu tek yönlü bir bağdır ve RemoteControl sınıfı bünyesinde TV tipinde bir sınıf değişkeni barındırarak bağı oluşturur. Sınıflar arası bağlar karşılıklı da (bi directional) olabilir. İki taraflı bağlarda sınıflar, bir sınıf değişkeni aracılığıyla karşılıklı olarak birbirlerine işaret ederler. RemoteControl nesnesi bir TV nesnesi olmadığı sürece işe yaramaz. Bu yüzden bu iki sınıf arasında direk bağlantı oluşturulmuştur. Böyle bir bağımlılık aşağıdaki sorunların oluşmasına sebep vermektedir:
package org.cevikjava.design.loosecoupling;
/**
* Bir televizyonu uzaktan kuman etme
* aletini simule eden sınıf.
*
* @author Oezcan Acar
*
*/
public class RemoteControl
{
/**
* Kontrol edilen televizyon
*/
private TV tv = new TV();
/**
* Televizyonu acmak
* için kullanilan metot.
*
*/
public void tvOn()
{
tv.on();
}
/**
* Televizyonu kapatmak
* için kullanilan metot.
*/
public void tvOff()
{
tv.off();
}
}
RemoteControl sınıfı bünyesinde TV tipinde bir sınıf değişkeni (tv) barındırdığı için kendisini TV sınıfına bağımlı kılar.
package org.cevikjava.design.loosecoupling;
/**
* Bir televizyonu simule eden sınıf.
*
* @author Oezcan Acar
*
*/
public class TV
{
/**
* Televizyonu acmak için
* kullanilan metot.
*
*/
public void on()
{
System.out.println("TV acildi.");
}
/**
* Televizyonu kapatmak için
* kullanilan metot.
*
*/
public void off()
{
System.out.println("TV kapandi");
}
}
Nesneler arası kuvvetli bağların, programın bakımı, geliştirilmesi ve kodun tekrar kullanımını negatif yönde etkilediğini gördük. Bu sorunu ortadan kaldırabilmek için sınıfların ilişkileri üzerinde yapısal değişikliğe gitmemiz gerekiyor. Esnek bağ oluşturabilmek için Abstract (soyut) ya da Interface sınıflarından faydalanabiliriz. Resim 2 de görüldüğü gibi RemoteControlInterface ismini taşıyan bir Interface sınıf ile RemoteControl ve bu sınıfın kontrol etmek istediği aletler arasında esnek bir bağ oluşturuyoruz. Böyle bir yapının esnekliği nereden gelmektedir, bunu yakından inceleyelim.
package org.cevikjava.design.loosecoupling.design;
/**
* RemoteControlInterface sınıfı
*
* @author Oezcan Acar
*
*/
public interface RemoteControlInterface
{
/**
* Bu sınıfı implement eden
* bir aleti acmak için
* kullanilan metot.
*
*/
void on();
/**
* Bu sınıfı implement eden
* bir aleti kapatmak için
* kullanilan metot.
*
*/
void off();
}
Interface sınıflar bünyesinde sadece metot gövdeleri tanımlanır. Alt sınıflar kullanılarak interface sınıfında tanımlanmış olan metotlar implemente edilir. Bu sayede interface sınıfında tanımlanmış metotlar için değişik sınıflarda değişik tarzda implementasyonlar yapmak mümkündür. Herhangi bir sınıf implements direktifini kullanarak bir interface sınıfını implemente edebilir. Interface sınıfında tanımlanmış olan tüm metotların alt sınıflarca implemente edilmesi gerekmektedir. Resim 2 de görüldüğü gibi RemoteControl sınıfı direk TV sınıfı kullanmak yerine, RemoteControlInterface ismini taşıyan interface sınıfı ile beraber çalışmaktadır. Böyle bir yapılanma ile RemoteControl ve bu sınıfın kullanmak istediği somut sınıflar (örneğin TV) arasına bir set çekmiş olduk. RemoteControl sınıfı bu setin arkasında yer alan sınıfları, bunlar RemoteControlInterface sınıfını implemente eden sınıflardır, tanımamaktadır ve tanımak zorunda değildir. RemoteControl sınıfının tanıması gereken tek sınıf RemoteControlInterface sınıfı ve bu interface sınıfının dış dünyaya sunduğu metotlardır. RemoteControl sınıfı böylece dolaylı olarak, RemoteControlInterface sınıfını implemente eden her sınıfı kullanabilir hale gelmektedir. Böylece RemoteControl sınıfının somut sınıflara olan bağımlılığı bir interface sınıf kullanılarak ortadan kaldırılmıştır.
package org.cevikjava.design.loosecoupling.design;
/**
* RemotControlInterface sınıfını
* implemente eden sınıfları
* kontrol edebilen sınıf.
*
* @author Oezcan Acar
*
*/
public class RemoteControl
{
/**
* Delegasyon islemi için RemoteControlInterface
* tipinde bir sınıf degiskeni tanimliyoruz.
* Tüm islemler bu nesnenin metodlarina
* delege edilir.
*/
private RemoteControlInterface remote;
/**
* Sinif konstuktörü. Bir nesne oluşturma islemi
* esnasında kullanilacak RemoteControlInterface
* implementasyonu parametre olarak verilir.
*
* @param _remote RemoteControlInterface
*/
public RemoteControl(RemoteControlInterface _remote)
{
this.remote = _remote;
}
/**
* Aleti acmak
* için kullanilan metot.
*
*/
public void on()
{
remote.on();
}
/**
* Aleti kapatmak
* için kullanilan metot.
*/
public void off()
{
remote.off();
}
}
RemoteControl sınıfını RemoteControlInterface sınıfını kullanacak şekilde değiştiriyoruz. Bu amaçla RemoteControl sınıfında RemoteControlInterface tipinde bir sınıf değişkeni (remote) tanımlıyoruz. Sınıf konstruktörü RemoteControlInterface tipinde bir parametre kabul etmektedir. Böylece RemoteControl sınıfından bir nesne oluştururken, istediğimiz tipte bir RemoteControlInterface implementasyon sınıfı kullanabiliriz. RemoteControl sınıfı on() ve off() isminde iki metot tanımlamaktadır. Bu metotlar RemoteControlInterface sınıfında yer alan on() ve off() metotları ile karıştırılmamalıdır. RemoteControl sınıfı sahip olduğu metotlara ac() ve kapat() isimlerini de verebilirdi. Bu metotlar içinde delegasyon yöntemiyle sınıf değişkeni olan remote nesnesi kullanılmaktadır. Bu sayede kullanılan RemoteControlInterface implementasyon sınıfının (örneğin TV) on() ve off() metotları devreye girecektir.
package org.cevikjava.design.loosecoupling.design;
/**
* Bir televizyonu simule eden sınıf.
* RemoteControlInterface sınıfını
* implemente ederek bir RemoteControlInterface
* haline gelir.
*
* @author Oezcan Acar
*
*/
public class TV implements RemoteControlInterface
{
/**
* Televizyonu acmak için
* kullanilan metot.
*
*/
public void on()
{
System.out.println("TV acildi.");
}
/**
* Televizyonu kapatmak için
* kullanilan metot.
*
*/
public void off()
{
System.out.println("TV kapandi");
}
}
TV sınıfı RemoteControlInterface sınıfını implemente etmektedir. Bu sebepten dolayı RemoteControlInterface sınıfında tanımlanmış olan on() ve off() metotlarına sahiptir. TV sınıfı RemoteControlInterface sınıfını implemente etmediği sürece RemoteControl sınıfı tarafından kullanılamaz.
package org.cevikjava.design.loosecoupling.design;
/**
* Test sınıfı
*
* @author Oezcan Acar
*
*/
public class Test
{
public static void main(String[] args)
{
RemoteControlInterface rci = new TV();
RemoteControl control = new RemoteControl(rci);
control.on();
control.off();
}
}
Test.main() bünyesinde ilk önce kullanmak istediğimiz alet nesnesini, (TV) ve akabinde RemoteControl nesnesini oluşturuyoruz. RemoteControl konstruktör parametresi olarak bir satır önce oluşturduğumuz TV nesnesini almaktadır. RemoteControl bünyesinde yer alan on() ve off() metotları ile televizyonu açıp, kapatabiliriz. Ekran çıktısı şu şekilde olacaktır:
RemoteControl sınıfının konstruktörü RemoteControlInterface sınıfını implemente eden her sınıfı kabul ettiği için istediğimiz herhangi bir aleti RemoteControl sınıfı ile kontrol edebilir hale geliyoruz. Oluşturduğumuz yeni tasarımın bize sağladığı avantajlar şöyledir:
İncelediğimiz örneklerde nesneler arası bağın ortadan kaldırılamayacağını ama esnek bağ oluşturma prensibini uygulayarak kontrol edilebilir bir hale getirilebileceklerini gördük. Esnek bağlar oluşturabilmek için Interface yada soyut sınıflardan yararlanabiliriz. Usta yazılımcılar tasarım prensiplerine ve tasarım şablonlarına (design pattern) hakim olup,onları doğru yerde kullanmasını bilirler. Eğer şimdiye kadar tasarım prensipleri hakkında bir çalışmanız olmadıysa, sizin için yazılım disiplininde bir üst boyutun kapısını aralamış olduk. Tasarım prensiplerini uygulayarak ve tasarım şablonlarını kullanarak konseptüel daha yüksek seviyede çalışabilirsiniz. Bu bölümde yer alan tasarım prensipleri yazılım sürecine olan bakış açınızı tamamen değiştirecek niteliktedir. Bu yazıyı PDF dosyası olarak aşağıdaki linkten edinebilirsiniz.
|
| You are subscribed to email updates from Kurumsal Java Yazılımı
To stop receiving these emails, you may unsubscribe now. |
Email delivery powered by Google |
| Google Inc., 20 West Kinzie, Chicago IL USA 60610 | |