2017-02-23 55 views
5

Czy możemy użyć odbicie w Skrypcie Typu podobnie jak C#, aby uzyskać listę klas, które implementują pewną klasę bazową?Jak uzyskać klasy potomne, które implementują pewną klasę bazową za pomocą refleksji w skrypcie Type?

Załóżmy na przykład, że Snake i Horse implementują klasę podstawową Animal. Teraz muszę uzyskać klasy, które implementują Animal. Podobny do tego, co możemy zrobić w C#:

kod C# równoważne: klas

var childTypes = assembly.GetTypes().Where(_ => _.IsSubclassOf(typeof(Animal))); 

script type:

class Animal { 
} 

class Snake extends Animal { 
} 

class Horse extends Animal { 
} 
+1

To jest obecnie [poza golem] (https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals) dla TypeScript. Jaki jest faktyczny problem, który próbujesz rozwiązać? –

+0

A może za pomocą https://frhagn.github.io/Typewriter/ utworzyć swoje klasy w TypeScript? –

+0

@PaulBullivant Uwielbiam to narzędzie, ale myślę, że on szuka czegoś innego –

Odpowiedz

0

Jednym ze sposobów byłoby śledzić wszystkie klasy utworzone w jednej globalnej tablicy:

var allClasses = [] 
function register(c) { 
    allClasses.push(c); 
} 
register(class Animal {}); 
register(class Snake extends Animal {}); 
register(class Horse extends Animal {}); 
register(class Car {}); 

i kiedy trzeba klas, które dziedziczą z danej klasy po prostu odfiltrować te, które chcesz:

Object.values(allClasses).filter((c) => Animal.isPrototypeOf(c)) 

To rozwiązanie nie jest specyficzna dla maszynopis, będziesz pracować z czystym JavaScript zbyt.

0

Potrzebowałem deserializacji odpowiedzi z usługi sieciowej SOAP. Najpierw użyłem istniejącego klienta SOAP, aby uzyskać obiekt ze zwróconego pliku XML, a następnie użyłem deserializatora JSON - tego z http://cloudmark.github.io/Json-Mapping/ - aby deserializować ten obiekt do rzeczywistych typów, które chcę.

Problem polegał na tym, że jedna z zwróconych właściwości została zadeklarowana jako typ podstawowy i mogła zawierać instancje wielu różnych podtypów.

Rozwiązałem go, tworząc dekorator klas, który stosuję do typów pochodnych. Ten dekorator klasy pobiera klasę podstawową i stosuje do niej metadane, opisując klasę pochodną.

Aby zrozumieć poniższy kod, należy wiedzieć, że parser XML -> JSON dodaje właściwość $type, jeśli kod XML z usługi sieciowej zawiera atrybut type, który wskazuje, że polimorfizm jest w grze.

Rzeczywiste metadane zastosowane do typu podstawowego to nazwa typu z XML i konstruktora typu pochodnego.

Meta rejestracji danych i klasa dekorator:

export interface IJsonPolymorphism { 
    name: string; 
    clazz: {new(): any}; 
} 

export function RegisterForPolymorphism(name: string) { 
    return (target) => { 
     let metadata = getJsonPolymorphism(target.__proto__); 
     if (!metadata) { 
      metadata = []; 
     } 
     metadata.push({name: name, clazz: target}); 
     target.__proto__ = Reflect.metadata(jsonPolymorphismMetadataKey, metadata)(target.__proto__); 
     return target; 
    }; 
} 

export function getJsonPolymorphism(target: any): IJsonPolymorphism[] { 
    return Reflect.getMetadata(jsonPolymorphismMetadataKey, target); 
} 

Zastosowanie:

// The base class 
export class PropertyBase { /*...*/ } 

// The derived classes 
// 

@RegisterForPolymorphism("b:PicklistProperty") 
export class PicklistProperty extends PropertyBase { /*...*/ } 
@RegisterForPolymorphism("b:TextProperty") 
export class TextProperty extends PropertyBase { /*...*/ } 

Struny, które są przekazywane do dekoratora klasy są wartości atrybutu w odpowiedzi XML z sieci type usługa.

Kod z Deserializatora sprawia, że ​​korzystanie z niej tak:

if (jsonObject["$type"]) { 
    const polymorphism = getJsonPolymorphism(clazz); 
    if (polymorphism && polymorphism.filter) { 
     const subclassDefinition = polymorphism.filter(x => x.name === jsonObject["$type"])[0]; 
     if (subclassDefinition && subclassDefinition.clazz) { 
      clazz = subclassDefinition.clazz; 
     } 
    } 
} 

Zasadniczo clazz jest konstruktor typu deserializacji obiekt JSON i zastąpienie go konstruktora typu pochodnego.

Kod ten ma obecnie ograniczenie, że dodaje metadane tylko do bezpośredniej klasy bazowej do całej hierarchii. Ale można to łatwo rozwiązać, dostosowując kod wewnątrz RegisterForPolymorphism, aby podejść do drzewa.

4

Jeśli chcesz uzyskać zależność od funkcji, która prawdopodobnie wkrótce się zmieni, ponieważ jest oparta na przestarzałej specyfikacji, a jeśli chcesz używać tylko klas, a nie interfejsów, możesz to zrobić, używając dekorator.

Oto przykład:

hierarchy-tracked.ts

export default function hierarchyTracked(target: new (...args: any[]) => object) { 
    for (const proto of walkPrototypeChain(target)) { 
    if (!Object.hasOwnProperty.call(proto, 'extendedBy')) { 
     const extendedBy: typeof Function.extendedBy = []; 
     Object.defineProperty(proto, 'extendedBy', { 
     get:() => extendedBy 
     }); 
    } 
    // ! is used to suppress a strictNullChecks error on optional. 
    // This is OK since we know it is now defined. 
    proto.extendedBy!.push(target); 
    } 
} 

declare global { 
    interface Function { 
    // Declared as optional because not all classes are extended. 
    extendedBy?: Array<new (...args: any[]) => object>; 
    } 
} 

function* walkPrototypeChain(target: new (...args: any[]) => object) { 
    let proto = Reflect.getPrototypeOf(target); 
    while (proto && proto !== Object) { 
    yield proto; 
    proto = Reflect.getPrototypeOf(proto); 
    } 
} 

animals.ts

import hierarchyTracked from './hierarachy-tracked'; 

export class Animal { 
    alive = true; 
    static get slayable() {return true;} 
    static extinct = false; 
} 

@hierarchyTracked export class Snake extends Animal { 
    isEctotherm = true; 
} 

@hierarchyTracked export class Cobra extends Snake { 
    isDeadly = true; 
} 

@hierarchyTracked export class Horse extends Animal { 
    isEndotherm = true; 
} 
// logs 
Animal.extendedBy && Animal.extendedBy.map(Taxon => Taxon.name) 
    .forEach(name => { 
    console.log(name); 
    }); 
// Snake 
// Cobra 
// Horse 

Snake.extendedBy && Snake.extendedBy.map(Taxon => Taxon.name) 
    .forEach(name => { 
    console.log(name); 
    }); 
// Cobra 

Nie ma potrzeby uciekać się do stanu globalnej i to jest całkiem uporządkowany i wyraźny.

Działa to również z Babel 7, jeśli nie korzystasz z TypeScript. (Zauważ, że te same zastrzeżenia dotyczące użytkowania dekorator wymienione powyżej nadal obowiązują)

Oczywiście to jest trywialne napisać ręcznie, jeśli nie chcesz polegać na dekoratorów:

import trackHierarchy from './hierarachy-tracked'; 

export class Animal { } 

class Snake extends Animal { ... } 
trackHierarchy(Snake); 

export {Snake}; 

powrotem do kodu przykładu powyżej, łatwo to osiągnąć.

To idzie z

var childTypes = assembly.GetTypes().Where(_ => _.IsSubclassOf(typeof(Animal))); 

po prostu

const childClasses = Animal.extendedBy || []; 

Słowo ostrzeżenia

Jeśli znajdziesz się chcąc napisać kod tak, należy zrobić krok do tyłu i upewnij się, że znasz JavaScript. Ten rodzaj wzorca, a nawet przykład użycia, zwykle wskazuje, że ktoś przyszedł do języka z klasycznym nastawieniem, zauważył klasy ES 2015 i zaczął myśleć, że są one powiązane z klasami w językach tradycyjnych.

Klasy ES nie mogą być mniej podobne do klas C#, C++, Java lub Scala.

Przede wszystkim: Klasy w JavaScript to typy , a nie.

Klasy w JavaScript to wartości.

Ich deklaracja jest zasadniczo tylko syntaktycznym cukrem nad prototypami. Wzór, który próbujesz osiągnąć, sugeruje, że możesz tego nie rozumieć dobrze. W szczególności sugeruje, że możesz pomyśleć, że są one specjalne.

+0

To nadal działa w Babel 7, nawet przy masowych zmianach w dekoratorach. Oczywiście, jeśli chcesz być po bezpiecznej stronie, po prostu skorzystasz z formularza funkcji –