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



Hiç yorum yok:

Yorum Gönder