19 Aralık 2019 Perşembe

GoF - Strategy Örüntüsü

Strategy Ne Değildir
Strategy ana algoritmayı gerçekleştiren sınıftan kalıtım değildir! Bu anlayış tamamen yanlış.

Kalıtım varsa bunun ismi Template Method Örüntüsüdür. Strategy ana algoritmayı gerçekleştiren sınıfa takılan davranıştır. Açıklaması şöyle.
Imagine you design a Cache. The cache can have options regarding

- eviction policy (LIFO, FIFO, LRU)
- expiration policy (after read, after write)
- maximum size (number of elements, memory usage)

Now imagine you want to let the user of your cache choose any of these options, and you use inheritance. You'll need 3 * 2 * 2 = 12 different classes:

- LifoAfterReadNumberOfElementsBasedCache,
- LifoAfterReadMemoryBasedCache,
-  etc.

Each of the 4 LifoXxx classes will have to implement the same algorithm to implement the LIFO strategy. Same for the other ones.

Implement it with the strategy pattern instead of inheritance, and you'll have a single Cache class, and 7 strategy classes (one for each strategy), that you can combine however you want, and without code duplication.
Strategy Nedir
Strategy ile tanımlanan arayüze uygun, farklı davranışlar gerçekleştiren sınıflar yazılır. Ve bu sınıflar ana algoritmayı çağıran sınıfa takılırlar. Strategy sınıfları genellikle bir Factory aracılığıyla yaratılır.

Stategy'lerin Parameterik Olması
Örnek
Elimizde şöyle bir kod olsun
public abstract class Temperature {
  public abstract double convert(Temperature to) throws AbsoluteZeroException;
}
Bu sınıftan kalıtan şöyle alt sınıflar olsun
public class Celsius extends Temperature {
  @Override
  public double convert(Temperature to) {
    ...
  }
}
ve
public class Fahrenheit extends Temperature {
 
  @Override
  public double convert(Temperature to) throws AbsoluteZeroException {
    ...
  }
}
Her strategy bir Temperature nesnesi alıyor ancak birimi belli değil.

Çözüm 1
Şöyle yaparız. Her strategy kendi değerini kelvin'e veya başka ortak bir değere çevirir. Ana sınıf bu değerden okur.
public static Temperature convert(Temperature from, TemperatureScale to) {
  double kelvin = from.toKelvin();
  return to.fromKelvin(kelvin);
}
Çözüm 2
Şöyle yaparız. Belki de strategy kullanmaya gerek bile yoktur. Her sınıf kullandığı birimi bilir. Geri kalan her şey parametriktir :)
Tempature celcius = new Tempature(98.6, TempatureType.CELSIUS);
Tempature fahrenheit = Tempature.convert(tempatureCels, TempatureType.FAHRENHEIT);
Enumeration ile Strategy Üretmek
Örnek - Sadece Enum Kullanmak
Şöyle yaparız
public enum FileType {
  EXCEL(".xlsx") {
    @Override
    public void download() {
    }
  },
  CSV(".csv") {
    @Override
    public void download() {
    }
  },
  private String suffix;

  FileType(String suffix) {
    this.suffix = suffix;
  }

  public String getSuffix() {
    return suffix;
  }

  public abstract void download();
}

@GetMapping("/exportOrderRecords")
public void downloadFile(User user, HttpServletResponse response) {
  ...
  String fileType = user.getFileType();
  FileType type = FileType.valueOf(fileType);
  if (type!=null) {
    type.download();
  } else {
    FileType.CSV.download();
  }
}
Örnek - EnumMap
Burada yine bir Strategy arayüzü var ve her strategy kalıtımı için de bir Enum var. Aşağıdaki gibi Strategy sınıfları olsun
public interface class IStrategy {
}
public class DistanceStrategy implements IStrategy {
}
public class PowerStrategy implements IStrategy {
}
Bu sınıflar stateless ise Java'da EnumMap başka dillerde ise herhangi bir Map veri yapısına önceden doldurulurlar ve istenilince döndürülürler
enum DataPresenterStrategyType { DISTANCE, POWER }

static EnumMap<DataPresenterStrategyType, IStrategy> lookupStrategy = new EnumMap();
{
  lookupStrategy.put(DISTANCE, new DistanceStrategy());
  lookupStrategy.put(POWER, new PowerStrategy());
}

DataPresenterStrategy toStrategy(StrategyType type) {
  return lookupStrategy.get(type);
}
Eğer sınıflar statefull ise her seferinde yeni bir nesne yaratmak gerekir. Bu durumda Java 8 ile gelen Supplier arayüzü kullanılabilir. Örnekte Supplier ile Method Expression kullanılıyor.
enum DataPresenterStrategyType { 
  DISTANCE(DistanceStrategy::new), POWER(PowerStrategy::new);
  private final Supplier<DataPresenterStrategy> constructor;

  DataPresenterStrategyType(Supplier<DataPresenterStrategy> constructor){
    this.constructor = constructor;
  }

  DataPresenterStrategy newStrategy() {
    return constructor.get();
  }
}
Strategy ve Functional Programming
GoF - Strategy Örüntüsü - Functional Java yazısına taşıdım

Strategy ve Dependency Injection
Dependency Injection altyapılarında strategy nesneleri bean ise bir kere yaratılır. Bun nesneleri kullanan bir başka bean'e inject etmek bile problem olabilir.

Aynı anda Etkin Birden Fazla Strategy'e Sahip Olmak
Normalde strategylerden sadece bir tanesi etkindir. Her strategy birbiriyle yer değiştirebilen nesne anlamına gelir. Bir nesneye farklı roller eklenebiliyorsa her role strategy demek yanlıştır. Nesneye bir çok rol çalışma zamanında eklenip çıkarılabilir. Bu durumda Decorator Örüntüsüne başvurmak gerekir.



Hiç yorum yok:

Yorum Gönder