javascript - TypeScript: grensesnitt polymorfisme problem
Jeg har et grunnleggende Account-grensesnitt:
interface Account {
id: number;
email: string;
password: string;
type: AccountType;
}
hvor AccountType:
enum AccountType {
Foo = 'foo',
Bar = 'bar'
}
og to kontosubtyper (FooAccount og BarAccount) som utvider Account-grensesnittet:
interface FooAccount extends Account {
foo: Foo;
}
interface BarAccount extends Account {
bar: Bar;
}
Account er en aggregat som inneholder grunnleggende kontoinformasjon og, avhengig av typen, eier et Foo- eller et Bar-objekt.
Handlinger på disse objektene kan bare utføres av eierne deres (kontoen).
Jeg har definert et AccountRepository:
export interface AccountRepository {
findById(accountId: number): Account;
}
hvor findById(accountId: number) returnerer en Account, men denne kontoen kan være en hvilken som helst FooAccount eller BarAccount.
Jeg vil bruke denne findById-funksjonen før jeg utfører noen handling på en Foo eller Bar. For eksempel, la oss si at jeg vil oppdatere en kontos Foo:
- ville bruke
findById(accountId: number)for å hente kontoen - sjekk AccountType for kontoen, i dette tilfellet
account.type === AccountType.Foo - hvis AccountType-sjekken er korrekt, kan du deretter få tilgang til
account.foo.idog bruke denfooIdfor å utføre den ønskede oppdateringen
Problemet her er at dette siste punktet feiler: ettersom findById(accountId: number): Account returnerer en Account og det ikke finnes en foo: Foo egenskap definert i dens grensesnitt.
Jeg har også prøvd følgende, men det lar seg heller ikke gjøre:
const fooAccount: FooAccount = findById(accountId);fordi funksjonen returnerer en Account.
Jeg prøver å finne ut hvordan dette kan oppnås, hva går jeg glipp av? Er det noe jeg kan gjøre galt?
Svar #1
Den beste løsningen er sannsynligvis å bruke en diskriminert union.
export class Bar { public idBar: number; }
class Foo { public idFoo: number; }
interface AccountCommon {
id: number;
email: string;
password: string;
}
enum AccountType {
Foo = 'foo',
Bar = 'bar'
}
interface FooAccount extends AccountCommon {
type: AccountType.Foo; // type can only be Foo
foo: Foo;
}
interface BarAccount extends AccountCommon {
type: AccountType.Bar; // type can only be Bar
bar: Bar;
}
// The discriminated union
type Account = BarAccount | FooAccount //type is common so type can be either Foo or Bar
export interface AccountRepository {
findById(accountId: number): Account;
}
let r: AccountRepository;
let a = r.findById(0);
if (a.type === AccountType.Bar) { // type guard
a.bar.idBar // a is now BarAccount
} else {
a.foo.idFoo // a is now FooAccount
}
Svar #2
Løst dette ved å bruke Type Assertion, ved bare å legge til as FooAccount slik:
const fooAccount: FooAccount = findById(accountId) as FooAccount;Det er ikke nødvendig å endre det eksisterende designet for å oppnå det.
I utgangspunktet lykkes påstanden fra type S til T hvis enten S er en subtype av T eller T er en subtype av S.
Mer info: https://basarat.gitbooks.io/typescript/docs/types/type-assertion.html
