Erstellen von Wiederverwendbaren Basis-Klassen in TypeScript mit einem Beispiel aus dem Echten Leben

0
25

Hey CSS-Betrüger! Bryan Hughes war so freundlich, sich ein Konzept aus einem bestehenden post veröffentlichte er auf Umwandlung in Typoskript und nehmen Sie es ein paar paar Schritte weiter in diesem Beitrag zu erarbeiten, zu erstellen wiederverwendbare Basisklassen. Während dieser post erfordert nicht Lesen, der andere, es ist sicherlich lohnt sich, es heraus zu überprüfen, weil es deckt die schwierige Aufgabe umschreiben einer Codebasis und schreiben von unit-tests um den Prozess zu helfen.

Johnny-Five ist eine IoT-und Robotik-Bibliothek für Node.js. Johnny-Five macht es einfach, um die Interaktion mit der hardware, indem Sie einen ähnlichen Ansatz, jQuery nahm, um das web vor einem Jahrzehnt: es normalisiert die Unterschiede zwischen den verschiedenen hardware-Plattformen. Auch wie jQuery, Johnny-Five bietet higher-level Abstraktionen, die es leichter machen die Arbeit mit der Plattform.

Johnny-Five unterstützt eine Vielzahl von Plattformen via IO-Plugins, aus der Arduino-Familie, zu der Tessel, der Raspberry Pi, und viele mehr. Jeder IO-Plugin implementiert eine standardisierte Schnittstelle für die Interaktion mit Low-level-hardware-Peripheriegeräte. Raspi IO, die ich zuerst erstellt vor fünf Jahren, ist das IO-plugin implementiert, dass die Unterstützung für den Raspberry Pi.

Jederzeit gibt es mehrere Implementierungen, die entsprechen eine Sache, es ist eine chance, um code gemeinsam zu nutzen. IO-Plugins sind keine Ausnahme, allerdings haben wir keine gemeinsamen code zwischen IO-Plugins aktuell. Wir haben vor kurzem beschlossen, als Gruppe, wollten wir dies ändern. Der Zeitpunkt war zufällig, da ich war in der Planung umschreiben Raspi IO Typoskript sowieso, also habe ich zugestimmt, diese Aufgabe zu übernehmen.

Die Ziele, die von einer Basisklasse

Für diejenigen, die viele nicht vertraut mit der Verwendung von Klasse-basierte Vererbung zu aktivieren, code-sharing, machen wir einen kurzen Spaziergang durch das, was ich meine, wenn ich sage “eine Basisklasse zu erstellen, um code wiederzuverwenden.”

Eine Basisklasse stellt die Struktur und der common code, die andere Klassen erweitern können. Werfen wir einen Blick auf eine vereinfachte Typoskript Beispiel base-Klasse erweitern wird später:

abstract class Automobil {
private _speed: number = 0;
private _direction: number = 0;
öffentliches Laufwerk(speed: number): void {
diese._speed = Geschwindigkeit;
}

öffentlich-turn(Richtung: number): void {
if (Richtung > 90 || Richtung < -90) {
throw new Error(`Ungültige Richtung “${direction}”`);
}
diese._direction = Richtung;
}

public abstract getNumDoors(): number;
}

Hier haben wir eine Basis-Klasse, nennen wir Automobils. Dies bietet einige grundlegende Funktionen, die gemeinsam von allen Arten von Autos, ob es ein Auto, ein pickup, etc. Beachten Sie, wie diese Klasse ist als Abstrakt markiert, und wie es eine abstrakte Methode namens getNumDoors. Diese Methode ist abhängig von der spezifischen Art von Automobil. Durch die Definition es als eine abstrakte Klasse, die wir sagen, dass wir eine andere Klasse auf diese Klasse erweitern und diese Methode implementieren. Wir können dies tun, mit diesem code:

Klasse-Limousine extends Automobil {
öffentliche getNumDoors(): number {
return 4;
}
}

const myCar = new Limousine();
myCar.getNumDoors(); // gibt 4
myCar.drive(35); // setzt _speed 35
myCar.turn(20); // setzt _direction 20

Wir dehnen die Automobil-Klasse zu erstellen, die eine vier-Tür-Limousine Klasse. Wir können nun alle Methoden der beiden Klassen, als wenn es eine einzelne Klasse.

Für dieses Projekt erstellen wir eine Basis-Klasse aufgerufen, AbstractIO, dass alle E / a-plugin-Autor kann dann verlängern zu implementieren, die eine Plattform-spezifische IO-Plugin.

Die Erstellung der Abstract-IO-Basis-Klasse

Abstract IO ist die Basis-Klasse I erstellt implementieren die IO-Plugin-spec für die Nutzung durch alle IO Plugin-Autoren, und steht heute auf npm.

Jeder Autor / jede Autorin hat Ihren eigenen Stil, einige bevorzugen Typoskript und der andere lieber Vanille JavaScript. Dies bedeutet, dass in Erster Linie Basis-Klasse muss konform zu der Kreuzung von Typoskript best practices und JavaScript best practices. Ermitteln der Schnittmenge von best practices nicht immer offensichtlich obwohl. Um dies zu verdeutlichen, betrachten wir zwei Teile der IO-Plugin-spec: MODI-Eigenschaft und die digitalWrite-Methode, die beide erforderlich sind in IO-Plugins.

Die MODI-Eigenschaft ist ein Objekt, mit dem jede Taste mit einem lesbaren Namen für einen bestimmten Modus-Typ, wie EINGABE, und der Wert der numerischen Konstanten im Zusammenhang mit der Modus-Typ, wie 0. Diese numerischen Werte zeigen sich an anderen stellen in der API, wie die pinMode-Methode. In Typoskript, wir würden wollen, dass die Verwendung einer Enumeration zur Darstellung dieser Werte, aber JavaScript nicht unterstützt wird. In JavaScript ist die beste Vorgehensweise zu definieren, der eine Serie von Konstanten und setzen Sie Sie in ein Objekt “container.”

Wie lösen wir diesen scheinbaren Spagat zwischen best practices? Legen Sie über die anderen! Wir beginnen mit der Definition eines enum in Typoskript und definieren Sie jede mode mit einer expliziten Initialisierung für jeden Wert:

export enum Modus {
EINGABE = 0,
AUSGANG = 1,
ANALOG = 2,
PWM = 3,
SERVO = 4,
STEPPER = 5,
UNBEKANNT = 99
}

Jeder Wert ist gezielt gewählt, um die anzeigen explizit auf die MODI-Eigenschaft gemäß der spec, die implementiert ist als Teil der Abstrakten IO-Klasse mit dem folgenden code:

öffentliche get-MODI() {
return {
EINGABE: Mode.EINGANG,
AUSGANG: – Modus.AUSGABE,
ANALOG: Modus.ANALOG,
PWM: Modus.PWM,
SERVO: Modus.SERVO,
UNBEKANNT: Modus.UNBEKANNT
}
}

Mit diesem, wir haben schöne Enumerationen, wenn wir in den Typoskript-land und ignorieren die numerischen Werte völlig. Wenn wir uns in reinem JavaScript-land, wir haben noch die MODI Eigenschaft, die wir weitergeben können, um im typischen JavaScript-fashion, so dass wir nicht haben, um mit den numerischen Werten direkt.

Aber was über die Methoden in der Basisklasse, abgeleitete Klassen, überschreiben muss? In dem Typoskript Welt würden wir verwenden eine abstrakte Klasse ist, wie wir es im Beispiel oben. Cool! Aber was ist mit der JavaScript-Seite? Abstrakte Methoden sind zusammengestellt aus und gar nicht vorhanden in der Ausgabe JavaScript, also wir haben nichts, um sicherzustellen, Methoden überschrieben werden dort. Ein weiterer Widerspruch!

Leider konnte ich nicht kommen mit einem Weg, zu respektieren beide Sprachen, also ich hatte mit der JavaScript-Ansatz: erstellen Sie eine Methode in der Basisklasse, die eine exception wirft:

öffentliche digitalWrite(pin: string | Nummer, value: number): void {
throw new Error(`digitalWrite wird nicht unterstützt, die von ${dies.name}`);
}

Es ist nicht ideal, da der TypeScript compiler nicht warnt uns, wenn wir nicht das überschreiben der abstrakten Methode. Aber es funktioniert in Typoskript (wir bekommen immer noch die Ausnahme zur Laufzeit), als auch als ein best practice für JavaScript.

Lessons learned

Beim durcharbeiten der beste Weg, um design für beide Typoskript und vanilla JavaScript, habe ich beschlossen, dass die beste Art und Weise zu lösen Unstimmigkeiten:

  1. Verwenden Sie eine gemeinsame best-practice-gemeinsam durch jede Sprache
  2. Wenn das nicht funktioniert, versuchen Sie, mit dem Typoskript best practice “intern”, und ordnen Sie Sie auf einem separaten Vanille JavaScript best practice “extern”, wie wir es für die MODI-Eigenschaft.
  3. Und wenn das nicht funktioniert, standardmäßig verwendet die Vanille JavaScript best practice, wie wir es für die digitalWrite-Methode, und ignorieren das Typoskript best practice.
  4. Diese Schritte, die gut funktioniert für die Erstellung von Abstrakten IO, aber diese sind einfach das, was ich entdeckt habe. Wenn Sie irgendwelche bessere Ideen, ich würde lieben, Sie zu hören in den Kommentaren!

    Ich entdeckte, dass abstrakte Klassen sind nicht sinnvoll, in vanilla-JavaScript-Projekte, da JavaScript nicht über ein solches Konzept. Dies bedeutet, dass Sie sind ein anti-pattern hier, auch wenn abstrakte Klassen sind ein Typoskript best practice. Typoskript auch keine Mechanismen, um zu gewährleisten Typ-Sicherheit beim überschreiben von Methoden, wie das override-Schlüsselwort in C#. Die Lektion hier ist, dass Sie behaupten, dass abstrakte Klassen nicht existieren, wenn Sie auf Vanille-JavaScript-Nutzer als gut.

    Ich habe auch gelernt, dass es wirklich wichtig ist, eine single source of truth ” für gemeinsame Schnittstellen zwischen Modulen. Typoskript verwendet duck typing als Kern-Typ system-Muster, und ist sehr nützlich, in einem bestimmten Typoskript Projekt. Jedoch, synchronisieren von Arten in separate Module mit duck typing stellte sich heraus, um mehr von Wartungs-und Kopfschmerzen als exportieren eine Schnittstelle zwischen einem Modul und importieren Sie Sie in ein anderes. Ich hatte zu halten publishing kleine änderungen an Modulen, bis ich Sie ausgerichtet, auf Ihre Art, was zu überschüssige Versionsnummer Butterfass.

    Die Letzte Lektion, die ich gelernt, ist nicht neu: Holen Sie sich feedback während des gesamten design-Prozess von der Bibliothek die Verbraucher. Mit diesem wissen gehen in, habe ich zwei primäre Runden feedback. Die erste Runde war persönlich bei der Johnny-Five-Mitarbeiter den Gipfel, wo wir mit Brainstorming-Gruppen. Die zweite Runde war ein pull-request öffnete ich gegen mich selbst, obwohl er der einzige Mitarbeiter auf Abstrakte IO mit der Erlaubnis zum Zusammenführen von pull-requests. Die pull-Anfrage von unschätzbarem Wert erwiesen als wir wühlten durch, die details einer Gruppe. Den code am Anfang der PR-bared wenig ähnlichkeit mit dem code, wenn es wurde zusammengeführt, was gut ist!

    Zusammenfassung

    Herstellung von Basis-Klassen, dazu bestimmt, verzehrt zu werden, die von beiden Typoskript und Vanille-JavaScript-Nutzer ist ein meist einfacher Prozess, aber es gibt einige Fallstricke. Da TypeScript ist eine Obermenge von JavaScript, die zwei meistens teilen sich die gleichen best practices. Wenn Unterschiede in best practices entstehen, ein wenig Kreativität erforderlich ist, um den besten Kompromiss.

    Ich es geschafft, meine Ziele zu erreichen, mit Abstrakten IO, und implementiert eine Basisklasse, das sowohl Typoskript und vanilla JavaScript-IO-Plugin Autoren. Stellt sich heraus, es ist (meist) nicht möglich ist, Ihren Kuchen haben und ihn auch Essen!