Strategy Pattern
Bu yazı, Head First Design Pattern kitabı referans alınarak hazırlanmıştır. İlgili patternin, kitap içindeki ilgili senaryosunun özeti niteliğindedir. Bu özet, strategy patternin avantajlarını göstermeyi hedef edinir ve ingilizce-türkçe karışık terminolojiyi içerir.
Bir oyun firmasında çalıştığımız ve bu oyun firmasının içerisinde, çeşitli ördek tiplerini ve bunların davranışlarını barındıran bir simülasyon oyunu olduğu var sayılsın. İçerisindeki her ördek tipinin kendine has dış görünüşü ve uçma, yüzme gibi davranışları olsun. Bu durumda yapılabilecek işlemlerden biri, bir Duck superclass’ı tanımlayıp, içerisinde davranışların implementasyonlarını yapmak ve her ördek tipinin , Duck superclass’ını inherit etmesini sağlamak olacaktır. Bu durum aşağıda gösterildiği gibidir.
Durum bu şekilde iken, şirket tarafından ördeklere, uçma davranışı eklenmesi istenmesi bize sıkıntı çıkarabilir. Bunun sebebi, sistemde var olabilecek uçma davranışına sahip olmayan ördek tiplerine, plastik ördek gibi, uçma davranışı verilmesidir.
Bu sorun, fly() metodunu RubberDuck sınıfında override ederek çözülebilir. Keza quack() metodunun da benzer bir sorun oluşturduğu ve aynı şekilde çözüldüğü yukarıdaki şekilden anlaşılabilir. Bu çözüm, gelecekte sisteme eklenebilecek olası tüm ördek tiplerinde override işlemi yapılabilir sonucunu da beraberinde getiriyor. Bu, ördek tipleri arasında değişkenlik gösteren fly() ve quack() metodlarının dışarıda interface olarak tanımlanması ve ördek sınıflarının bu interfaceleri implement etmesiyle çözülebilir.
Bu kısmi olarak bize çözüm sağlamış gibi görünüyor. İlgili davranışa sahip ördek tiplerine ait sınıflar, ilgili davranışın interfacesini implement ediyor. Lakin ortaya çok ciddi bir sıkıntı çıkmak üzere. Örneğin şirket yöneticileri ördek tiplerinin uçma davranışında ufacık bir değişiklik yapmak istedi. Bu durum, FlyAble interfacesini implement eden tüm sınıflarda değişiklik yapılması ile sonuçlanacaktır. Aynı sorun ses çıkarma davranışı için de geçerlidir. İşte tam bu noktada strategy pattern devreye girer.
Strategy Pattern, değişkenlik gösteren kısımları encapsulate ederek bunları bağımsız olarak değiştirilebilir, algoritma aileleri haline getirir.
Bizim senaryomuzda, değişkenlik gösteren uçma ve ses çıkarma davranışları için iki farklı interface ve bu interfaceleri implement eden davranış sınıfları oluşturulur.
Görüleceği üzere davranış şekilleri, ilgili interfaceleri implement etmiş durumdalar (Yani FlyWithWings ve FlyNoWay, FlyBehavioru implement etmişler, Quack, Squeak, MuteQuack’da QuackBehaviour’u implement etmiş). Bu yapı sayesinde var olan probleme bir çözüm sunabildiğimiz gibi çalışma zamanında, davranış değişikliği yapmak mümkün hale gelecek.
İlgili yapının Java kodları aşağıdadır.
abstract class Duck{
QuackBehaviour quackBehaviour;
FlyBehaviour flyBehaviour;
public abstract void display();
public void performFly(){
flyBehaviour.fly();
}
public void performQuack(){
quackBehaviour.quack();
}
public void swim(){
System.out.println("All of them can swim");
}
public void setQuackBehaviour(QuackBehaviour qb){
quackBehaviour=qb;
}
public void setFlyBehaviour(FlyBehaviour fb){
flyBehaviour=fb;
}
}
class MallardDuck extends Duck{
MallardDuck(){
quackBehaviour=new Quack();
flyBehaviour= new FlyWithWings();
}
@Override
public void display() {
System.out.println("I am Mallord Duck...");
}
}
class ModelDuck extends Duck{
ModelDuck(){
quackBehaviour=new Quack();
flyBehaviour= new FlyWithWings();
}
@Override
public void display() {
System.out.println("I am Model Duck...");
}
}
interface FlyBehaviour{
public void fly();
}
class FlyWithWings implements FlyBehaviour{
@Override
public void fly() {
System.out.println("I am flying with wings");
}
}
class FlyNoWay implements FlyBehaviour{
@Override
public void fly() {
System.out.println("I can't fly");
}
}
class FlyRocketPowered implements FlyBehaviour{
@Override
public void fly() {
System.out.println("I can fly with rocket");
}
}
interface QuackBehaviour{
public void quack();
}
class Quack implements QuackBehaviour{
@Override
public void quack() {
System.out.printf("It is a normal quack");
}
}
class Squeak implements QuackBehaviour{
@Override
public void quack() {
System.out.println("It's a squeak");
}
}
class MuteQuack implements QuackBehaviour{
@Override
public void quack() {
System.out.println("Silents");
}
}
Kodda görüleceği üzere MallordDuck, default constructerinde FlyAble ve QuackAble tipteki referanslara, FlyAble ve QuackAble interfacelerini implement eden objelerle atamalar yapmaktadır. Bu durum, polymorfizm sayesinde gerçekleşmektedir ve çalışma zamanında bu objeleri, belirtilen interfaceleri implement eden başka objelerle, değiştirmek mümkündür(Yine polymorfizm sayesinde). Çalışma zamanındaki bu değişiklik aşağıdaki gibi olacaktır.
public class Main {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.performFly();
mallard.performQuack();
mallard.setFlyBehaviour(new FlyNoWay());
mallard.setQuackBehaviour(new MuteQuack());
mallard.performFly();
mallard.performQuack();
}
}
Main method bir tane MallordDuck objesi oluşturur ve bu objenin performFly(), performQuack() metodlarını çalıştırır. Daha sonra uçma ve ses çıkarma davranışlarını değiştirebilmek için set metodlarını kullanırak, FlyAble ve QuackAble interfacelerini implement eden classlardan türemiş başka objeleri, yeni davranış olarak atar. Ardından tekrar performFly() ve performQuack() metodlarını çalıştırır. Çıktı ise aşağıdaki gibi olur.
Strategy Pattern, algoritma aileleri tanımlar, her birini encapsulate eder ve değiştirilebilir hale getirir. Böylece algoritmanın, clientin kullandığından bağımsız olarak değiştirilebilir olmasına izin verir.
Daha detaylı bilgiler ve eğlenceli bir anlatım için kitabı okumanızı tavsiye ederim.
Faydalı olması dileğiyle…