24 Ağustos 2020 Pazartesi

GoF - Decorator Örüntüsü - Kalıtım Kullanarak

Giriş
Decorator'ün çalışabilmesi için sarmalanan nesnenin, bir arayüzden kalıtması gerekir. Yeni geliştirilen decorator da aynı arayüzden kalıtır.
The decorator implements the same interface as the type it decorates, each method calling the decorated type's members, adding functionality as needed.
Örnek
Örneğin ICar isimli bir arayüzümüz olsun.
public interface ICar
{
  void Drive();
  void Stop();
  void Park();
}
Basit bir decorator her işlemi sarmaladığı nesneye gönderir.
class Decorator : ICar
{
  private readonly ICar _car;

  public Decorator(ICar car) {
    _car = car;
  }

  public void Drive()
  {
    _car.Drive();
  }

  public void Stop()
  {
    _car.Stop();
  }

  public void Park()
  {
    _car.Park();
  }
}
Örnek
Elimizde şöyle bir kod olsun. Bu kod "DarkRoast with double mocha and a whip" yaratır.
Beverage beverage = new DarkRoast();
beverage = new Mocha(beverage);
beverage = new Mocha(beverage);
beverage = new Whip(beverage);
Bazı Örnekler
Kullanımına bazı iyi örnekler:

1. O ana kadar sınıf hiyerarşisinden bulunmayan yeni bir özelliğin eklenmesi. Örneğin widget hiyerarşisinin draw() metoduna vertical/horizontal scroll bar veya border eklenmesi gibi.

2. Miktarı farklı ilaveler kullanarak fiyat hesaplanması. Örneğin süt vs. gibi ilaveler kendi fiyatlarını da sarmalanan kahve üzerine ekleyerek yeni bir hesaplama yapmamıza imkan tanıyor.

3. Loglamada hem loglama yapılması, hem de eğer log belli bir cümleyi içeriyorsa tweet atılması.Örnekte TweetingFileWriter gerekiyorsa Tweet atıyor.

Benim Gördüğüm Bazı Örnekler
1. Bir kaynaktan gelen event'leri işleyem callback yapısı vardı. Daha sonra bu event'leri sıralı işleme ihtiyacı ortaya çıkınca event'leri belli bir mantığa göre sıralayan bir callback decorator gördüm.


Bazı sıkıntılar
1. Eğer sarmalanan nesne çok büyük bir arayüze sahipse, decorator örüntüsü bir sürü metodu sarmalanan nesneye delege etmek zorunda kaldığı için kullanışsız hale gelebilir. Örnek'te durum görülebilir.
//decorator has to implement it, but just passes the operation
public ITearResult tear() {
    return page.tear();
}
2. Eğer kod nesnenin tipine göre çalışıyorsa decorator kodu bozabilir. Bu madde Head First Design Patterns kitabından anlatılıyor. Örneğin b nesnesini sarmalayan bir decorator yaratalım.
I b = new B; // I is an interface implemented by B
b = D(b); // the decorator D implements I as well
doSomething(b);
Eğer kod şöyle çalışıyorsa Decorator işlevi bozar.
doSomething(I i){
    if i instanceof B
        doThis();
    else
        doThat();
}
Dikkat Edilmesi Gereken Noktalar
Decorator Liskov Kuralına uygun davranmalıdır. Yani kalıttığı arayüzü bozmamalıdır.

Örnek
Örnekte WorkBank arayüzü görülüyor. Bu arayüz'den kalıtan SomeWordBank getWords() metodu ile bir dizi kelime dönüyor. WordSorter bir decorator olarak kelimeleri sıralayarak döndürüyor. Eğer arayüzümüz çift kelimelere izin verseydi ve WordSorter çift kelimeleri süzerek döndürseydi Liskov kuralını ihlal ettiği için iyi bir decorator olmazdı.


Decorator Nasıl Yaratılır
Bir çok decorator basit bir şekilde constructor metoduna sarmayalacağı nesneyi alır. Çok nadiren de olsa Decorator yaratmak için Factory kullanılabilir.

Örnek
Elimizde şöyle bir kod olsunn-
public interface Animal {
  void eat();
}

public class Lion implements Animal {
  public void eat() {
    // do something
  }
}

/* In the original Decorator pattern, 
the decorator is an abstract class, 
but for the sake of brevity, 
in this example it's a concrete class. */
public class AnimalWithEatCountDecorator implements Animal {
  private Animal animalWeWantToCountEats;
  private int eatCount=0;

  public AnimalWithEatCountDecorator(Animal animal) {
    this.animalWeWantToCountEats= animal;
  }
        
  public void eat(){ 
    this.animalWeWantToCountEats.eat();
    this.eatCount++;
  }
        
  public int getEatCount() {
    return this.eatCount;
  }   
}  
Şöyle yaparız
public static void main(String[] args) {
  AnimalWithEatCountDecorator lion = new AnimalWithEatCountDecorator(new Lion());
  lion.eat();
  lion.eat();
  lion.eat();
        
  System.out.println(lion.getEatCount());
}
Örnek
Şöyle yaparız. Factory verilen path'i InputStream ve daha sonra BufferedInputStream ile sarmalıyor. Ek olarak eğer dosya ismi .gz ile bitiyorsa GZIPInputStream ile de sarmalayıp sarmalamamaya karar veriyor.
public static InputStream openFile(File file) throws IOException {
  InputStream stream = null;
  try {
    stream = new FileInputStream(file);
    stream = new BufferedInputStream(stream);
    if (file.getName().endsWith(".gz"))
       stream = new GZIPInputStream(stream);
    return stream;
  } catch (IOException ex) {
    closeQuietly(stream);
    throw ex;
  }
}

Hiç yorum yok:

Yorum Gönder