Návrhový vzor Fluid Logic

Návrhový vzor Fluid Logic byl publikován v díle Real World Java EE Patterns vydané v roce 2009 Adamem Bienem. Jedná se o návrhový vzor využívající se při vývoji software, který do sebe musí implementovat prvky dynamických jazyků. Vzor byl navržen a je prezentován pomocí programovacího jazyka Java, který je spustitelný v prostředí JVM, stejně jako i mnoho nově vznikající dynamických jazyků.

Důvody pro vznik návrhového vzoru editovat

Java je statický jazyk zdůrazňující silné typování, proto je vhodná pro implementování dobře definované a stabilní business logiky. Tento programovací jazyk však není stavěný na algoritmy, které se často mění, protože každá změna aplikace vyžaduje její kompilaci a nové nasazení do produkčního prostředí.

Silné typování a statická podoba jazyka nese tudíž i negativa. Pro různé účely je lepší využít možností dynamických jazyků než jazyka Java. Těmito jazyky mohou být například JavaScript, Ruby nebo Python. V jazyce Java se nám jen stěží podaří interpretovat business logiku aplikace za jejím běhu. Mohli bychom využít různé nástroje, jako je například ANTLR (http://www.antlr.org/), a vytvořit si vlastní doménově specifický jazyk (DSL), který by byl zpracováván a interpretován za běhu programu. S vytvořením vlastního tímto DSL je však spojeno obrovské úsilí ohledně designu, testování a dokumentace.

Výhody využití návrhového vzoru editovat

  • Program je navržen tak, že musí spustit některou svojí část business logicky dynamicky.
  • Algoritmy, které se často mění je potřeba izolovat a nahrávat dynamicky bez toho, aby ovlivnily zbytek aplikace.
  • Část business logiky může být změněna, aniž by se musel program kompilovat a znovu nasazovat do produkčního prostředí.
  • Není potřeba realizovat náročný vývoj vlastního interpretu.
  • Možnost snadného integrování skriptových komponent s programy běžícími na Java EE prostředí.

Vytvoření návrhového vzoru editovat

Problém s vložením vlastním skriptů, které by náš program mohl dynamicky zpracovávat byl vyřešen v Java 6 Standard Edition. V Java specifikaci označené jako JSR-223 (Scripting for the Java Platform) je navrženo vhodné API, které nám nabízí interakci s více než 100 skriptovacími jazyky. Je dokonce možné vložit java objekt do objektu ScriptEngine a učinit ho tak dostupným pro zpracování dynamickým jazykem. Inicializace samotného ScriptEngine vezme jen pár řádek kódu a je ukázána níže na příkladu. Použití skriptovacích jazyků není závislé na žádném prostředí a může být využito jak případech aplikací, které musí běžet v různých Java EE kontejnerech, tak v případech, kde postačuje nasazení platformy Java SE. Protože návrhový vzor úzce souvisí s Java Scripting API, tak proto podrobněji vysvětluji a ukazuji na příkladech jeho využití, aby bylo jasně patrné, čeho všeho lze implementováním vzoru Fluid Logic dosáhnout.

Třída Calculator, definovaná níže v kódu, je naprogramována tak, aby zpracovala výraz předaný jako parametr metodě calculate.

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

public class Calculator {

    private static final String ENGINE_NAME = "JavaScript";
    
    private ScriptEngine scriptEngine = null;
    
    private final static double FIVE = 5;
    
    public Calculator(){
        initScripting();
    }
    
    private void initScripting(){
        ScriptEngineManager engineManager = new ScriptEngineManager();
        this.scriptEngine = engineManager.getEngineByName(ENGINE_NAME);
        if(this.scriptEngine == null){
            throw new IllegalStateException("Cannot create... " + ENGINE_NAME);
        }
    }
    
    public Object calculate(String formula){
        Object retVal = null;
        try {
            Bindings binding = this.scriptEngine.createBindings();
            binding.put("FIVE", FIVE);
            this.scriptEngine.setBindings(binding, ScriptContext.GLOBAL_SCOPE);
            retVal = this.scriptEngine.eval(formula);
        } catch (Exception e) {
            throw new IllegalStateException("Cannot create... " + e, e);
        }
        return retVal;
    }
}

Nyní si ukážeme, jak třídu Calculator využít a spustit kód z JavaScript v Javě pomocí Java Scripting API. Můžete si všimnout, že při generování výrazu 2 * 2 * FIVE, jsme využili konstantu, kterou jsme si ve třídě Calculator nadefinovali a vložili do skriptového kontextu.

public class App {
    public static void main( String[] args ){
        Calculator cal = new Calculator();
        System.out.println("Výsledek výpočtu je " + 
                           cal.calculate("2 * 2 * FIVE"));
    }
}

Jak jsme již napsali dříve, návrhový vzor Fluid Logic je vhodný zejména pro jeho možnost jednoduchého přemístění dynamického kódu nebo jeho změny. Kód může být nahrán z mnoha zdrojů. V další části si ukážeme, jak může být kód dynamicky nahrán ze souboru, jsou však možné i jiné varianty, například nahrání kódu uloženého v databázi nebo ze vzdáleného souboru umístěného na síti.

Způsob nahrání kódu ze souboru editovat

Nyní ukážu, jak je možné nahrát dynamicky kód ze souboru a nechat ho zpracovat pomocí Java Scripting API. Znovu se jedná o kód napsaný v JavaScriptu.

// Vytvoříme engine manager
ScriptEngineManager engineManager = new ScriptEngineManager();

// Vytvoříme konkrétní JavaScript engine
ScriptEngine scriptEngine = engineManager.getEngineByName("JavaScript");

// Pomocí JavaScript enginu zpracujeme soubor s kódem
scriptEngine.eval(new FileReader("soubor.js"));

Zavolání konkrétní funkce editovat

Další příklad nám ukazuje, jak je možné vytvořit metodu a poté jí opakovaně volat pomocí rozhraní Invocable.

String script = "function hello(name) { print('Hello, ' + name); }";
scriptEngine.eval(script);

// javax.script.Invocable je volitelné rozhraní.
// Před použitím musíme zkontrolovat, jestli náš engine rozhraní implementuje!
// JavaScript toto rozhraní implementuje.
Invocable inv = (Invocable) engine;

// Zavolání funkce hello s parametrem name
inv.invokeFunction("hello", "Java Scripting API");

Volání metod na objektech editovat

Pokud námi zvolený dynamický jazyk podporuje objektově orientované programování, tak je možné využití objektů i pomocí Java Scripting API.

// Pomocí JavaScriptu vytvoříme objekt s metodou hello
String script = "var obj = new Object();"; 
script += "obj.hello = function(name) { print('Hello, ' + name); }";
        
scriptEngine.eval(script);
Invocable inv = (Invocable) scriptEngine;

// Získáme objekt, na kterém chceme volat metodu
Object obj = scriptEngine.get("obj");

// Zavoláme metodu "hello" na objektu "obj" s daným parametrem
inv.invokeMethod(obj, "hello", "Java Scripting API" );

Implementace rozhraní editovat

Další příklad nám ukáže jak můžeme vzít objekt ze skriptovacího jazyka a použít ho jako implementaci javovského rozhraní.

String script = "var obj = new Object();";
script += "obj.run = function() { println('run method called'); }";
        
scriptEngine.eval(script);
Object obj = scriptEngine.get("obj");
Invocable inv = (Invocable) scriptEngine;

// Získaný objekt vložíme jako implementaci "Runnable" rozhraní
Runnable r = inv.getInterface(obj, Runnable.class);

// Vytvoříme nové vlákno s implementací ze skriptovacího jazyka
Thread th = new Thread(r);
th.start();

Využití java tříd ve skriptovacím jazyce editovat

Poslední příklad ukazuje na využití javovských tříd ve skritovacím jazyce. Na příkladu je zdrojový kód z JavaScriptu, který následně lze snadno nahrát jako souboru a spustit ho. Výsledkem bude vytvoření okna a vypsání „hello“ na standardní výstup.

importPackage(java.awt);
importClass(java.awt.Frame);
var frame = new java.awt.Frame("hello");
frame.setVisible(true);
print(frame.title);

Další podporované dynamické jazyky editovat

Příklad dalších podporovaných dynamických jazyků, které mají vytvořeny svůj ScriptEngine, tudíž mohou interpretovány a stát se tak součástí business logiky aplikace.
JRuby, Groovy, BeanShell, JACL, Jaskell, Java
Jawk, Jelly, JEXL, Javascript, Jython, OGNL
EEP, xPath, XSLT, Pnuts, Scheme, sl

Literatura editovat