26 Temmuz 2021 Pazartesi

GoF - Builder Örüntüsü

Giriş
Temelde iki çeşit Builder örüntüsü var
1. Blind Builder : Ayrı bir sınıftır
2. Bloch Builder : Kullanılmak istenen sınıfın içine public static bir builder yazılır. 
3. Functional Builder : Sınıfa bir şeyler eklemek için array of functions kullanılır

Blind Builder ve Bloch Builder'ın Ortak Özellikleri
Her iki Builder sınıfı da seçime bağlı özellikler için default değerler sağlamalıdır. Zorunlu parametreler zaten kullanıcı tarafından verilecektir.
class Builder {
  private String builderColor = "white"; // default color
  private String builderWheels = "round"; // default wheels
  private int builderSpeed = 100; // default speed
     
}
Build Edilen Sınıf Zorunlu Parametreleri final Yapmalıdır
Bu alanlar artık değişmeyecektir.
public class Car {

    public static Builder builder() {
        return new Builder();
    }

    // static inner class builder
    public static class Builder {
        ...
    }

    // final (immutable) fields.
    private final String color;
    private final String wheels;
    private final int speed;

    // private constructor, only the builder calls this....
    private Car(String color, String wheels, int speed) {
        this.color = color;
        this.wheels = wheels;
        this.speed = speed;
    }
  
}

1. Blind Builder
Builder yarattığı nesneden ayrıdır. Yaratılan nesne ise içine Builder alan private bir constructor veya setter metodları tanımlar. Örnek
class CarBuilder {
  private String wheels;
  private String color;
  private int speed;

  ...
}
Bu Builder tüm zorunlu ve isteğe bağlı parametreleri içinde biriktirir. Daha sonra build metodu ile yeni bir nesne yaratır. Dolayısıyla build metodu aşağıdakine benzer.
public Car build() {
  // validate your attributes
  Car car = new Car();
  // set all attributes in your car
  return car;
}
Eğer gerekirse build metodu içinde verinin doğruluğu da kontrol edilebilir.
public Car build(){
    // validate your values e.g.
    if (speed < 25) {
      throw new IllegalValueException("Car is too slow.");
    }
    // further validation
    Car car = new Car();
    car.setWheels(wheels);
    car.setColor(color);
    car.setSpeed(speed);
    return car;
}

2. Bloch Builder
Bloch Builder, Effective Java kitabında gösteriliyor. Kullanılmak istenen sınıfın içine public static bir builder yerleştiriliyor. Yaratılan nesne ise içine Builder alan private bir constructor tanımlar.

Yukarıdaki örnekte Builder static ve nested bir sınıf. Kullanırken Pizza sınıfını yarattığı kolayca okunabilir.
Pizza pizza = new Pizza.Builder(12)
                       .cheese(true)
                       .pepperoni(true)
                       .bacon(true)
                       .build();
Örnek - Generics Kullanan Block Builder
Açıklaması şöyle. Bir başka açıklama burada
Type parameters are not inherited from outer class to static nested class. 
Bu yüzden şöyle yapmak gerekir
LanguageMatcher.Builder<MyClass, YourClass> lm =
    new LanguageMatcher.Builder<MyClass, YourClass>();
Örnek
Ben şöyle yaptım
public class RemoteMapSourceParams<T, K, V> {
  private final String mapName;
  private final Predicate<K, V> predicate;
  private final Projection<? super Map.Entry<K, V>, ? extends T> projection;

  private RemoteMapSourceParams(Builder<T, K, V> builder) {
    this.mapName = builder.mapName;
    this.predicate = builder.predicate;
    this.projection = builder.projection;
  }

  // Getters
  public static <T, K, V> Builder<T, K, V> builder(String mapName) {
    return new Builder<>(mapName);
  }

  public static class Builder<T, K, V> {
    private final String mapName;
    private String dataConnectionName;
    private ClientConfig clientConfig;
    private Predicate<K, V> predicate;
    private Projection<? super Map.Entry<K, V>, ? extends T> projection;

    public Builder(String mapName) {
      Objects.requireNonNull(mapName, "mapName can not be null");
      this.mapName = mapName;
    }

    //Setters
    public RemoteMapSourceParams<T, K, V> build() {
      return new RemoteMapSourceParams<>(this);
    }
  }
}
3. Functional Builder
Örnek
Elimizde şöyle bir arayüz olsun
@FunctionalInterface
interface Coffee {
  List<String> ingredients();  
        
  static Coffee withSaltedCaramelFudge(Coffee coffee) {
    return () -> coffee.add("Salted Caramel Fudge");
  }

  default List<String> add(String item) {
    return new ArrayList<>(ingredients()) {{
      add(item);
    }};
  }
    
  static Coffee withSweetenedMilk(Coffee coffee) {
    return () -> coffee.add("Sweetened Milk");
  }

  static Coffee withDarkCookieCrumb(Coffee coffee) {
    return () -> coffee.add("Dark Cookie Crumb");
  }

  static Coffee withVanillaAlmondExtract(Coffee coffee) {
    return () -> coffee.add("Vanilla/Almond Extract");
  }
}
Builder olarak şu kodu kullanırız.
@SafeVarargs
static Coffee getCoffeeWithExtra(Coffee coffee, Function<Coffee, Coffee>... ingredients) {
  var reduced = Stream.of(ingredients)
    .reduce(Function.identity(), Function::andThen);
  return reduced.apply(coffee);
}
Bu kodu kullanmak için şöyle yaparız
record CoffeeCup(List<String> initialIngredient) implements Coffee {
  @Override
  public List<String> ingredients() {
    return initialIngredient;
  }
}

var ingredients = List.of("Tim Horton");
var coffeeCup = new CoffeeCup(ingredients);

var coffee = getCoffeeWithExtra(coffeeCup,
  Coffee::withDarkCookieCrumb,
  Coffee::withSaltedCaramelFudge,
  Coffee::withSweetenedMilk,
  Coffee::withVanillaAlmondExtract);

  System.out.println("Coffee with " + String.join(", ", coffee.ingredients()));
}


Hiç yorum yok:

Yorum Gönder