Náhrada návrhových vzorů ve funkcionálním programování

Programátoři ve funkcionálních jazycích nepoužívají terminologii návrhových vzorů. Používají stejné mechanismy, ale mají pro ně jiné názvy. Jednomu by se pak mohlo zdát, že design patterns jsou zbytečné, ono to tak není. Spíš funkcionální jazyky, tím, že jsou expresivnější, než jazyky objektové, můžou takový dojem pouze vytvářet.

Článek původně vyšel na autorově blogu. Jiří Knesl pomáhá IT firmám k vyššímu zisku pomocí zvyšování kvality a produktivity vývojářů a IT managementu. Více se o něm dozvíte na www.knesl.com.

Internetem proběhl takový drobný kopanec do návrhových vzorů:

fpvsoo

Samozřejmě, v tomto obrázku není žádná faktická chyba. Ve funkcionálním programování je vše řešeno funkcemi (nečekaně). Na levé straně mohly být všude nápisy „Classes and objects“. A pak co, prostě různá paradigmata používají různé výrazové prostředky.

Funkce jsou univerzálnější, ale použít objektový jazyk neznamená, že něco nejde naprogramovat.

Pojďme se podívat na to, jak funkcionální programování nahrazuje některé návrhové vzory.

Factory pattern

Návrhový vzor Factory funguje tak, že zodpovídá za vytváření, konfiguraci a mnohdy i Dependency Injection objektu, u kterého je tato činnost příliš složitá, než abyste ji měli v DI kontejneru, případně rozstrkanou po celé aplikaci, pokud nepoužíváte DI.

Factories bývají často statické třídy s metodou create, což je ale implementační detail, ne podmínka.

Ve funkcionálním programování můžeme chápat tuto situaci tak, že např. máme složitou funkci, která očekává řadu parametrů.

mojefunkce(fn1, fn2, config1, config2, config3, par1, par2)

Vše mimo par1 a par2 chceme předat jednou, z jednoho místa.

Jednoduché řešení je vytvořit novou funkci:

function mojefunkce2(par1, par2) {
        return mojefunkce(fn1, fn2, config1, config2, par1, par2);
}

Tato nová funkce nastaví fn1, fn2, config1, config2 z nějakých svých zdrojů stejným způsobem, jako to udělá factory.

Výsledný zdroják je o trošku jednodušší než OOP varianta, ale ne o moc.

Existuje ještě jedna věc, jak vše zjednodušit a to s pomocí automatic partial application, kterou jsem zde už mnohokrát zmiňoval.

Pak lze vytvoření takové factory zapsat jako:

mojefunkce2 = mojefunkce(fn1, fn2, config1, config2)

Předal jsem jen ty parametry, které znám, a nechám zbytek předat v okamžiku, kdy je funkce aplikována.

Strategy pattern

Strategy slouží pro situace, kdy máme více algoritmů, každý z nich může nahradit původní. Hodí se v situacích, kdy například komprimujete data, vypočítáváte cenu pro zákazníka, renderujete detail položky atd.

Principem, stejně jako u návrhového vzoru Adapter, je stejné rozhraní.

U funkcionálního programování není téměř co ukazovat, princip je stejný. Vytvoříte funkce, které čekají stejné parametry a máte hotovo. Tzn. místo toho, abyste měli X tříd, které implementují interface IPricingStrategy, máte x funkcí se stejným interfacem, jako by měla metoda počítající cenu v realizaci IPricingStrategy (za předpokladu, že jste už předali nějak závislosti, např. postupem, který jsem popsal v kapitole Factory pattern).

Decorator patten

Návrhový vzor Decorator se nějčastěji používá pro situace, kdy potřebujete rozšířit funkčnost existující třídy bez toho, abyste do ní zasáhli a bez toho, aby ona třída měla nějakou explicitní podporu pro vlastní rozšiřování (typicky tak, že čeká, že jí předáte nějaké dependencies, které tato třída bude volat).

Nejčastější realizace je buď přes dědičnost a překrytí metod, jejichž chování měníte, nebo přes delegaci, kdy původní objekt držíte v atributu a kdy implementujete stejný interface, všechna volání delegujete dál mimo těch, kde chcete nějaké své vlastní chování.

Ve funkcionálním programování byste problém mohli řešit podobným způsobem. Měli byste funkci, která implementuje stejné rozhraní, udělá si své a pokud chce, zavolá i původní funkci.

Dependency Injection

Dependency Injection je bestie, která slouží mnoha pánům. Umožňuje unit testování, vyjasňuje závislosti, které objekt má, zvyšuje flexibilitu při použití objektů. Spousta výhod, které jsou opravdu skvělé.

V FP byste použili takzvané High Order Funkce, tzn. psali byste funkce, které očekávají (obecně HOF čekají nebo vrací funkce, zde očekáváme, o výstupu nic nevíme) funkce na vstupu.

Tyto funkce by byly naší závislostí. Pak byste si připravili funkce, které umí vytvářet jiné funkce, jako jsem ukázal zde Dependency Injection ve funkcionálním LiveScriptu.

Jen taková poznámka na okraj, zjistil jsem, že u FP mám daleko menší potřebu používat DI. Tam, kde je nahraditelnost použitelná a užitečná, tam člověk přirozeně použije HOF, ve všech ostatních případech volá funkci napevno.

Singleton

Singleton se používá tehdy, kdy smíte mít právě 1 instanci. Příkladem může být přístup ke zdroji, který z principu není thread-safe, nebo k omezeným zdrojům (připojení k databázi).

Někteří lidé nesprávně používají Singleton tam, kde jim 1 instance stačí – což je rozhodně příliš málo k tomu, aby z něčeho singleton dělali. Ale o tom jindy, ostatně Singleton vymírá, DI kontejnery veškeré výhody Singletonu vynulovaly.

Nejblíž k singletonu ve funkcionálních jazycích mají Actory, kde je právě 1 actor, který může řešit danou odpovědnost. To sice není mechanismus, který je unikátní pouze pro FP, ale přijde mi nejbližší.

Pokud nepotřebujete svázat funkčnost se stavem a stačí vám předávat pouze 1 hodnotu, řešilo by se to tak, že by si funkce navzájem předávaly právě tu 1 hodnotu a je to.

Observer

Observer slouží pro situace, kdy události dějící se jednomu objektu jsou zajímavé i pro ostatní objekty. Tyto objekty (Observery) se zaregistrují u objektu, který je zajímá (Observable) a pak jsou notifikovány.

Funkcionální programování, co jsem si tak všiml, neřeší problémy tímto způsobem. Registrovat se někde znamená změnit stav. Pocitově (sám jsem Observer ve FP nikdy nepotřeboval) bych se snažil věc vyřešit normálním voláním a určením observerů v compile-time, ne až v run-time, jako to umožňuje Observer.

Jak jste mohli vidět, v FP jsou věci velmi podobné OOP. Některé věci by se řešily principielně jinak, ale naprostá většina je jen rozdíl v přemýšlení a terminologii.

Jiří Knesl se zabývá hlavně Scrumem a správným vývojem software (prevence chyb, vyšší produktivita).

Komentáře: 11

Přehled komentářů

Pavel Tisnovsky Zajímavé postřehy
podlesh Re: Zajímavé postřehy
Radek Miček
Lemming
Jiří Knesl (autor) Re:
Lemming Re:
balki Singleton
satai Re: Singleton
balki Re: Singleton
satai Re: Singleton
tacoberu Singleton
Zdroj: https://www.zdrojak.cz/?p=14003