k47.cz  — každý den dokud se vám to nezačne líbit
výběr foto Praha povídky kultura
TECH ▞▞ kolo | twitter RSS

Rozdělení velkých tříd ve Scale

1. 8. 2011 (před 8 lety) — k47 (CC by) (♪)

I když je Scala velice stručný jazyk, může se stát, že jedna třída pře­sáhne ro­zum­nou ve­li­kost a bylo by nej­lepší, kdyby byla roz­dě­lena do sou­borů. Ale jak toho do­sáh­nout, když třída (na rozdíl od jmen­ného pro­storu) musí být v jenom sou­boru?


C# 2.0 pro po­dobné pří­pady zavedl par­tial class, vlast­nost hlavně ur­če­nou pro čás­tečně ge­ne­ro­vané třídy – v jednom sou­boru au­to­ma­ticky vy­ge­ne­ro­vaná část třídy (třeba vý­sle­dek GUI bu­il­deru), ve druhém vlastní kód.

// file1.cs:
public partial class MyClass // klíčové slovo partial se postará o všechnu magii
{
  public void MyMethod1() { /* Manually written code */ }
}
// file2.cs:
public partial class MyClass
{
  public void MyMethod2() { /* Automatically generated code */ }
}

Scala je za­tra­ceně fle­xi­bilní a stej­ného vý­sledku se dá do­sáh­nout bez roz­ší­ření jazyka.

Po­cho­pi­telně se pro tento účel dá zne­u­žít dě­dič­nost: jedna část kódu (třeba ta au­to­ma­ticky vy­ge­ne­ro­vaná) přijde do abs­trakt­ního rodiče, další do po­tomka. Tenhle pří­stup fun­guje, ale jednak zne­u­ží­váme dě­dič­nost pro něco, k čemu není určena a druhak je velice ne­fle­xi­bilní. Po­to­mek může při­stu­po­vat k členům rodiče, ale ne naopak a přesně to po­tře­bu­jeme. Tohle je prin­ci­pi­ální ome­zení dě­dič­nosti a ne­mů­žeme s ním nic dělat. Ale nám nejde o dě­dič­nost, nám jde o roz­dě­lení jedné velké třídy do ně­ko­lika sou­borů. Dě­dič­nost je zkrátka špatný ná­stroj.

Mnohem fle­xi­bil­nější je po­u­žití traitů a self-typů. Můžeme se od­ka­zo­vat na členy ve všech ostat­ních čás­tech a zá­ro­veň můžeme roz­dě­lit třídu do li­bo­vol­ného počtu částí.

// hlavní třída, do které se mixnou všechny ostatní části
class MyClass extends MyClassMixin with MyClassMixin2 {
  val myProperty = "Anomalocaris Detrimentum"
}

trait MyClassMixin { this: MyClass =>
  // můžu přistupovat ke všem členům třídy MyClass a všem mixnutých traitů,
  // protože self-typ zajišťuje, že tento trait bude přimíchán do třídy MyClass
  // nebo nějakého jeho potomka
  def myMethod = myProperty
}

trait MyClassMixin2 { this: MyClass =>
  def myMethod2 = 2084
}

// test //

val c = new MyClass
c.myProperty  // definováno v MyClass
c.myMethod    // definováno v MyClassMixin
c.myMethod2   // definováno v MyClassMixin2

Tohhle pří­stup je za­lo­žen na ná­vr­ho­vém vzoru cake pat­tern, který se byl poprvé uve­řej­něn v článku Sca­la­ble Com­po­nent Abs­tracti­ons. Cake pat­tern je ale mnohem moc­nější, umož­ňuje roz­dě­lit systém na sa­mo­statné kom­po­nenty, které mezi sebou mají zá­vis­losti a pro­vo­zo­vat pl­no­hod­notný de­pen­dency in­jection bez po­u­žití ex­ter­ních ná­strojů.

píše k47 & hosté, ascii@k47.cz