Fluent interface a funkcionální programování

Fluent interface (FI) je maličký OOP návrhový vzor pro návrh příjemnějších API. Metoda objektu je fluent, když poté, co provede svojí práci, vrátí objekt samotný. Takto je možné jednotlivá volání plynule řetězit.

Typická ne-fluent metoda vrací void/unit/nic (doufám, že mi prominete, že jsem pro ukázky zvolil programovací jazyk Scala):

def addEntry(e: Entry): Unit =
  this.entries.add(e)

Když pak takovou metodu zavoláme několikrát v řadě, nemáme jinou šanci než to udělat takto:

myObject.addEntry(e1)
myObject.addEntry(e2)
myObject.addEntry(e3)

Fluent metoada může být definována například následujícím způsobem:

def addEntry(e: Entry): MyObject = {
  this.entries.add(e) // side-effect
  this                // return this => fluent
}

Použití je pak o něco příjemnější:

myObject.addEntry(e1).addEntry(e2).addEntry(e3)

Tolik k principům, které jsou stejně všem dobře známé. Teď bych se ale chtěl podívat, jak je na tom FI ve vztahu k funkcionálnímu programování.

Funkcionální kód na první pohled vypadá, že je vždycky a za všech okolností fluent, ale jde jenom o povrchní podobnost. FI je totiž nevyhnutelně spjato s vedlejšími účinky, kterým se funkcionální kód snaží v co největší míře vyhýbat. Každá FI metoda provede nějaké modifikace this objektu a pak ho ve snaze zpohodlnit API vrátí. Naproti tomu (čistě) funkcionální kód vždycky vrátí zcela novou kopii objektu, protože nic jiného ani nemůže udělat, nemůže nijak jinak komunikovat s okolím.

Ale to neznamená, že je FI ve své podstatě špatné. Měnitelná povaha FI nemusí být na škodu, když je použita jako builder, jehož všechny mutace jsou lokální a objekt je po vytvoření zafixován a už se nemění (objekt, metoda nebo funkce může používat divoké změny stavů a vedlejší efekty, ale když vnitřní dovpčinu nikdy nemůžeme pozorovat zvenčí, jde fakticky také o funkcionální kód).


V kódu, který pracuje se neměnnými (immutable) objekty, dostaneme FI většinou zadarmo. Ve Scale je to triviální:

def withEntry(e: Entry) =
  copy(entries = entries :+ e)

Ale naopak je to složitější (FI kódu přidat funkcionální povahu)

def withEntry(e: Entry) = {
  val copy = this.clone
  copy.entries.add(e)
  copy
}

I když můžeme pomocí FI zřetězovat libovolné operace, ne vždycky to dává smysl. Pokud mají jednotlivé metody sémantiku výrazů (expression), je FI naprosto ideální. Když mají sémantiku příkazů (statement), hodí se už podstatně méně.


Jedna malé poznámka na okraj: Ve Scale můžete zaručit, že metoda vrátí stejnou instanci (nejenom typ) tím, že nastavíte návratový typ na this.type.

trait A {
  def getA: A             // vrací nějaký objekt typu A
  def getThisA: this.type // vrací this, kontrolováno kompilátorem
}

Flattr this!

This entry was posted in Funkcionální programování, OOP. Bookmark the permalink.

One Response to Fluent interface a funkcionální programování

  1. v6ak says:

    Jde to i bez fluent interface. Ve Scale si stačí napsat jednoduchou automatickou konverzi a psát něco jako myObject >:> {_.foo()} >:> {_.bar()} >:> {_.gagar()}. Je to delší, ale univerzálnější.

    Jinak fluent interface mi přijde jako takový hack. Sice příjemný a široce používaný i akceptovaný, ale pořád hack.

Leave a Reply

Your email address will not be published. Required fields are marked *