Sådan bruges dekoratører med fabriksfunktioner

Find funktionel JavaScript blev udnævnt til en af ​​de bedste nye funktionelle programmeringsbøger af BookAuthority!

Foto af chuttersnap på Unsplash

Metoddekoratører er et værktøj til genbrug af fælles logik. De supplerer objektorienteret programmering. Dekoratører indkapsler ansvaret, der deles af forskellige genstande.

Overvej følgende kode:

funktion TodoStore (nuværende bruger) {
  lad todos = [];
  
  funktion tilføj (todo) {
    lad start = Date.now ();
    hvis (currentUser.isAuthenticated ()) {
      todos.push (todo);
    } andet {
      kaste "Ikke autoriseret til at udføre denne handling";
    }
            
    lad varighed = Date.now () - start;
    console.log ("tilføj () varighed:" + varighed);
  }
    
  return Object.freeze ({
    tilføje
  });
}

Formålet med metoden add () er at tilføje nye to-doser til den interne tilstand. Derudover skal metoden kontrollere brugerens tilladelse og logge udførelsesvarigheden. Disse to ting er sekundære bekymringer og kan faktisk gentages på andre metoder.

Forestil dig, at vi kan indkapsle disse sekundære ansvarsområder i funktioner. Så kan vi skrive koden på følgende måde:

funktion TodoStore () {
  lad todos = [];
  
  funktion tilføj (todo) {
    todos.push (todo);
  }
    
  return Object.freeze ({
     tilføjer: komponere (logDuration tillade) (tilføje)
  });
}

Nu tilføjer metoden () bare todo'en til listen. De øvrige ansvarsområder implementeres ved at dekorere metoden.

logDuration () og autorize () er dekoratører.

En funktionsdekoratør er en funktion med højere orden, der tager én funktion som et argument og returnerer en anden funktion, og den returnerede funktion er en variation af argumentfunktionen.
Reginald Braithwaite i Javascript Allongé

Log Varighed

Et almindeligt scenario er at logge varigheden af ​​et metodekald. Følgende dekoratør logger varigheden af ​​et synkronopkald.

funktion logDuration (fn) {
  returfunktionsdekoratør (... args) {
    lad start = Date.now ();
    lad resultat = fn.apply (dette, args);
    lad varighed = Date.now () - start;
    console.log (fn.name + "() varighed:" + varighed);
    returneresultat;
  }
}

Bemærk, hvordan den originale funktion blev kaldt - ved at indtaste den aktuelle værdi af dette og alle argumenter: fn.apply (dette, args).

Bemyndigelse

Dekoratøren for autorize () sørger for, at brugeren har rettighederne til at udføre metoden. Denne dekoratør er mere kompleks, da den er afhængig af et andet objekt-aktuelle bruger. I dette tilfælde kan vi bruge en funktion createAuthorizeDecorator () til at bygge dekoratøren. Dekoratøren udfører kun metoden, hvis brugeren er godkendt.

funktion createAuthorizeDecorator (nuværende bruger) {
  returfunktion autoriser (fn) {
    returfunktionsdekoratør (... args) {
      hvis (currentUser.isAuthenticated ()) {
        return fn.apply (dette argumenterer);
      } andet {
        kaste "Ikke autoriseret til at udføre" + fn.name + "()";
      }
    }
  }
}

Nu kan vi oprette dekoratøren og videregive afhængigheder.

lad autorize = createAuthorizeDecorator (currentUser);

komponere ()

Vi er ofte nødt til at anvende flere dekoratører til en metode. En enkel måde at gøre det på er at kalde dekoratørerne den ene efter den anden. Se på det næste eksempel:

funktion tilføj () {}
lad addWithAuthorize = autorisere (tilføje);
lad addWithAuthorizeAndLog = logDuration (addWithAuthorize);
addWithAuthorizeAndLog ();

En anden måde er at komponere alle dekoratører og derefter anvende den nye sammensatte dekoratør på den originale funktion. Vi kan bruge compose () -funktionen fra biblioteker som underscore.js.

Funktionssammensætning anvender én funktion på resultatet af en anden.

At anvende f () på resultatet af g () betyder komponere (f, g) (x) og er det samme som f (g (x)).

Komposition fungerer bedst med unære funktioner. Vores dekoratører er unære funktioner.

En unary funktion er en funktion, der tager ét argument.

lad composDecorator = _.compose (logDuration, autorize);
lad addWithComposedDecorator = composDecorator (tilføj);
addWithComposedDecorator ();

Nedenfor kan du se, hvordan komponere () bruges til at anvende dekoratører til metoden add ().

funktion TodoStore () {
  funktion tilføj () {}
    
  return Object.freeze ({
     tilføjer: _ komponere (logDuration tillade) (tilføje).
  });
}
let todoStore = TodoStore ();
todoStore.add ();

Bestille

I nogle tilfælde er den rækkefølge, hvor dekoratører udføres, muligvis ikke vigtig. Men ofte betyder ordren noget.

I vores eksempel udføres de fra venstre mod højre:

  • 1. - varighedsloggen starter
  • 2. - autorisationen udføres
  • Tredje - den originale metode kaldes

Fabriksfunktioner

Jeg favoriserer fabriksfunktioner frem for klasser af de flydende grunde:

  • Indkapsling. Medlemmer er som standard private. Jeg kan beslutte, hvilke metoder der skal udsættes i det offentlige API. I klasser er alle medlemmer offentlige.
  • Der er ingen problemer med denne tabende kontekst. Fabriksfunktioner bruger ikke dette, så der ikke er flere relaterede problemer.
  • Objekter har en bedre sikkerhed. Den interne tilstand er indkapslet, og API'et er uforanderlig. For eksempel kan et globalt objekt defineret med klasse ændres fra udviklerkonsollen.

For en mere dybtgående sammenligning, se på funktionen Class vs Factory: udforske vejen frem.

Fjern den selvudførende del fra Reveling Module-mønsteret, og du får en fabriksfunktion. Nedenfor er definitionen af ​​TodoStore fabriksfunktion.

funktion TodoStore () {
  funktion get () {}
  funktion tilføj (todo) {}
  funktionsredigering (id, todo) {}
  funktion fjern (id) {}
    
  return Object.freeze ({
      få,
      tilføje,
      redigere,
      fjerne
  });
}
let todoStore = TodoStore ();

Udsmykning af fabriksfunktionen

Det er ofte tilfældet, at vi er nødt til at anvende dekoratører på alle offentlige metoder til et objekt. Vi kan oprette funktionen decorateMethods () for at anvende dekoratører på alle offentlige metoder til fabriksfunktionen.

funktion decorateMethods (obj, ... dekoratører) {
  funktion decorateMethod (fnName) {
    if (typeof (obj [fnName]) === "funktion") {
      obj [fnName] = _.compose (... dekoratører) (obj [fnName]);
    }
  }
  Object.keys (obj) .forEach (decorateMethod);
  return obj
}
funktion dekorereAndFreeze (obj, ... args) {
  var newObj = {... obj};
  dekorere metoder (nyObj, ... args);
  return Object.freeze (newObj);
}

Nu kan vi bruge decorateAndFreeze () -funktionen til at dekorere alle offentlige metoder til fabriksfunktionen.

funktion TodoStore () {
  funktion get () {}
  funktion tilføj (todo) {}
  funktionsredigering (id, todo) {}
  funktion fjern (id) {}
    
  returnere dekorere og frise ({
      få,
      tilføje,
      redigere,
      fjerne
  }, logDuration, autorisere);
}
Dekoratorerne fungerer som nøgleord eller annotationer og dokumenterer metodens opførsel, men adskiller tydeligt disse sekundære bekymringer fra metodens kernelogik.
Reginald Braithwaite i Javascript Allongé

Bevar metodens kontekst

Dekoratøren skal bevare den originale metodes kontekst. Kort sagt bør det ikke miste værdien af ​​dette. Der er to regler, der skal følges:

  • dekoratøren skal returnere en funktion ved hjælp af funktionsnøgleordet (ikke brug pilens syntaks)
  • den originale funktion skal kaldes med opkald (dette, ...) eller anvendes (dette, ...)

Hvis vi ikke gør det, mister den dekorerede metode konteksten herfor.

Fabriksfunktioner bruger ikke dette, så de fungerer.

Klasser, konstruktørfunktioner og objektlitterære bruger dette, så de mister kontekst.

Se nu på følgende eksempel:

lad logDuration = fn => (... args) => {
    lad start = Date.now ();
    lad resultat = fn (... args);
    lad varighed = Date.now () - start;
    console.log (fn.name + "() varighed:" + varighed);
    returneresultat;
  }
// Konstruktorfunktion
funktionstjeneste () {
  this.url = "http: //";
}
Service.prototype.fetch = logDuration (funktion hente () {
   console.log (this.url); // undefined
  });
lad service = ny service ();
service.fetch ();
// Objekt bogstavelig
lad en anden service = {
  url: "http: //",
  fetch: logDuration (funktion hente () {
   console.log (this.url); // undefined
  })
}
anotherService.fetch ();

Konklusion

Fabriksfunktioner og dekoratører er kraftfulde værktøjer i vores værktøjskasse. Fabriksfunktioner bygger OOP-objekter. Dekoratører indkapsler almindelig logik, der kan genanvendes mellem disse objekter. De to koncepter er komplementære.

Du kan også finde dekoratører i populære biblioteker. Se på Her er et par funktionsdekoranter, du kan skrive fra bunden for eksempler.

Dekoratører forbedrer læsbarheden. Koden er meget renere, da den fokuserer på dens hovedansvar uden at blive sløret af den sekundære logik.

Find funktionel JavaScript blev udnævnt til en af ​​de bedste nye funktionelle programmeringsbøger af BookAuthority!

For mere om anvendelse af funktionelle programmeringsteknikker i React, se på Funktionel reaktion.

Læs mere om Vue og Vuex i en hurtig introduktion til Vue.js-komponenter.

Lær, hvordan du anvender principperne i designmønstre.

Du kan finde mig også på Twitter.