22 Ağustos 2011 Pazartesi

The Strategy Pattern (Strateji Şablonu)

C++ veya Java gibi diller öğretilirken genellikle "is-a" ilişkisinin "has-a" ilişkisinden daha iyi olduğu söylenir. Yani, bir sınıfın içinde başka bir sınıftan nesne tutmak yerine aynı sorunu türeterek çözmemizin daha iyi olduğu söylenir. Hatta cümle olarak "Favor inheritance over composition", yani "türetmeyi kompozisyona (sahibi olma ilişkisi) tercih et". Fakat aslında bu genellikle tam tersidir. Genellikle diyorum çünkü gerçek hayatta amaca yönelik olarak her şey değiştirilebilir. Mesela çok hızlı çalışmasını düşündüğümüz bir robotik projesinde chip üzerinde (Eşref Adalı görse çok kızardı) Java veya C# kodunun çalışmasını henüz beklemeyiz. Dolayısıyla tasarım şablonları bir amaç değil araçtır.

"Kompozisyonu türetmeye tercih et!"

Bu bölümde tam da bu sözü doğrulayan bir tasarım şablonu olan strateji şablonunu anlatacağım. Strateji şablonu aralarında değiştirilebilir algoritma aileleri tanımlar. Ayrıca strateji algoritmanın kullanıcısından bağımsız olarak değişmesini sağlar.


Strateji şablonunu kullanarak her davranış değişikliği için bu davranışı kullanan sınıfı türetmek yerine bu davranışı temsil eden bir arayüz nesnesi
tutup ondan davranışlar türetilirse kullanıcı olan sınıf artık yeni davranışları takip etmek zorunda kalmaz.

Bunu anlatmak için örnek verelim. Örnekleri Java dilinde vereceğim çünkü bahsettiğim gibi tasarım şablonları ile en iyi uyumlu olan dil Javadır. 

Örneğin bize kendini tanıtan, bir yere giden ve bize veda edip geldiği yere geri dönen bir Person sınıfımız olsun. Bu kişi farklı dillerde selam verebilir, selamlaşabilir ve bir yere farklı şekillerde gidebilir. Bütün bu çeşitler için bir sınıf türetmeye kalksak tüm davranışların sayısının çarpımı kadar alt sınıf yazmamız gerekirken, bazı sınıflar ise belki çok anlamsız ve gereksiz olacaktır. Ayrıca alt sınıfımızı dinamik olarak değiştirmek de güvenli olmayabilir.
//package info

public class Person {
    //constructors
 
 public void introduce()
 {
     helloBehavior.sayHello(this); 
 }
 
 public void moveTo(int x, int y)
 {
     moveBehavior.move(this, x, y);
 }
 
 public void leaveAndGoTo(int x, int y)
 {
     goodbyeBehavior.sayGoodbye();
     moveBehavior.move(this, x, y);
 }

 // Getters and setters

 // Behaviors
 HelloBehavior helloBehavior;
 GoodbyeBehavior goodbyeBehavior;
 MoveBehavior moveBehavior;
 
 // Properties
 protected int x, y;
 protected String name;
}
Burada anlattığım gibi üç tane davranışı temsil eden HelloBehavior, GoodbyeBehavior,
MoveBehavior arayüzleri var. Burada ayrıca bir prensibi daha görüyoruz:

"Uygulamayı değil, arayüzü tasarla!"

Bunun anlamı somut (concrete) sınıflarla tasarımı sınırlandırmaktansa, arayüzlerle sonradan yapılacak değişiklik sorumluluğunu artık ana koddan çıkartıp genişletici sınıflara yükleyebiliriz. Yani burada da "değişiklik için kapalı genişletmek için açık" prensibini de vurgulamış olabiliriz.
Zaten hazır olarak kullandığımız kodların hepsi bu kurala uyar (eğer derlenmiş halde ise). Yani sizden onların kaynak kodlarını açıp değiştirmeniz beklenmez. Bunun için de bir arayüz yapısı şarttır.

Peki bu davranışları nasıl genişletebiliriz?

Tabiki bu arayüzlerden sınıflarımız türeterek. Örneklerin bir kaçı şöyle:
public class HelloTurkish implements HelloBehavior {

 @Override
 public void sayHello(Person person) {
  System.out.println("Merhaba, ben " + person.name + "!");
 }

}

public class HelloGerman implements HelloBehavior {

 @Override
 public void sayHello(Person person) {
  System.out.println("Hallo, ich bin " + person.name + "!");
 }

}

public class Jump implements MoveBehavior {

 @Override
 public void move(Person person, int x, int y) {
  person.x = x;
  person.y = y;
  System.out.println("I jumpped to (" + x + ", " + y + ")");
 }

}

public class Run implements MoveBehavior {

 @Override
 public void move(Person person, int x, int y) {
  person.x = x;
  person.y = y;
  System.out.println("I ran to (" + x + ", " + y + ")");
 }

}

public class GoodbyeTurkish implements GoodbyeBehavior {

 @Override
 public void sayGoodbye() {
  System.out.println("Güle güle!");

 }

}

public class GoodbyeGerman implements GoodbyeBehavior {

 @Override
 public void sayGoodbye() {
  System.out.println("Aufwiedersehen!");
 }

}
Örnek olarak sizde İngilizce selamlaşma ve vedalaşma sınıfları türetebilir ve ayrıca koşma ve zıplamanın yanına uçmak da ekleyebilirsiniz. Bunun için Person sınıfına dokunmanıza hiç gerek yoktur. Zaten alttaki örnekte de bu yapının nasıl kullanıldığını göreceğiz:
public class PersonSimulator {

 public static void main(String[] args) {
  Person germanRunner = new Person("Wilhelm Renner", 0, 0, new HelloGerman(), new GoodbyeGerman(), new Run());
  Person turkishJumper = new Person("Bahri Ziplak", 0, 0, new HelloTurkish(), new GoodbyeTurkish(), new Jump());
  Person turkishGermanRunner = new Person("Tunç Werner", 0, 0, new HelloTurkish(), new GoodbyeGerman(), new Run());
  
  ArrayList<Person> persons = new ArrayList<Person>();
  
  persons.add(germanRunner);
  persons.add(turkishJumper);
  persons.add(turkishGermanRunner);
  
  for (Person person : persons)
  {
   person.introduce();
   person.moveTo(20, 40);
   person.leaveAndGoTo(0, 0);
   System.out.println();
  }
  
  turkishGermanRunner.setMoveBehavior(new Jump());
  
  System.out.println("After Tunç decides to jump rather than run like Germans did...");
  
  for (Person person : persons)
  {
   person.introduce();
   person.moveTo(20, 40);
   person.leaveAndGoTo(0, 0);
   System.out.println();
  }
 }
}
Burada artık kişimizin kim olduğuna aldırış etmeden kendini tanıttırıyoruz, (20, 40) pozisyonuna yolluyoruz ve veda ettirip başlangıç noktası olan (0, 0) pozisyonuna geri yolluyoruz. Sonra kişilerden biri koşmaktansa zıplamayı tercih ediyor. Bu kararı çalışma zamanında verebiliyoruz. Bunu davranışı temsil eden sınıf nesnesine başka bir davranış nesnesini atayarak yapabiliyoruz.
Bu örnek ile ilk şablonumuzu tamamlamış bulunuyoruz.

Buradan tüm projenin kaynak kodlarına ulaşabilirsiniz.
Başka bir ilginç örneğe buradan ulaşabilirsiniz.

Başarılar. :)

Hiç yorum yok:

Yorum Gönder