JavaFX-sovellus MVVM-arkkitehtuurilla

Aiemmissa sarjan blogeissa totesimme, että JavaFX sovellusten perusarkkitehtuuri on MVC. Jos haluamme rakentaa sovelluksen MVVM-arkkitehtuurin mukaiseksi, emme joudu tekemään mitään JavaFX:n sovelluksen perusrakennetta vastaan, vaan kyse on siitä, miten sovellus rakennetaan siitä eteenpäin. Tämä vaatii työtä ja kurinalaisuutta, mutta vastapainoksi MVVM ottaa enemmän irti JavaFX:n databindauksesta ja jakaa JavaFX:n kontrolleriluokkaa pienemmäksi ja kokonaisuutta samalla hallittavammaksi. Mitään ulkopuolisia kirjastoja ei tarvita, kyse on vain arkkitehtuurisesta ratkaisusta. Ideana on myös erottaa käyttöliittymä selkeästi sovelluslogiikasta ja välissä olevat kontrolleri ja ViewModel toimivat tässä ikäänkuin välissä olevana liimana.

MVVM:n perusomainaisuuksia, ainakin tässä ratkaisussa ovat:
– kaksisuuntainen datan sidonta
– käyttöliittymän (View) ja sovelluslogiikan (ViewModel) erottaminen toisistaan
– erittäin hyvä (automaatti)testattavuus ilman käyttöliittymää

JavFX application with MVVM-architecture

JavaFX:n kanssa käytettynä seppien MVVM-malli pienii MVC-mallin mukaista isoa kontrolleriluokkaa pienemmiksi paloiksi, kolmeen osaan. Nämä kolme osaa ovat alla lueteltuina, aloittaen siitä osasta, joka on lähimpänä käyttöliittymää ja mikä jää hoitamaan kontrollerin yhtä ydintehtävää, eli käyttöliittymän palvelemista:

(1) Kontrolleri, tässä määritellään fxml-käyttöliittymän komponentit standardin mukaisella tavalla, ja siinä kutsutaan ViewModelin kautta sovelluslogiikkakerrosta ja sidotaan sovelluslogiikan data fxml-komponentteihin (databinding). Tämä sidonta on työläs osuus sovelluksessa, JavaFX tarvitsee tähän ikävä kyllä paljon rakennekoodia (boilerplate). Toteutus on täysin JavaFX:n vakiodokumentaation mukainen, eikä sitä käsitellä tässä. Itse metodikutsut ovat puolestaan hyvin yksinkertaisia, koska haetun datan sidonta tekee työn ilman eri koodia. Näin toteutettuna kontrolleri-luokka on käyttöliittymän apuluokka ja se on täysin erotettu sovelluslogiikan toteutuksesta, jota ViewModel (alla) kontrollerin jäsenmuuttujana edustaa.

public class HelloController implements Initializable
{
    @FXML private TextField textFirstName;
    @FXML private TextField textLastName;

    private final HelloViewModel viewModel = new HelloViewModel();

    /**
     * Sido viewModelin data fxml-kontrollerihin.
     */
    @Override public void initialize(URL url, ResourceBundle rb)
    {
        // Sovelluslogiikan datan sidonta UI:n fxml-komponentteihin
        // datan (= property olioita) antaminen sidontaan,
        // esim. viewModel.getPersonData();
        // …..
    }

    /**
    * Tapahtumankäsitelijä, esim. buttonin
     * Kontrolleri kutsuu sovelluslogiikkaa, sovelluslogiikan data
    * sidotaan automaattisesti fxml-kontrollereihin.
    */
    @FXML private void handleGetNamesClick(ActionEvent event)
    {
        // haetaan data sovelluslogiikan property-olioihin
        // jotka ovat jo sidottuja fxml-kontrollerihin
        viewModel.fetchPerson();
    }
}

(2) ViewModel, viewModel-olio edellä. ViewModelia voisi esittää tässä olio, jonka luokka toteuttaisi kontrollerin käyttämän rajapintaluokan. Välttämättä mitään rajapintaa ei tarvita, se on vain korostamassa käyttöliittymän tarvitsemia toimintoja. Tässä käytämme rajapinnan sijasta toista vaihtoehtoa, sovelluslogiikasta perittyä luokkaa, jossa esitetään vain käyttöliittymän tarvitsemat metodit, ei mitään muuta. Tämä lähestymistapa on hyvä testauksen kannalta, koska tämän luokan korvaaminen testiluokalla antaa mahdollisuuden testata sovellusta heti kontrollerin alapuolelta kaikilla käyttöliittymän toiminnoilla. ViewModel ei “tiedä” mitään käyttöliittymästä, joten UI ja sovelluslogiikka ovat täysin erillään.

public class HelloViewModel extends HelloLogic
{
    public void fetchPerson() {
        selectPerson(); // base-luokan metodi.
    }
}

(3) Sovelluslogiikka, jossa toteutetaan palvelurajapinnan käyttö ja sen tuottaman JSON-datan siirtäminen käyttöliittymän tarvitsemiin Property-olioihin. JSON on tässä siis vain siirtoformaatti sovelluslogiikan ja property-olioiden välillä. Property-oliot puolestaan edustavat sidottavaa dataa ja ne ovat siksi sovelluslogiikan jäseniä (members). Kun JSON data siirretään property-olioihin, property-olioiden datan sidonta käyttöliittymään hoitaa loput automaattisesti. Datan käsittely ja tilan säilyttäminen tapahtuu kokonaisuudessan tässä luokassa, ei ViewModel-luokassa.

public class HelloLogic
{
    public void selectPerson() {….}
    // restful-kutsujen suoritus, datan hallinta
}

Tärkeää on huomata, että sovelluksessa ei ole mitään HelloLogic-luokan oliota, on vain ViewModel-luokan olio, jonka luokan yläluokka on HelloLogic.

JavaFX:aa koskevan blogisarjan viimeisessä osassa esitämme vaihtoehdon tälle arkkitehtuurimallille, joka soveltuu ehkä paremmin hieman kevyemmille käyttöliittymille.