Luokka vs. tehdastoiminto: tutkitaan etenemistapaa

Discover Functional JavaScript nimettiin yhdeksi BookAuthorityn parhaista uusista funktionaalisen ohjelmoinnin kirjoista!

ECMAScript 2015 (alias ES6) tulee luokkasyntaksin kanssa, joten meillä on nyt kaksi kilpailevaa mallia objektien luomiseen. Niiden vertaamiseksi luon saman objektimääritelmän (TodoModel) kuin luokan ja sitten tehdastoiminnon.

TodoModel luokana

luokka TodoModel {
    rakentaja(){
        tämä.todos = [];
        this.lastChange = nolla;
    }
    
    addToPrivateList () {
        console.log ( "addToPrivateList");
    }
    add () {console.log ("add"); }
    lataa () {}
}

TodoModel tehdastoiminnona

toiminto TodoModel () {
    var todos = [];
    var lastChange = nolla;
        
    toiminto addToPrivateList () {
        console.log ( "addToPrivateList");
    }
    toiminto add () {console.log ("add"); }
    toiminnon lataus () {}
    
    palauta Object.freeze ({
        lisätä,
        ladata
    });
}

kapselointi

Ensin huomaamme, että kaikki luokkaobjektin jäsenet, kentät ja menetelmät ovat julkisia.

var todoModel = uusi TodoModel ();
console.log (todoModel.todos); // []
console.log (todoModel.lastChange) // null
todoModel.addToPrivateList (); // addToPrivateList

Koteloinnin puute voi aiheuttaa turvallisuusongelmia. Ota esimerkki globaalista objektista, jota voidaan muokata suoraan kehittäjäkonsolista.

Tehdastoimintoa käytettäessä vain paljastamme menetelmät ovat julkisia, kaikki muu on kapseloitu.

var todoModel = TodoModel ();
console.log (todoModel.todos); // määrittelemätön
console.log (todoModel.lastChange) // määrittelemätön
todoModel.addToPrivateList (); //taskModel.addToPrivateList
                                    ei ole toiminto

Tämä

nämä menettävät asiayhteysongelmat ovat edelleen olemassa luokkaa käytettäessä. Esimerkiksi tämä menettää kontekstin sisäkkäisissä toiminnoissa. Se ei ole vain ärsyttävä koodauksen aikana, vaan se on myös jatkuva virheiden lähde.

luokka TodoModel {
    rakentaja(){
        tämä.todos = [];
    }
    
    lataa () {
        setTimeout (toimintaloki () {
           console.log (this.todos); // määrittelemätön
        }, 0);
    }
}
todoModel.reload (); // määrittelemätön

tai tämä menettää kontekstin, kun menetelmää käytetään takaisinsoitona, kuten DOM-tapahtumassa.

$ ( "# Btn"). Klikkaa (todoModel.reload); // määrittelemätön

Tehdastoimintoa käytettäessä ei ole tällaisia ​​ongelmia, koska se ei käytä sitä ollenkaan.

toiminto TodoModel () {
    var todos = [];
        
    toiminnon uudelleenlataus () {
        setTimeout (toimintaloki () {
           console.log (todos); // []
       }, 0);
    }
}
todoModel.reload (); // []
$ ( "# Btn"). Klikkaa (todoModel.reload); // []

tämä ja nuolitoiminto

Nuolitoiminto ratkaisee osittain nämä kadonneet kontekstiongelmat luokissa, mutta luo samalla uuden ongelman:

  • tämä ei enää menetä kontekstia sisäkkäisissä toiminnoissa
  • tämä on kontekstin menettämistä, kun menetelmää käytetään takaisinsoitona
  • nuolitoiminto edistää nimettömien toimintojen käyttöä

Suoritin uudelleen TodoModelin nuolitoiminnolla. On tärkeätä huomata, että nuolitoimintoon reagoimisprosessissa voidaan menettää jotain erittäin luettavuuden kannalta tärkeätä, funktion nimi. Katso esimerkiksi:

// käyttämällä funktion nimeä aikomuksen ilmaisemiseen
setTimeout (toiminto renderTodosForReview () {
      / * koodi * /
}, 0);
// verrattuna nimettömän funktion käyttöön
setTimeout (() => {
      / * koodi * /
}, 0);

Muuttamaton sovellusliittymä

Kun objekti on luotu, odotan sen API: n olevan muuttumaton. Voin helposti muuttaa julkisen menetelmän toteuttamista tekemällä jotain muuta, kun se on luotu luokkaa käyttämällä.

todoModel.reload = function () {console.log ("uusi reload"); }
todoModel.reload (); // uusi reload

Tämä ongelma voidaan ratkaista kutsumalla Object.freeze (TodoModel.prototype) luokan määritelmän jälkeen.

Tehdastoiminnolla luodun objektin API on muuttumaton. Huomaa Object.freeze (): n käyttöä palautetussa objektissa, joka sisältää vain julkisia menetelmiä. Objektin yksityisiä tietoja voidaan muokata, mutta vain näiden julkisten menetelmien avulla.

todoModel.reload = function () {console.log ("uusi reload"); }
todoModel.reload (); // reload

Uusi

uutta tulisi käyttää luotaessa objekteja luokkien avulla.

uutta ei tarvita, kun luot objekteja tehdastoiminnoilla, mutta jos se tekee siitä paremmin luettavan, voit etsiä sen, siitä ei ole haittaa.

var todoModel = uusi TodoModel ();

Uuden käyttäminen tehdastoiminnolla palauttaa vain tehtaan luoman objektin.

Koostumus perimisen suhteen

Luokat tukevat sekä perintöä että koostumusta.

Alla on esimerkki perinnöistä, joissa SpecialService-luokka perii palveluluokasta:

luokkapalvelu {
  doSomething () {console.log ("doSomething"); }
}
luokka SpecialService laajentaa palvelua {
  doSomethingElse () {console.log ("doSomethingElse"); }
}
var specialService = uusi SpecialService ();
specialService.doSomething ();
specialService.doSomethingElse ();

Tässä on toinen esimerkki, jossa SpecialService käyttää uudelleen palvelun jäsentä koostumuksen avulla:

luokkapalvelu {
  doSomething () {console.log ("doSomething"); }
}
luokka SpecialService {
  rakentajan (args) {
    this.service = args.service;
  }
  doSomething () {this.service.doSomething (); }
  
  doSomethingElse () {console.log ("doSomethingElse"); }
}
var specialService = uusi SpecialService ({
   palvelu: uusi palvelu ()
});
specialService.doSomething ();
specialService.doSomethingElse ();

Tehtaan toiminnot edistävät koostumusta perinnöllä. Katso seuraava esimerkki, jossa SpecialService käyttää uudelleen palvelun jäseniä:

toiminto Palvelu () {
  toiminto doSomething () {console.log ("doSomething"); }
  palauta Object.freeze ({
    tee jotain
  });
}
toiminto SpecialService (args) {
  var service = args.service;
  toiminto doSomethingElse () {console.log ("doSomethingElse"); }
  palauta Object.freeze ({
    doSomething: service.doSomething,
    doSomethingElse
  });
}
var specialService = SpecialService ({
   palvelu: Palvelu ()
});
specialService.doSomething ();
specialService.doSomethingElse ();

Muisti

Luokat ovat paremmin muistin säilyttämisessä, koska ne toteutetaan prototyyppijärjestelmän kautta. Kaikki menetelmät luodaan vain kerran prototyyppiobjektissa ja jaetaan kaikkien ilmentymien kesken.

Tehdastoiminnon muistikustannukset ovat huomattavat, kun luodaan tuhansia samaa objektia.

Tässä on sivu, jota käytetään muistin kustannusten testaamiseen tehdastoimintoa käytettäessä.

Muistin hinta (Chromessa)
+ ----------- + ------------ + ------------ +
| Tapaukset | 10 menetelmää | 20 menetelmää |
+ ----------- + --------------- + --------- +
| 10 | 0 | 0 |
| 100 | 0,1Mb | 0,1Mb |
| 1000 | 0,7Mb | 1,4 Mt |
| 10000 | 7,3Mb | 14,2Mb |
+ ----------- + ------------ + ------------ +

Objektit vs. tietorakenteet

Ennen kuin muistokustannuksia analysoidaan tarkemmin, on tehtävä ero kahden tyyppisten esineiden välillä:

  • OOP-esineet
  • Tietoobjektit (alias tietorakenteet)
Objektit paljastavat käyttäytymisen ja piilottavat tiedot.
Tietorakenteet paljastavat tietoja, eikä niillä ole merkittävää käyttäytymistä.
- Robert Martin “Puhdas koodi”

Katson uudelleen TodoModel-esimerkkiä ja selitän nämä kaksi tyyppistä objektia.

toiminto TodoModel () {
    var todos = [];
           
    funktio add () {}
    toiminnon lataus () {}
       
    palauta Object.freeze ({
        lisätä,
        ladata
    });
}
  • TodoModel vastaa tehtäväluettelon tallentamisesta ja hallinnasta. TodoModel on OOP-objekti, joka paljastaa käyttäytymisen ja piilottaa tiedot. Sovelluksessa on vain yksi esimerkki siitä, joten tehdastoiminnon käyttämisestä ei aiheudu ylimääräisiä muistikustannuksia.
  • Todos-objektit edustavat tietorakenteita. Näitä esineitä voi olla paljon, mutta ne ovat vain tavallisia JavaScriptiä. Emme ole kiinnostuneita pitämään heidän menetelmiään yksityisinä - pikemminkin haluamme paljastaa kaikki heidän tiedot ja menetelmät. Joten kaikki nämä objektit rakennetaan prototyyppijärjestelmän päälle, ja ne hyötyvät muistin säilyttämisestä. Ne voidaan rakentaa käyttämällä yksinkertaista objektikirjaimellista tai Object.create ().

UI-komponentit

Sovelluksessa voi olla satoja tai tuhansia käyttöliittymäkomponentin esiintymiä. Tässä tilanteessa meidän on tehtävä kompromissi kapseloinnin ja muistin säilyttämisen välillä.

Komponentit rakennetaan komponenttikehyksen käytännön mukaisesti. Esimerkiksi objekti-kirjaimia käytetään Vue-luokassa tai luokkia Reakt-luokassa. Jokaisen komponentin jäsenet ovat julkisia, mutta prototyyppijärjestelmän muistin säilyttämisestä he hyötyvät.

johtopäätös

Luokan vahvoja puolia ovat sen tuntemus ihmisille, jotka tulevat luokkaperustaisesta taustasta, ja sen mukavampi syntaksi prototyyppijärjestelmässä. Turvaongelmat ja tämän käyttö, joka on jatkuva kontekstivirheiden menettämisen lähde, tekevät siitä kuitenkin toisen vaihtoehdon. Poikkeuksena luokkia käytetään, jos komponentin kehys vaatii sitä, kuten Reaktin tapauksessa.

Tehdastoiminto ei ole vain parempi vaihtoehto suojattujen, kapseloitujen ja joustavien OOP-objektien luomiseksi, mutta myös avaa oven uudelle, JavaScript-yksilölliselle ohjelmointikäytännölle.

Discover Functional JavaScript nimettiin yhdeksi BookAuthorityn parhaista uusista funktionaalisen ohjelmoinnin kirjoista!

Lisätietoja toiminnallisten ohjelmointitekniikoiden käytöstä Reaktissa on toiminnallisessa reagoinnissa.

Lue lisää Vue- ja Vuex-artikkeleista Vue.js-komponenttien pikaohjeesta.

Opi soveltamaan suunnittelumallien periaatteita.

Löydät minut myös Twitteristä.