11 Aralık 2020 Cuma

Composition Over Inheritance Functional Yöntemler

Giriş
Bu yazıda kalıtım yerine, birleşim kullanmak için tercih edilebilecek functional yöntemler var. 

Birleşimde metodlar genellikle delegate gibi çalışıyorlar.

Predicate
Örnek - Filterer
Klasik kalıtım yaklaşımında bir sınıfın döndürdüğü listeyi filtrelemek için X sınıfdan kalıtan yeni XFilterer kalıtılır. XFilterer çağrıyı önce sarmaladığı X nesneine yönlendirir ve sonucu süzerek döndürür. Bu yaklaşım yerine X sınıfına basit bir Predicate alan get metodu yazarak, sonucu yine süzerek alabiliriz.

BiFunction
Elimizde şöyle bir kod olsun. Bu kod SOLID - Open-Closed Principle 'a uygun değil, çünkü eski kod tüm hesaplama mantığının içerdiği için her türlü yeni istekte değişmek zorunda kalıyor
class Movie {
  enum Type {
    REGULAR, NEW_RELEASE, CHILDREN
  }
  private final Type type;

  public Movie(Type type) {
    this.type = type;
}

  public int computePrice(int days) {
    switch (type) {
      case REGULAR: return days + 1;
      case NEW_RELEASE: return days * 2;
      case CHILDREN: return 5;
      default: throw new IllegalArgumentException(); // Always have this here!
    }
  }
}
Bu kodu inheritance kullanarak daha Object Oriented hale getiriyoruz. Bu aslında bir Domain Modeli oluşturmaya benziyor. Her Command hesaplama mantığını kendi içinde taşıyor. Ancak bu durumda kalıtım ortaya çıkıyor. 
abstract class Movie {
  public abstract int computePrice(int days);
}

class RegularMovie extends Movie {
  public int computePrice(int days) {
    return days+1;
  }
}
class NewReleaseMovie extends Movie {
  public int computePrice(int days) {
    return days*2;
  }
}
class ChildrenMovie extends Movie {
  public int computePrice(int days) {
    return 5;
  }
}
Şu anki Movie kriterleri belki yeterli ancak genellikle şu oluyor

1. Bambaşka bir movie kriteri geliyor ve computePrice() metoduna ihtiyacı olmuyor fakat extends ilişkisi sebebiyle override etmek zorunda kalıyor. Çözüm olarak computePrice() metodunu daha genel bir isim yapabiliriz. execute() gibi :) Domain modelinde bu fikir tabii ki iyi değil.

2. Herhangi bir kriter sabit değeler yerine veri tabanından, konfigürasyondan bir şey okumak zorunda kalıyor. Bu durumda ya ata sınıfa ya da bazı sınıflara değişiklik yapmak gerekiyor. Her ikisi de kötü çünkü Movie aslında bir domain object veya 

Movie sınıfı şöyle olabilirdi. Yani kendisi bir şey bilmeden sadece belirtilen metodu verilen parametre ile çalışır ve bir sonuç döner. Bir çeşit delegate. 
public class Movie
  public enum Type {
    REGULAR(PriceService::computeRegularPrice),
    NEW_RELEASE(PriceService::computeNewReleasePrice),
    CHILDREN(PriceService::computeChildrenPrice);
    
public final BiFunction<PriceService, Integer, Integer> priceAlgo;
    
private Type(BiFunction<PriceService, Integer, Integer> priceAlgo) {
      this.priceAlgo = priceAlgo;
    }
  }
  ...
}
Bu durumda şöyle kullanırım. Gerekli metodlar PriceService içinddir Aslında bu da farklı bir probleme Anemic Domain Model'e sebep veriyor :)
class PriceService {
  ...
  public int computePrice(Movie.Type type, int days) {
    return type.priceAlgo.apply(this, days);
  }
}
Farklı bir çözüm olarak hesaplama kodu yine Movie nesnesinde kalabilir ancak gerekli parametreler Movie nesnesine takılan functional delegate ile elde edilebilir.

Hiç yorum yok:

Yorum Gönder