Auf der Suche nach OO-Design-Ratschlägen

12

Ich entwickle eine App, mit der Ventile in einer industriellen Umgebung geöffnet und geschlossen werden können, und habe mir etwas Einfaches überlegt:

public static void ValveController
{
    public static void OpenValve(string valveName)
    {
        // Implementation to open the valve
    }

    public static void CloseValve(string valveName)
    {
        // Implementation to close the valve
    }
}

(Die Implementierung würde einige Datenbytes an die serielle Schnittstelle schreiben, um das Ventil zu steuern - eine "Adresse", die vom Ventilnamen abgeleitet ist, und entweder eine "1" oder eine "0", um das Ventil zu öffnen oder zu schließen.)

Ein anderer Entwickler fragte, ob wir stattdessen eine separate Klasse für jedes physikalische Ventil erstellen sollten, von denen es Dutzende gibt. Ich bin damit einverstanden, dass es schöner wäre, Code zu schreiben PlasmaValve.Open()als ValveController.OpenValve("plasma"), aber ist das übertrieben?

Außerdem habe ich mich gefragt, wie ich das Design mit Blick auf einige hypothetische zukünftige Anforderungen am besten angehen kann:

  1. Wir werden gebeten, einen neuen Ventiltyp zu unterstützen, für dessen Öffnen und Schließen andere Werte erforderlich sind (nicht 0 und 1).
  2. Wir werden gebeten, ein Ventil zu unterstützen, das auf eine beliebige Position von 0-100 eingestellt werden kann, anstatt einfach "offen" oder "geschlossen" zu sein.

Normalerweise würde ich Vererbung für diese Art von Dingen verwenden, aber ich habe kürzlich angefangen, mich mit "Komposition über Vererbung" zu beschäftigen und mich zu fragen, ob es eine bessere Lösung gibt, wenn Komposition verwendet wird.

Andrew Stephens
quelle
2
Ich würde eine generische Ventilklasse erstellen, die eine Kennung für das spezifische Ventil (keine Zeichenfolge, möglicherweise eine Aufzählung) und alle Informationen enthält, die für den Steuerungsfluss innerhalb der OpenValve / CloseValve-Methoden erforderlich sind. Alternativ können Sie die Ventilklasse abstrakt machen und separate Implementierungen für jede Klasse vornehmen, wobei das Öffnen / Schließen-Ventil nur die Logik innerhalb der angegebenen Ventilklasse für den Fall aufruft, dass verschiedene Ventile unterschiedliche Öffnungs- / Schließmechanismen haben. Der gemeinsame Mechanismus würde in der Basisklasse definiert.
Jimmy Hoffa
2
Sorgen Sie sich nicht um hypothetische zukünftige Anforderungen. YAGNI.
pdr
3
@pdr YAGNI ist eine zweischneidige Klinge, ich stimme zu, es lohnt sich, sie allgemein zu befolgen, aber im Extremfall kann man sagen, dass alles, was zur zukünftigen Wartbarkeit oder Lesbarkeit beiträgt, YAGNI verletzt viele. Das heißt, viele Menschen erkennen, wo sie YAGNI verwenden und wo sie es werfen müssen, da die Berücksichtigung der Zukunft Ihnen ernsthafte Schmerzen ersparen wird. Ich denke nur, man sollte vorsichtig sein und Leuten empfehlen, YAGNI zu folgen, wenn man nicht weiß, wo sie in diesem Spektrum landen werden.
Jimmy Hoffa
2
Mann, 'Komposition über Inhertanz' wird überbewertet. Ich würde eine abstrakte Klasse / Schnittstelle für Valve erstellen und diese dann in PlasmaValve unterteilen. Und dann würde ich sicherstellen, dass mein ValveController mit Valve (s) funktioniert, ohne sich darum zu kümmern, um welche Unterklasse es sich genau handelt.
MrFox
2
@suslik: Auf jeden Fall. Ich habe auch exzellenten Code namens Spaghetti von Leuten gesehen, die die SOLID-Prinzipien nicht verstehen. Damit könnten wir für immer weitermachen. Mein Punkt ist, dass ich mehr Probleme gesehen habe, die dadurch verursacht wurden, dass etablierte Prinzipien (die aus langjähriger Erfahrung hervorgegangen sind) aus der Hand geworfen wurden, als dass ich sie durch zu starkes Festhalten verursacht habe. Aber ich stimme zu, dass beide Extreme gefährlich sind.
pdr

Antworten:

12

Wenn jede Instanz des Ventilobjekts den gleichen Code wie dieser ValveController ausführen würde, wären mehrere Instanzen einer einzelnen Klasse der richtige Weg. In diesem Fall konfigurieren Sie einfach im Konstruktor des Ventilobjekts, welches Ventil es steuert (und wie).

Wenn jedoch für jede Ventilsteuerung ein anderer Code zum Ausführen erforderlich ist und auf dem aktuellen ValveController eine riesige switch-Anweisung ausgeführt wird, die je nach Ventiltyp unterschiedliche Aktionen ausführt, wurde der Polymorphismus nur unzureichend implementiert. In diesem Fall schreiben Sie es in mehrere Klassen mit einer gemeinsamen Basis (falls dies sinnvoll ist) und lassen Sie das Prinzip der einzelnen Verantwortung Ihr Designleitfaden sein.

Steinmetalle
quelle
1
+1 für die Erwähnung typbasierter switch-Anweisungen als Code-Geruch. Ich sehe häufig solche switch-Anweisungen, bei denen der Entwickler behauptet, er habe gerade KISS gefolgt. Perfektes Beispiel dafür, wie Designprinzipien pervertiert werden können
Jimmy Hoffa
2
Mehrere Instanzen vereinfachen möglicherweise auch das Verknüpfen von Ventilen in einer Sequenz, sodass Sie die tatsächliche Anlagenverrohrung als gerichteten Graphen in Ihrem Code modellieren können. Sie können den Klassen auch Geschäftslogik hinzufügen, falls Sie beispielsweise ein Ventil öffnen müssen, wenn ein anderes schließt, um einen Druckaufbau zu vermeiden, oder alle nachgeschalteten Ventile schließen, damit Sie keinen "Wasserschlag" -Effekt erhalten wenn das Ventil wieder geöffnet wird.
TMN
1

Mein Hauptproblem ist die Verwendung von Strings für den Parameter, der das Ventil identifiziert.

Erstellen Sie mindestens eine ValveKlasse, deren getAddressFormat den zugrunde liegenden Implementierungsanforderungen entspricht, und übergeben Sie diese an ValveControllerund stellen Sie sicher, dass Sie keine nicht vorhandenen Ventile erstellen können. Auf diese Weise müssen Sie bei der open- und close-Methode nicht mit falschen Zeichenfolgen umgehen.

Ob Sie Convenience-Methoden erstellen, die das Öffnen und Schließen aufrufen, ValveControllerliegt bei Ihnen, aber um ehrlich zu sein, würde ich die gesamte Kommunikation zum seriellen Port (einschließlich der Codierung) in einer einzigen Klasse halten, die andere Klassen bei Bedarf aufrufen. Dies bedeutet, dass Sie bei der Migration auf einen neuen Controller nur eine Klasse ändern müssen.

Wenn Sie gerne testen, sollten Sie auch ValveControllereinen Singleton erstellen, damit Sie ihn verspotten können (oder eine Trainingsmaschine für die Bediener erstellen).

Ratschenfreak
quelle
Ich habe noch nie jemanden gesehen, der zum Testen einen Singleton empfohlen hat - normalerweise geht es in die andere Richtung.
Kazark
Ehrlich gesagt ist der Singleton mehr, um die Statik zu umgehen, und so kann die Kommunikation synchronisiert werden
Ratschenfreak