25.06.2021

Tämä artikkeli on osa Programming TypeScript -arkkitehtuurisarjaa.

Apuluokkien ohjelmointi TypeScriptillä

Tässä oppaassa esittelen apuluokkakonseptin ja sen toteutuksen TypeScriptillä.

Tätä mallia voi käyttää mihin tahansa ohjelmistotyyppiin - esim. frontend- tai backend-sovelluksiin tai jopa Nginx:n NJS:ään tai PostgreSQL:n plv8:aan.

Koodiesimerkki

Ytimeltään se on yksinkertaisesti julkisia funktioita nimitilassa, jotka jakavat yhteisen kontekstin.

export class FooUtils {

  public static strip (value: string) : string {
    return value.replace(/ +/, "");
  }

}

export default FooUtils;

…ja käytä näin:

import FooUtils from './FooUtils.ts';

FooUtils.strip('a b c');

Mikä on apuluokka?

Apuluokan tekee apuluokaksi se, ettei sillä ole sisäistä tilaa muuten kuin argumentteina annettavat arvot.

Esim. sen ei pitäisi käyttää muuta sisäistä tilaa, joka voisi muuttaa funktion tulosta. Siksi vakiot ovat ok, mutta globaalit muuttujat eivät.

Toinen tärkeä osa arkkitehtuuria on konteksti. Funktiot kannattaa sijoittaa yhteisen kontekstin mukaan. Silloin tiedät, mistä etsiä niitä seuraavalla kerralla - ja niin tietää myös IDE:si. Se helpottaa myös uuden funktion sijoittamista oikeaan paikkaan.

Mutta miksei käyttää import/export-mallia?

Tämä olisi tietenkin standardi EcmaScript-tapa. Se on täysin ok, kunhan olet johdonmukainen, ellei ole parempaa syytä toimia toisin.

Tässä tapauksessa loisit tiedoston fooUtils.ts näin:

export function strip (value: string) : string {
  return value.replace(/ +/, "");
}

…ja käyttäisit sitä näin:

import fooUtils from './fooUtils.ts';

fooUtils.strip('a b c');

Tässä tyylissä on myös etuja: oikein toteutettu ES-import-järjestelmä voi ottaa mukaan vain ne osat, jotka importoit, ja jättää muut kääntämättä. Toisaalta oikein konfiguroitu minifioiva build-järjestelmä tekee saman myös luokkien käyttämättömille osille.

Tämän tyylin todennäköisin ongelma on, että se nojaa tiedostoihin funktioiden ryhmittelyssä. Staattinen luokkamuoto voidaan kääntää yhdeksi tiedostoksi ja debugata menettämättä ryhmitettyjen funktioiden kontekstia.

Mutta TypeScriptissahan on jo nimitilat!

Kyllä, TypeScriptissä on oikea namespace-konsepti, jota voit tietysti käyttää samalla tavalla, mutta koen luokan huomattavasti yksinkertaisemmaksi tässä käyttötapauksessa.

Pidän erityisesti luokkamallista, koska siinä on private-, protected- ja public-metodit ja yleisesti laajempi tuki - esim. nimitilat eivät ole suoraan tuettuja ES-järjestelmissä kuten NodeJS ja muissa Google v8 -pohjaisissa järjestelmissä (tätä artikkelia kirjoitettaessa).

On myös tämä artikkeli nimitilojen välttämisestä, koska on mahdollista, ettei nimitiloja lisätä standardiin, kun niiden toteuttamiseen on jo käsite. Tämä on toki spekulaatiota. Voit aina käyttää oikeita nimitiloja - kunhan olet johdonmukainen.

Miksi ei toteuteta suoraan varsinaiseen Foo-luokkaan?

Tämä on hyvä kysymys. Lopulta kyse on arkkitehtuurisesta mallista, joka voi näyttää turhalta pienessä ja minimaalisessa sovelluksessa.

Suurissa projekteissa, joissa useat ihmiset työskentelevät saman koodin parissa, pienet arkkitehtuurivalinnat ovat paljon tärkeämpiä ja voivat vaikuttaa siihen, miten projekti onnistuu ja kuinka usein syntyy [git]-konflikteja.

Mikä apuluokan idea siis on? Lopulta, kuten useimmat arkkitehtuurivalinnat, se on tapa jakaa koodin eri osat pienempiin tiedostoihin.

Annan esimerkin (lähes) tosielämän sovelluksista.

Oletetaan, että kehität frontend-sovellusta, joka käyttää backendia. Saat määrittelyn siitä, millaista JSONia backend käyttää - et ole suunnitellut sitä itse, vaan sen on tehnyt toinen tiimi tai jopa toinen yritys.

Lopulta nämä arkkitehtuurivalinnat irrottavat ohjelmiston eri osat toisistaan, jotta eri kehittäjät voivat helposti ylläpitää ja työskennellä saman lähdekoodin parissa erikseen.

Se myös vähentää sovelluksen päällekkäisiä koodirivejä, mikä voi vähentää bugeja, koska korjaus ei unohdu toiseen, päällekkäiseen koodikohtaan.