Schwierigkeiten, diese Klasse offen und geschlossen zu machen

8

Hier ist mein Problem: Ich möchte Eingaben von verschiedenen HID-Geräten wie einem Gamepad, einem Rennsport, einem Joystick usw. lesen. So ziemlich jeder Gamecontroller. Das Problem ist, dass sie alle unterschiedliche Eingänge haben.

Das Gamepad verfügt über Tasten, Schalter und Sticks, während der Rennsport möglicherweise einen Schalthebel hat. Ich habe es geschafft, all diese verschiedenen Komponenten in nur 3 zu abstrahieren, anstatt eine Basisklasse mit allen möglichen Kombinationen zu haben:

abstract class Device
    {
    public Buttons Buttons;
    public Axes Axes;
    public Switches Switches;
    public GearSticks GearSticks;
    //many more
    }

Ich kann jetzt haben:

abstract class Device
{
public Buttons Buttons;   //on or off
public Axes Axes;         //range [-100%:100%]
public Switches Switches; //multiple states
}

Anfangs war ich damit zufrieden, da es alle möglichen Arten von Eingaben abzudecken schien und meine Klasse geschlossen bleiben kann, während sie über alle konkreten Implementierungen erweitert werden kann, da alles auf nur drei Arten von Eingaben abstrahiert werden kann.

ABER dann überlege ich mir was, wenn ich nur das Unvermeidliche verzögere? Was ist, wenn ich eines Tages meiner DeviceKlasse ein weiteres Feld hinzufügen muss ? Es unterstützt so etwas wie einen Trackball nicht!

Gibt es eine Möglichkeit, diese Klasse zukunftssicher zu machen? So wie ich es sehe, würde ich am Ende so etwas haben:

public Device1 : Device
{
//buttons
//trackball
}

public Device2 : Device
{
//Switch
//Axis
}

public Device3 : Device
{
//trackball
//switch
}

Und ich müsste meiner Basisklasse jedes Mal Eigenschaften hinzufügen, wenn etwas Neues implementiert werden muss.

Mihai Bratulescu
quelle
Die einzige Möglichkeit, eine solche Klasse vollständig zukunftssicher zu machen, besteht darin, Schlüssel-Wert-Paare als Zeichenfolgen zu speichern und Werte mit einem typenlosen Protokoll wie Protokollpuffern an Ihre Klassen zu übergeben. Aber HID ist spezifischer als das. Auf welcher Plattform bist du? Es gibt mehrere Hilfsbibliotheken, die diesen Prozess vereinfachen können. Hier ist eine für .NET Framework: github.com/mikeobrien/HidLibrary . Oder dieses: github.com/jcoenraadts/hid-sharp
Robert Harvey
Dies ist eine ausgezeichnete Frage zum Software-Engineering, und ich habe keine Ahnung, warum Sie dafür eine Ablehnung erhalten haben.
Doc Brown
@RobertHarvey Als Sie sagten, Schlüssel-Wert-Paare machten in meinem Kopf etwas Sinn, aber was meinen Sie mit typlosem Protokoll? Ich bin auf der UWP-Plattform.
Mihai Bratulescu
Ein typenloses Protokoll funktioniert vollständig mit Zeichenfolgenpaaren, wie das, das eine URL verwendet: parameter1=value1&parameter2=value2usw.
Robert Harvey

Antworten:

7

Ich bin mir ziemlich sicher, dass dies durch die Einführung eines abstrakten Konzepts InputChannelund von Geräten mit einer konfigurierbaren Liste von Eingangskanälen erreicht werden kann. Ein Eingangskanal hat einen Namen, einen Typ, möglicherweise einige Metadaten, und er muss in der Lage sein, einen "Status" zu erzeugen, der zu diesem Typ passt. Möglicherweise gibt es vordefinierte Kanäle wie eine Schaltfläche, eine Axt oder einen Schalter oder einen neuen Kanal, den Sie derzeit nicht kennen (der jedoch später durch Einführung einer neuen InputChanneluntergeordneten Klasse hinzugefügt werden kann ).

Auf diese Weise wird ein Gerät zu einer Art Metamodell, und Sie benötigen auch eine Möglichkeit, die Gerätezustände zu verwalten, die der Liste der Eingangskanäle des Geräts entsprechen müssen.

Diese Art von generischem Ansatz birgt jedoch ein gewisses Risiko für Überentwicklung, auch als Inner-Platform-Effekt bekannt . Beispielsweise ist es möglicherweise nicht einfach, einem solchen generischen Gerät bestimmte Funktionen oder bestimmte Ereignisse oder Interaktionen zwischen verschiedenen Eingangskanälen hinzuzufügen. Es kann für einen Benutzer Ihrer generischen Gerätebibliothek auch schwieriger sein, diese zu verwenden und zu verstehen.

Beachten Sie, dass es nicht immer vorteilhaft ist, eine möglichst abstrakte Lösung zu erstellen. Das Ändern von Anforderungen an die Hardware erfordert in der Regel einen wesentlich höheren Aufwand für die Implementierung in der Hardware selbst als in der entsprechenden Software. Daher ist es häufig besser, sich an eine spezifischere Lösung in der Software zu halten und die Software bei Bedarf zu ändern.

Doc Brown
quelle
Eine InputChannelDose funktioniert, alles was ich tun muss, ist ihren Status zu aktualisieren und eine zu erhöhen onChangedEvent. Aber Sie haben vielleicht Recht mit der Überentwicklung ... Ich werde dies berücksichtigen. Denken Sie, dass es akzeptabel ist, ab und zu ein weiteres Feld zu einer Klasse hinzuzufügen?
Mihai Bratulescu
1
@MihaiBratulescu: Es hängt davon ab, welche Art von Software Sie schreiben - ein Spiel-Framework für sich selbst oder Ihr Team, in dem Sie die Anwendungsfälle kennen und entsprechend reagieren können, wenn sich die Anforderungen ändern, oder ein allgemeines Framework wie Qt oder das .Net-Framework Wird von Hunderttausenden von Entwicklern in unvorhersehbaren Situationen verwendet. Für letztere ist eine sehr generische und feste Lösung viel wichtiger als für erstere.
Doc Brown
Der Wegweiser vor Ihnen - Ihre nächste Station, die Twilight Zone : "Die Unterstützung für diese Bibliothek ist SEHR begrenzt. Es ist fast unmöglich, Probleme mit so vielen Geräten und Konfigurationen zu beheben." Dies ist aus der hidLibrary @RobertHarvey refs . Laut angedeutet, worauf man sich
einlässt,
3

Die Idee hinter dem Open-Closed-Prinzip ist, dass es weniger wahrscheinlich ist, dass vorhandene Funktionen beschädigt werden, wenn Sie neue Funktionen durch Vererbung implementieren, anstatt eine vorhandene Klasse zu ändern. Und Sie können dies tun, indem Sie von Ihrem Versteck erben. In einem Jahr und ein paar Monaten können Sie ein Hid2020 erstellen, das von Hid erbt und Unterstützung für den Trackball bietet, der im vierten Quartal 2019 erfunden wird. Nach der Erfindung und Popularisierung des Quetschdetektors im Jahr 2023 können Sie eine Hid2024-Klasse erstellen, die stammt von Hid2019 ab.

Das wäre der defensive Ansatz. Aus sauberer Sicht wäre es aber auch etwas schlampig. In Ihrem Fall würde ich keinen Schlaf verlieren, wenn ich gegen O verstoße, und einfach die Basisklasse ändern, wenn sich die Welt um Sie herum ändert. Es scheint nicht so, als würde die Implementierung für Trackball oder eine andere neue Art der Steuerung die Art und Weise beeinflussen, wie Sie jetzt mit Tastendrücken oder Statusänderungen umgehen.

Martin Maat
quelle
Das Gleiche gilt für den 2. Absatz. Kohärentes Design ist das Ding. Und wenn man von kohärent spricht; O / C hat hier bereits seinen Zweck erfüllt. Wenn man einfach O / C betrachtet, wird klar, dass die Klasse zu oft geöffnet (modifiziert) wird, wahrscheinlich aufgrund eines unzureichenden Designs. IMO ist dies die Existenzberechtigung des Open / Closed-Prinzips.
Radarbob