javascript - Rajapinta ja luokka TypeScriptissä


C#:ssa on melko suuri ero rajapintojen ja luokkien välillä. Itse asiassa luokka edustaa viitetyyppiä, joten voimme oikeasti luoda objekti, joka on mallinnettu tuon luokan mukaan, kun taas rajapinnat on tarkoitettu sopimuksiksi, joihin luokka sitoutuu varmistaakseen tietyn käyttäytymisen olemassaolon. Erityisesti emme voi luoda rajapinnan ilmentymiä.

Koko pointti rajapinnoissa on paljastaa käyttäytyminen. Luokka toteuttaa sen antamalla yhden selkeän toteutuksen kyseisestä käyttäytymisestä.

Siinä tapauksessa, vaikka rajapinnat voivat sisältää ominaisuuksia, useimmiten meitä kiinnostavat rajapinnat käyttäytymiskysymysten vuoksi. Joten suurimmaksi osaksi tyypistä, rajapinnat ovat vain käyttäytymissopimuksia.

TypeScriptissä olen sen sijaan nähnyt jotain, mikä sai minut melko levottomaksi, ja totisesti olen nähnyt tämän enemmän kuin kerran, mikä on syy tälle kysymykselle.

Yhdessä opetusohjelmassa näin tämän:

export interface User {
    name: string; // required with minimum 5 chracters
    address?: {
        street?: string; // required
        postcode?: string;
    }
}

Mutta odota hetki. Miksi User on rajapinta? Jos ajattelemme C#:n tavoin, User ei pitäisi olla rajapinta. Totuuden nimissä, katsottaessa sitä, näyttää siltä, että määrittelemme tietotyypin User sen sijaan, että määrittelisimme käyttäytymissopimuksen.

Ajatellen kuten teemme C#:ssa, luonnollinen asia olisi tämä:

export class User {
    public name: string;
    public address: Address;
}
export class Address {
    public street: string;
    public postcode: string;
}

Mutta tämä asia, että käytetään rajapintoja samalla tavalla kuin luokkia, pelkästään määrittelemään tietotyyppi, sen sijaan että määriteltäisiin käyttäytymissopimus, vaikuttaa erittäin yleiseltä TypeScriptissä.

Joten mihin rajapintoja tarkoitetaan TypeScriptissä? Miksi ihmiset käyttävät rajapintoja TypeScriptissä samalla tavalla kuin käytämme luokkia C#:ssa? Kuinka rajapintoja tulisi käyttää oikein TypeScriptissä: käyttäytymissopimusten luomiseen vai määrittämään ominaisuuksia, joita oliolla tulisi olla?

Vastaus #1

Harkitse, että Javascriptissä data vaihdetaan usein tavallisina objekteina, usein JSONin kautta:

let data = JSON.parse(someString);

Oletetaan, että tämä data on taulukko User-olioita, ja annamme sen funktiolle:

data.forEach(user => foo(user))

foo kirjoitettaisiin näin:

function foo(user: User) { ... }

Mutta odota, emmehän me missään vaiheessa tehneet new User! Pitäisikö meidän? Pitäisikö meidän kirjoittaa luokka User ja map kaikki data siihen, vaikka lopputulos olisi täsmälleen sama, Object ominaisuuksineen? Ei, se olisi hulluutta vain tyyppijärjestelmän tarpeiden täyttämiseksi, mutta ei muuttaisi mitään ajon aikana. Yksinkertainen interface, joka kuvaa, miltä tietyn objektin odotetaan näyttävän (eli miten sen odotetaan "käyttäytyvän"), riittää täysin tässä.

Vastaus #2

Minäkin tulin Typescriptiin C#-taustasta ja olen miettinyt samoja asioita. Ajattelin POCOjen linjaa (onko POTO olemassa?)

Joten mihin rajapintoja on tarkoitettu TypeScriptissä?

Typescript-käsikirja näyttää sanovan, että rajapintoja on tarkoitettu "sopimusten määrittämiseen koodissasi".

Miksi ihmiset käyttävät rajapintoja TypeScriptissä samalla tavalla kuin käytämme luokkia C#:ssä?

Olen samaa mieltä @decezen vastauksen kanssa tässä.

John Papa laajentaa luokkien ja rajapintojen aihetta blogissaan. Hän ehdottaa, että luokat soveltuvat parhaiten "useiden uusien instanssien luomiseen, perinnän käyttämiseen [ja] singleton-olioihin". Joten, perustuen Typescriptin rajapintojen tarkoitukseen kuten Typescript Handbookissa on kuvattu ja yhden miehen mielipiteeseen, näyttää siltä, että luokkia ei tarvita sopimusten määrittämiseen Typescriptissä. Sen sijaan sinun tulisi käyttää rajapintoja. (C#-vaistosi tulevat silti loukkaantumaan.)

Rajapintoja tulisi käyttää oikein TypeScriptissä: käyttäytymissopimusten luomiseen tai määrittämään ominaisuudet, joita objektilla pitäisi olla?

Jos ymmärrän kysymyksen oikein, kysyt, pitäisikö rajapintojen luoda käyttäytymisen sopimuksia vai rakenteen sopimuksia. Tähän vastaisin: molempia. Typescriptin rajapintoja voidaan silti käyttää samalla tavalla kuin C#- tai Java-rajapintoja (eli luokan käyttäytymisen kuvaamiseen), mutta ne tarjoavat myös mahdollisuuden kuvata datan rakennetta.

Lisäksi työtoverini moitti minua siitä, että käytin luokkia rajapintojen sijaan, koska rajapinnat eivät tuota koodia kääntäjässä.

Esimerkki :

Tämä Typescript:

class Car implements ICar {
    foo: string;
    bar(): void {
    }
}

interface ICar {
    foo: string;
    bar(): void;
}

tuottaa tämän Javascriptin:

var Car = (function () {
    function Car() {
    }
    Car.prototype.bar = function () {
    };
    return Car;
}());

Kokeile sitä

Vastaus #3

Rajapinnat TypeScriptissä ovat samankaltaisia kuin rajapinnat C#:ssa siinä mielessä, että ne molemmat tarjoavat sopimuksen. Toisin kuin C#:n rajapinnat, jotka sisältävät vain metodeja, TypeScriptin rajapinnat voivat myös kuvata kenttiä tai ominaisuuksia, joita objektit sisältävät. Siksi niitä voidaan käyttää myös asioihin, jotka eivät ole suoraan mahdollisia C#:n rajapintojen kanssa.

Merkittävä ero TypeScriptin rajapintojen ja luokkien välillä on se, että rajapinnat eivät omaa ajonaikaista esitystä, eikä niiden osalta luoda mitään koodia. Rajapintoja voi käyttää hyvin laajasti. Esimerkiksi voit käyttää objektikirjauksia luodaksesi objekteja, jotka täyttävät rajapinnan. Kuten:

let user: User = {
  name: 'abc',
  address: {
    street: 'xyz',
  },
};

Tai voit määrittää minkä tahansa dataobjektin (esim. JSON-parsinnan kautta saadun) rajapinnalle (mutta esitarkistustesi tulisi varmistaa, että se on todella kelvollista dataa). Siksi rajapinnat ovat erittäin joustavia datalle.

Toisaalta luokilla on tyyppi, joka liittyy niihin ajon aikana, ja koodi generoidaan. Voit tarkistaa tyypin ajon aikana instanceof-operaattorilla, ja prototyyppiketju on asetettu. Jos määrittelet User-luokan, se ei ole kelvollinen käyttäjä, ellei kutsu konstruktori-funktiota. Etkä voi vain määritellä minkäänlaista sopivaa dataa User-tyypiksi. Sinun täytyy luoda uusi instanssi ja kopioida ominaisuudet siihen.

Henkilökohtainen nyrkkisääntöni:

  • Jos käsittelen puhdasta dataa (eri lähteistä), käytän rajapintoja
  • Jos mallinnan jotain, jolla on identiteetti ja tila (ja todennäköisesti liitännäisiä metodeja tilan muuttamiseksi), käytän luokkaa.

Aiheeseen liittyviä artikkeleita