Composition Over Inheritance etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
Composition Over Inheritance etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

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.

3 Nisan 2020 Cuma

Composition Over Inheritance

Giriş
Composition UML terminolojisinde parent child (parça bütün) ilişkisine sahip ve birbirinden ayrılamaz parçalar anlamına gelir. 

Ancak bu yazıdaki Composition kelimesinden kasıt bu değil. Yani UML terminolojisi ile konuşulmuyor. UML ile konuşuyor olsaydık sanırım başlık "Association over Inheritance" olmalıydı. Zaten Composition tanımına bakınca nüanslara takılmayın deniliyor. Kısaca kasıt bir başka sınıfı Field (Üye Alan) olarak kullanmak
Composition: when a Field’s type is a class, the field will hold a reference to another object, thus creating an association relationship between them. Without getting into the nuances of the difference between simple association, aggregation, and composition, let’s intuitively define composition as when the class uses another object to provide some or all of its functionality.
Kalıtım Ne Zaman Kötü
Bazen nesnenin yaşam döngüsü içinde kendiliğinden değişecek şeyler, bileşim yapılacağına kalıtım haline getiriliyor.
Örnek
Elimizde şöyle bir kod olsun. Burada herkese Person nesnesinden kalıtıyor. Student mezun olup Teacher olursa ne olacak ? Buradaki sıkıntı değişebilecek/geçici bir şeyin yani rollerin kalıtım ile ele alınması
class Person {... }
Teacher : Person {...}
class Student : Person {...}

Kalıtımı Bileşim Haline Getirmek
1. Büyük Sınıfları Composition (Birleşim) Haline Getirmek
Bu yöntemde büyük sınıflardaki bazı kodlar XBehavior sınıfına taşınır ve büyük sınıf artık bu kodu çağırır. Açıklaması şöyle.
In object-oriented programming, we can use composition in cases where one object "has" (or is part of) another object. Some examples would be:

- A car has a battery (a battery is part of a car).
- A person has a heart  (a heart is part of a person).
- A house has a living room (a living room is part of a house).
Örnek
Bunu yapabilmek için sınıfa bazı attribute/behavior alanları tanımlamak gerekir. Şöyle yaparız
class Entity
  movable
  harmable
  burnable
  freezable
  ...
Daha sonra farklı özelliklere sahip nesneler tanımlarız. Şöyle yaparız
drunkard = Entity(
  movable=SometimesRandomMovable(),
  harmable=BasicHarmable(),
  burnable=MonsterBurnable(),
  freezable=LoseATurnFreezable()
  ...
)
Şöyle yaparız
ninja = Entity(
  movable=QuickMovable(),
  harmable=WeakHarmable(),
  burnable=MonsterBurnable(),
  freezable=NotFreezable()
  ...
)
Örnek
Yine bir sınıftan kalıtım yerine sınıfa yeni şöyle alanlar ekleriz
public class Person {
  public MarriageStatus marriageStatus;
  public Race race;
  public Wealth wealth;
}
Yeni alan sınıflarını tanımlarız. Şöyle yaparız
public class MarriageStatus {
  public Datetime anniversary;
  public Person husband;
  public Person wife;

  // TODO: In the future the stakeholder would like to support polyamory
  //  public List<Person> spouses;
}
2. Kalıtımı (Inheritance) Kırarak, Birleşim (Composition) Haline Getirmek

Örnek - Uzun Inheritance Zinciri
Elimizde şöyle bir kod olsun.
public class NamedEntity
{
  public int Id { get; set; }
  public string Name { get; set; }
}
Ondan kalıtan bir sınıf daha olsun
public class AuditedEntity : NamedEntity
{
  public DateTime CreatedOn { get; set; }
  public DateTime UpdatedOn { get; set; }
}
Sınıfımız şöyle olsun
public class Three : AuditedEntity
{ }
Bu iki seviye kalıtımı kırmak için iki interface haline getiririz. Şöyle yaparız. Burada yine tam anlamıyla kalıtımdan kaçınılmıyor ancak en azından iki seviye kalıtım yok.
public interface INamedEntity
{
  int Id { get; set; }
  string Name { get; set; }
}

public interface IAuditedEntity
{
    DateTime CreatedOn { get; set; }
    DateTime UpdatedOn { get; set; }
}

public class One 
{ }

public class Two : INamedEntity
{
  public int Id { get; set; }
  public string Name { get; set; }
}

public class Three : INamedEntity, IAuditedEntity
{
  public int Id { get; set; }
  public string Name { get; set; }
  DateTime CreatedOn { get; set; }
  DateTime UpdatedOn { get; set; }
}

public class Four : IAuditedEntity
{
    DateTime CreatedOn { get; set; }
    DateTime UpdatedOn { get; set; }
}
Örnek - Template Pattern
GoF Template Pattern ile ister istemez bir kalıtım oluşuyor. Elimizde şöyle bir kod olsun. Bu kodda esas algoritma Enemy sınıfında. move() metodunda önce Type 2 move yapılıp yapılamayacağı üst sınıfa soruluyor. Cevaba göre Type 1 ve Typ2 move yapılıyor. Ancak Type 3 move de dahil edilmek istenirse işler karışmaya başlıyor.
abstract class Enemy:
  show()         // Called each game tick
  update()       // Called each game tick
  move()         // Tries alternateMove, if unsuccessful, perform type 1 movement
  abstract alternateMove() // Returns a boolean

class Drunkard extends Enemy:
  alternateMove(): return False

class Mummy extends Enemy:
  alternateMove() // Type 2 movement if in range, otherwise return false

class Ninja extends Enemy:
  alternateMove() // Type 3 movement and return true
Bu durumda basit bir çözüm olarak belki şöyle yaparız. Yani move() abstract yaparız. Template pattern'i MovementPlanEnemy sınıfına taşırız. Template olarak çalışmayan Ninja ise move metodunu override ederek hayatına devam eder. Ancak bu sefer kalıtmın bir kısmı template pattern'i kullanır bir kısmı da kullanmaz hale geliyor.
abstract class Enemy:
  show()   // Called each game tick
  update() // Called each game tick
  abstract move() // Called in update

class MovementPlanEnemy:
  move() // Type 1 movement
  abstract alternateMove()

class Drunkard extends MovementPlanEnemy:
  alternateMove() // Return false

class Mummy extends MovementPlanEnemy:
  alternateMove() // Tries type 2 movement

class Ninja extends Enemy:
  move() // Type 3 movement
Bir diğer çözüm ise template pattern'i tamamen bozarak herkesin kendi move() metodunu gerçekleştirmesi. Şöyle yaparız. Her move() metodu ortak kodları yine XBehavior sınıflarına delegate edebilir.
abstract class Enemy:
  show()   // Called each game tick
  update() // Called each game tick
  abstract move() // Called in update

class Drunkard extends Enemy:
  move() // Type 1 movement

class Mummy extends Enemy:
  move() // Type 1 + type 2 movement

class Ninja extends Enemy:
  move() // Type 3 movement
Örnek - Multiple Inheritance
Multiple inheritance single responsibility kuralını ihlal ediyor gibi görünüyor. Refactoring yaklaşımında multiple inheritance, composition (bileşim) ile değiştiriliyor. Böylece sınıfın tek bir iş yapması sağlanıyor.
Aşağıda multiple inheritance yerine composition getirilmesini gösteren bir örnek var. Sprite sınıfı ilk başta hem Node hem de kaynakları otomatik olarak bırakan Resource sınıfından türüyor.

Composition haline getirmek için önce iki tane arayüz tanımlanıyor.
// traditional part

interface Node {
  Position getPosition();
  // anything else
}

interface Resource {
  void allocate(...);
  void destroy();
}
Daha sonra bu arayüzleri döndüren iki yeni XHolder arayüzü tanımlanıyor.
// composition interfaces

interface NodeHolder {
  Node asNode(); // the only method
}

interface ResourceHolder {
  Resource asResource(); // the only method
}
Nihayetinde Sprite sınıfı her iki XHolder arayüzünden kalıtıyor.
class Sprite 
  extends Node 
  implements NodeHolder, ResourceHolder 
{
  private Resource my_resource;
  public Sprite(...) {
    // whatever construction needed
    my_resource = new ResourceImpl();
  }
Örnek - Multiple Inheritance
Elimizde şöyle bir kod olsun.
class Application implements DatabaseReader, DatabaseWriter, UserInteraction,
  Visualizer {
    ...
}
DatabaseReader ve DatabaseWriter arayüzlerini kalıtımdan kurtarıp compositon yapısına getirmek için şöyle yaparız.
interface DatabaseReader { String read(); }
interface DatabaseWriter { void write(String s); }

class Database {
    DatabaseConnection connection = create();
    DatabaseReader reader = createReader(connection);
    DatabaseReader writer = createWriter(connection);

    DatabaseReader getReader() { return reader; }
    DatabaseReader getWriter() { return writer; }
}
3. Functinal Yöntemler
Composition Over Inheritance Functional Yöntemler yazısına taşıdım