Decorator Tasarım Kalıbı ve Kullanımı
Burada Decorator
tasarım kalıbınının kullanımına yönelik örnek bir çalışma
yapılmıştır. Öncelikle örnek bir uygulama verilmiş ve bu örnek
uygulamadaki yetersizlikler belirtilerek, daha ideal bir tasarımın
Decorator tasarım kalıbı kullanarak nasıl yapılabileceği
anlatılmıştır.
Bu örnek hazırlanırken “Head First Design Patterns” kitabında verilen içecek örneğinden esinlenilmiştir. [1]
Bu örnek hazırlanırken “Head First Design Patterns” kitabında verilen içecek örneğinden esinlenilmiştir. [1]
Kahve Dükkanı Uygulaması
"Kahve Dükkanı" isimli kahve ve çeşitli içecekler satan bir firmanın yönetimi
için geliştirilen uygulamanın tasarımı aşağıdaki şekilde
verilmiştir.
Firmanın
işlerini geliştirmesiyle birlikte, sundukları içecek
seçeneklerine göre yazılımı geliştirmek gerekmektedir. Yeni
gereksinimlere göre, kahve ve benzeri içecekler süt, karamel,
krema, mocha gibi ekstra tatlarla sunulabilmektedir. Sunulan bu
ekstra tatlara göre de içecek fiyatlarına ekstra ücret
eklenmektedir. Mevcut yazılıma bu ihtiyaca cevap verecek şekilde
yeni eklentiler yapılması için çalışma başlatılmıştır.
- Gereksinim: İceceklerin aşağıdaki ekstra tatlarla, ekstra ücretleri eklenerek servis edilebilmesinin sağlanması.
- Süt: 0.10 TL
- Karamel: 0.10 TL
- Krema: 0.15 TL
- Mocha: 0.20 TL
Öncelikle kalıtım
esasına uygun olarak, bu eklentilerin yeni içecek olarak sisteme
tanımlanması düşünülmüştür. Bunun için, SutluEspresso,
KaramelliEspresso,
KremaliEspresso,
MochaliEspresso,
SutluKaramelliEspresso ve
bunun gibi yeni sınıflar eklenmesi düşünülmüştür. Ancak bu
yöntem, her bir içecek için 4 yeni sınıf, toplamda 5 içecek
için 20 yeni sınıf eklenmesi anlamına geldiği için tercih
edilmemiştir. Ayrıca bu yöntemde, her yeni içecek ve ekstra tat
için eklenecek sınıf sayısının üssel olarak artacak olması
sıkıntı yaratmıştır. Bu tip bir uygulamanın bakım ve yönetimi
imkansız hale gelecektir. Bu yöntemden vazgeçildikten sonra daha
akılcı bir dizayn oluşturulmaya çalışılmış ve ilk dizayn
oluşturulmuştur.
İlk Dizayn
Yeni
dizaynda ana Icecek
sınıfı üzerine boolean değişkenler eklenerek içeceğin sütlü,
karamelli, kremalı ya da mochalı olup olmadığının bu
değişkenlerle kontrol edilmesi tasarlanmıştır. Bu şekilde
kalıtım ilişkisiyle her bir içeceğin hangi ekstra tadı içerip
içermediği kontrol edilebilecektir.
Ek fiyatlandırma
için ise ana Icecek sınıfındaki fiyatlandir()
metodu yeni eklenen boolean değişkenlere göre ek fiyatı
hesaplayacaktır. Alt sınıflar ise kendi fiyatlandir()
metodlarında kendi hesaplarına ek olarak, üst sınıfın metodunu
da super anahtar kelimesi ile çağıracaklar ve bu ikisinin sonucunu
toplayacaklardır.
- public abstract class Icecek {protected String aciklama = "Icecek";private boolean sutlu;private boolean karamelli;private boolean kremali;private boolean mochali;public String getAciklama() { return aciklama; }public double fiyatlandir(){int ekFiyat = 0;if(sutlu){ekFiyat += 0.10;}if(karamelli){ekFiyat += 0.10;}if(kremali){ekFiyat += 0.15;}if(mochali){ekFiyat += 0.20;}return ekFiyat;}// getter ve setter metodlari// ...}
- public class Espresso extends Icecek {public Espresso() { aciklama = "Espresso"; }public double fiyatlandir() {return 1.99 + super.fiyatlandir();}}
Yapılan
dizaynın UML diyagramı aşağıda gösterilmiştir.
Bu
dizaynın örnek bir kullanımı aşağıda verilmiştir:
- public class KahveDukkani {
public static void main(String args[]) {
Icecek icecek1 = new Espresso();System.out.println(icecek1.getAciklama()+ " $" + icecek1.fiyatlandir());
Icecek icecek2 = new Neskafe();icecek2.setMochali(true);icecek2.setKaramelli(true);System.out.println(icecek2.getAciklama()+ " $" + icecek2.fiyatlandir());
Icecek icecek3 = new FiltreKahve();icecek3.setKaramelli(true);icecek3.setSutlu(true);System.out.println(icecek3.getAciklama()+ " $" + icecek3.fiyatlandir());
}}
İlk Dizayn Kusurları
İlk
dizayn özellikle kalıtım ile çözme fikri ile kıyaslandığında,
ilk bakışta düzgün bir tasarım gibi görünse de incelendiğinde
aşağıdaki sıkıntılar tespit edilmiştir.
- Mevcut dizayn çifte kremalı, çifte karamelli gibi müşterinin taleplerini desteklememektedir. Bu durum müşteri memnuniyetsizliğine yol açabilecek ciddi bir problemdir.
- Her ekstra tat, her içeceğe uygun olmayabilir. Örneğin buzlu çaya süt ya da mocha katmanın bir anlamı yoktur. Buna rağmen BuzluCay sınıfı ana sınıftaki isSutlu(), setSutlu(), isMochali(), setMochali() gibi metodları da kalıtımla miras almış olur.
- Ekstra tatlardaki fiyat değişimleri nedeniyle Icecek sınıfındaki fiyatlandir() metod kodunu değiştirmemiz gerekecektir.
- Yeni bir ekstra tat tanımı için, Icecek sınıfına yeni boolean değişkenler, yeni getter ve setter metodları eklemek gerekecek ve fiyatlandir() metod kodunu değiştirmemiz gerekecektir.
- Alt sınıfların üst sınıftan override ettikleri fiyatlandir() metodunda hesap yaparken, üst sınıftaki fiyatlandir() metodunu super ile çağırması hem bir yinelenen kod durumu oluşturmakta hem de hatalara neden olabileceği için sakıncalı görünmektedir. Yazılımcı her bir alt sınıfta aynı eklemeyi yapacağı için, bu eklemeyi yapmayı unutabilir ya da toplama yerine çıkarma işareti koyma gibi hatalar yapabilir. Bunun yerine şablon metod oluşturma (form template method) çözümüne gidilebilir. fiyatlandir() metodu ikiye yarılarak esktraUcret() ve icecekUcreti() gibi iki parçaya ayrılabilir. ekstraUcret() metodu yine ust sınıfta tanımlanırken, icecekUcreti() metodu üst sınıfta abstract olarak tanımlanır ve gerçeklenmesi alt sınıfa bırakılır. Bu çözum aşağıda kabaca gösterilmiştir:
- public double fiyatlandir(){return icecekUcreti() + ekstraUcret();}public double ekstraUcret(){int ekFiyat = 0;if(sutlu)ekFiyat += 0.10;if(karamelli)ekFiyat += 0.10;if(kremali)ekFiyat += 0.15;if(mochali)ekFiyat += 0.20;return ekFiyat;}public abstract double icecekUcreti();
- Sınıflardaki aciklama değişkeni, içecek hakkında bilgiler sunmaktadır. Mevcut dizaynda, bu açıklamalar içeceğe katılan ekstra tatlar hakkında bilgi vermemektedir. Bunu sağlamak için fiyatlandir() metodunda yapıldığı gibi her bir içeceğin sütlü, karamelli, kremalı ya da mochalı olup olmadığı kontrol edilip bu özellikler açıklanmaya eklenmelidir. Tabi ki bu da kodun bakımı için daha önceden açıklanan sıkıntıların burada da söz konusu olmasına yol açacaktır.
- Söz konusu tasarım “Sınıflar eklentiye açık, fakat değişikliğe kapalı olmalıdır” prensibine uygun değildir. Fiyat değişimi, eskstra tat eklenmesi gibi değişikler mevcut sınıflardaki kodların değişmesi ihtiyacına yol açmaktadır.
Yeni Tasarım
İlk
tasarımdaki eksiklikler göze alındığında yeni bir tasarım
geliştirilmesi ihtiyacı doğmuştur. Bir dizayn ilkesi olarak
değişen kısımları bulup, onları sarmalamalıdır. Bu nedenle,
yukarıdaki tasarımda olduğu gibi ekstra tatları içeceğin bir
özelliği olarak tutmak yerine ayrı bir soyutlamada tutmak
gerekmektedir.
Gereksinimler
gözden geçirildiğinde görülmektedir ki, içeceğe ekstra bir tat
eklendiğinde ortaya yine bir içecek çıkmaktadır. Yani asıl
yaptığımız bir içecek oluşturmak, ona çeşitli ekstra tatlar
ekleyerek içeceği süslemek ve ortaya yeni bir içecek çıkarmaktır.
Bu gereksinimi sağlamak için “Decorator” tasarım kalıbı
uygun görünmektedir.
Decorator
tasarım kalıbında amaç, nesnelere çalışma anında (dinamik
olarak) ek sorumluluklar atamaktır. Bu kalıbın bazı özellikleri
şunlardır:
- Decorator sınıfı, etkilemek istenen sınıfın türünde tanımlanır.
- Bir ya da birden fazla Decorator kullanarak, etkilenmek istenen obje saramalanabilir.
- Decorator sınıfı da üst sınıfın tipinde olduğu için, bir üst sınıf gibi davranır.
- Nesneler dinamik olarak Decorator ile sarmallanarak nesneye yeni özellikler kazandırılabilir.
- Decorator sınıfının operation() metodu kendi ekleyeceği işlemleri tanımladıktan sonra ya da önce, sarmalladığı nesnenin operation nesnesini çağırır.
Yeni
tasarımda Icecek sınıfı
Component rolündedir. Espresso,
FiltreKahve,
KafeinsizKahve, Nescafe
ve BuzluCay sınıfları
ise ConcreteComponent rolündedirler. Decorator rolünde ise EkstraTatDecorator isimli bir sınıf tanımlanmıştır. Sut,
Karamel, Krema
ve Mocha ise
EkstraTatDecoraton'dan türemiş ConcreteDecorator sınıflarıdır.
Sonuçta ortaya
çıkan tasarım aşağıdaki şekilde verilmiştir.
Decorator tasarım
kalıbına uygun olarak tanımlanan EkstraTatDecorator ve örnek bir
alt sınıfı aşağıda verilmiştir.
- public abstract class EkstraTatDecorator extends Icecek {protected Icecek icecek;public EkstraTatDecorator(Icecek icecek) {this.icecek = icecek;}public abstract String getAciklama();}
- public class Karamel extends EkstraTatDecorator {public Karamel(Icecek icecek) { super(icecek); }public String getAciklama() {return icecek.getAciklama() + ", Karamel";}public double fiyatlandir() {return .10 + icecek.fiyatlandir();}}
Görüldüğü
üzere Karamel sınıfı fiyatlandırma hesabı yaparken önce kendi
ücretini eklemekte, daha sonra eklendiği içeceğin ücretini
toplayarak sonucu dönmektedir. Bu şekilde rekursif mantığa benzer
şekilde, her decorator bu işlemi tekrarlamakta ve en son bir
componente gelindiğinde sonuç fiyatlandırması hesaplanmış
olmaktadır.
Bu
dizaynın örnek bir kullanımı aşağıda verilmiştir:
- public class KahveDukkani {public static void main(String args[]) {
Icecek icecek1 = new Espresso();System.out.println(icecek1.getAciklama()+ " $" + icecek1.fiyatlandir());
Icecek icecek2 = new Neskafe();icecek2 = new Mocha(icecek2);icecek2 = new Mocha(icecek2);icecek2 = new Karamel(icecek2);System.out.println(icecek2.getAciklama()+ " $" + icecek2.fiyatlandir());
Icecek icecek3 = new FiltreKahve();icecek3 = new Krema(icecek3);icecek3 = new Mocha(icecek3);icecek3 = new Karamel(icecek3);System.out.println(icecek3.getAciklama()+ " $" + icecek3.fiyatlandir());}}
Kaynak Kodlar
Dizaynın
son halinin kaynak kodları sınıf sınıf aşağıda verilmiştir:
- public class KahveDukkani {public static void main(String args[]) {
Icecek icecek1 = new Espresso();System.out.println(icecek1.getAciklama()+ " $" + icecek1.fiyatlandir());
Icecek icecek2 = new Neskafe();icecek2 = new Mocha(icecek2);icecek2 = new Mocha(icecek2);icecek2 = new Karamel(icecek2);System.out.println(icecek2.getAciklama()+ " $" + icecek2.fiyatlandir());
Icecek icecek3 = new FiltreKahve();icecek3 = new Krema(icecek3);icecek3 = new Mocha(icecek3);icecek3 = new Karamel(icecek3);System.out.println(icecek3.getAciklama()+ " $" + icecek3.fiyatlandir());}}
- public abstract class Icecek {protected String aciklama = "Icecek";
public String getAciklama() {return aciklama;}public abstract double fiyatlandir();}
- public class Espresso extends Icecek {public Espresso() {aciklama = "Espresso";}public double fiyatlandir() {return 1.99;}}
- public class FiltreKahve extends Icecek {public FiltreKahve() {aciklama = "Filtre Kahve";}
public double fiyatlandir() {return .89;}}
- public class KafeinsizKahve extends Icecek {public KafeinsizKahve() {aciklama = "Kafeinsiz Kahve";}
public double fiyatlandir() {return 1.05;}}
- public class Neskafe extends Icecek {public Neskafe() {aciklama = "Neskafe";}
public double fiyatlandir() {return .99;}}
- public class BuzluCay extends Icecek {public BuzluCay() {aciklama = "Buzlu Cay";}
public double fiyatlandir() {return 2.99;}}
- public abstract class EkstraTatDecorator extends Icecek {protected Icecek icecek;public EkstraTatDecorator(Icecek icecek) {this.icecek = icecek;}
public abstract String getAciklama();}
- public class Sut extends EkstraTatDecorator {public Sut(Icecek icecek) { super(icecek); }public String getAciklama() {return icecek.getAciklama() + ", Sut";}public double fiyatlandir() {return .10 + icecek.fiyatlandir();}}
- public class Karamel extends EkstraTatDecorator {public Karamel(Icecek icecek) { super(icecek); }public String getAciklama() {return icecek.getAciklama() + ", Karamel";}public double fiyatlandir() {return .10 + icecek.fiyatlandir();}}
- public class Krema extends EkstraTatDecorator {public Krema(Icecek icecek) { super(icecek); }public String getAciklama() {return icecek.getAciklama() + ", Krema";}public double fiyatlandir() {return .15 + icecek.fiyatlandir();}}
- public class Mocha extends EkstraTatDecorator {public Mocha(Icecek icecek) { super(icecek); }public String getAciklama() {return icecek.getAciklama() + ", Mocha";}
public double fiyatlandir() {return .20 + icecek.fiyatlandir();}}
Kaynaklar
[1] Elisabeth
Freeman, Eric Freeman, Bert Bates, and Kathy Sierra. 2004. Head First
Design Patterns. O' Reilly & Associates, Inc..c
[2] Alan
Shalloway and James Trott. 2004. Design Patterns Explained: A New
Perspective on Object-Oriented Design (2nd Edition) (Software
Patterns Series). Addison-Wesley Professional.
[3] Yunus Emre
Selcuk, YTÜ Nesneye Dayalı Tasarım ve Modelleme Yüksek Lisanas
Ders Notları, 2013
1 yorum:
Eline sağlık : )
Bu arada Head First Design Patterns kitabına biraz bakmıştım. Çok güzel anlatımlara, görsellere ve örneklere sahip. Okumak lazım
Yorum Gönder