k47.cz
mastodon twitter RSS
bandcamp explorer

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

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

I když je Scala velice stručný jazyk, může se stát, že jedna třída přesáhne rozumnou velikost a bylo by nejlepší, kdyby byla rozdělena do souborů. Ale jak toho dosáhnout, když třída (na rozdíl od jmenného prostoru) musí být v jenom souboru?


C# 2.0 pro podobné případy zavedl partial class, vlastnost hlavně určenou pro částečně generované třídy – v jednom souboru automaticky vygenerovaná část třídy (třeba výsledek GUI builderu), 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 zatraceně flexibilní a stejného výsledku se dá dosáhnout bez rozšíření jazyka.

Pochopitelně se pro tento účel dá zneužít dědičnost: jedna část kódu (třeba ta automaticky vygenerovaná) přijde do abstraktního rodiče, další do potomka. Tenhle přístup funguje, ale jednak zneužíváme dědičnost pro něco, k čemu není určena a druhak je velice neflexibilní. Potomek může přistupovat k členům rodiče, ale ne naopak a přesně to potřebujeme. Tohle je principiální omezení dědičnosti a nemůžeme s ním nic dělat. Ale nám nejde o dědičnost, nám jde o rozdělení jedné velké třídy do několika souborů. Dědičnost je zkrátka špatný nástroj.

Mnohem flexibilnější je použití traitů a self-typů. Můžeme se odkazovat na členy ve všech ostatních částech a zároveň můžeme rozdělit třídu do libovolné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 založen na návrhovém vzoru cake pattern, který se byl poprvé uveřejněn v článku Scalable Component Abstractions. Cake pattern je ale mnohem mocnější, umožňuje rozdělit systém na samostatné komponenty, které mezi sebou mají závislosti a provozovat plnohodnotný dependency injection bez použití externích nástrojů.

píše k47, ascii@k47.cz