Objekt zweimal an dieselbe Methode übergeben oder mit kombinierter Schnittstelle konsolidieren?

15

Ich habe eine Methode, die eine Datendatei erstellt, nachdem ich mit einer digitalen Karte gesprochen habe:

CreateDataFile(IFileAccess boardFileAccess, IMeasurer boardMeasurer)

Hier boardFileAccessund boardMeasurersind die gleiche Instanz eines BoardObjekts, das sowohl IFileAccessals auch implementiert IMeasurer. IMeasurerwird in diesem Fall für eine einzelne Methode verwendet, bei der ein Pin auf der Platine aktiviert wird, um eine einfache Messung durchzuführen. Die Daten aus dieser Messung werden dann lokal auf der Karte mit gespeichert IFileAccess. Boardbefindet sich in einem separaten Projekt.

Ich bin zu dem Schluss gekommen, dass CreateDataFileeine Sache darin besteht, eine schnelle Messung durchzuführen und die Daten dann zu speichern. Beides in derselben Methode zu tun, ist für jemanden, der diesen Code verwendet, intuitiver, als eine Messung durchzuführen und in eine Datei zu schreiben als separate Methodenaufrufe.

Mir erscheint es umständlich, dasselbe Objekt zweimal an eine Methode zu übergeben. Ich habe überlegt, eine lokale Schnittstelle zu erstellen IDataFileCreator, die erweitert wird, IFileAccessund IMeasurerdann eine Implementierung zu haben, die eine BoardInstanz enthält, die nur die erforderlichen BoardMethoden aufruft. Wenn man bedenkt, dass immer dasselbe Board-Objekt zum Messen und Schreiben von Dateien verwendet wird, ist es dann eine schlechte Praxis, dasselbe Objekt zweimal an eine Methode zu übergeben? Wenn ja, ist die Verwendung einer lokalen Schnittstelle und Implementierung eine geeignete Lösung?

Pavuxun
quelle
2
Es ist schwer bis unmöglich, die Absicht Ihres Codes anhand der von Ihnen verwendeten Namen zu erkennen. Eine Schnittstelle mit dem Namen IDataFileCreator, die an eine Methode mit dem Namen CreateDataFile übergeben wird, ist umwerfend. Wetteifern sie um die Verantwortung, Daten zu speichern? Von welcher Klasse ist CreateDataFile überhaupt eine Methode? Messen hat nichts mit persistierenden Daten zu tun, so viel ist klar. Ihre Frage bezieht sich nicht auf das größte Problem, das Sie mit Ihrem Code haben.
Martin Maat
Ist es jemals vorstellbar, dass Ihr Dateizugriffsobjekt und Ihr Vermessungsobjekt zwei verschiedene Objekte sind? Ich würde Ja sagen. Wenn Sie es jetzt ändern, müssen Sie es zurück in Version 2 ändern , die das Durchführen von Messungen über das Netzwerk unterstützt.
user253751
2
Hier ist jedoch eine andere Frage: Warum sind der Datendateizugriff und die Messobjekte überhaupt gleich?
user253751

Antworten:

40

Nein, das ist vollkommen in Ordnung. Dies bedeutet lediglich, dass die API in Bezug auf Ihre aktuelle Anwendung überarbeitet wurde .

Dies beweist jedoch nicht, dass es niemals einen Anwendungsfall geben wird, in dem die Datenquelle und der Vermesser unterschiedlich sind. Der Sinn einer API besteht darin, dem Anwendungsprogrammierer Möglichkeiten anzubieten, von denen nicht alle genutzt werden. Sie sollten nicht künstlich einschränken, was API-Benutzer tun können, es sei denn, dies erschwert die API, sodass die Verständlichkeit im Netz abnimmt.

Kilian Foth
quelle
7

Vereinbaren Sie mit @ KilianFoth Antwort , dass dies völlig in Ordnung ist.

Wenn Sie möchten, können Sie dennoch eine Methode erstellen, die ein einzelnes Objekt verwendet, das beide Schnittstellen implementiert:

public object CreateDataFile<T_BoardInterface>(
             T_BoardInterface boardInterface
    )
    where T_BoardInterface : IFileAccess, IMeasurer
{
    return CreateDataFile(
                boardInterface
            ,   boardInterface
        );
}

Es gibt keinen allgemeinen Grund, warum Argumente unterschiedliche Objekte sein müssen, und wenn für eine Methode andere Argumente erforderlich wären, wäre dies eine besondere Anforderung, die der Vertrag klarstellen sollte.

Nat
quelle
4

Ich bin zu dem Schluss gekommen, dass CreateDataFileeine Sache darin besteht, eine schnelle Messung durchzuführen und die Daten dann zu speichern. Beides in derselben Methode zu tun, ist für jemanden, der diesen Code verwendet, intuitiver, als eine Messung durchzuführen und in eine Datei zu schreiben als separate Methodenaufrufe.

Ich denke, das ist eigentlich dein Problem. Die Methode macht nicht eine Sache. Es werden zwei unterschiedliche Vorgänge ausgeführt, bei denen E / A-Vorgänge für verschiedene Geräte ausgeführt werden. Beide Vorgänge werden auf andere Objekte heruntergeladen:

  • Messung abrufen
  • Speichern Sie das Ergebnis in einer Datei

Dies sind zwei verschiedene E / A-Operationen. Insbesondere verändert der erste Befehl das Dateisystem in keiner Weise.

In der Tat sollten wir beachten, dass es einen impliziten mittleren Schritt gibt:

  • Messung abrufen
  • Serialisieren Sie die Messung in ein bekanntes Format
  • Speichern Sie die serialisierte Messung in einer Datei

Ihre API sollte diese in irgendeiner Form separat bereitstellen. Woher wissen Sie, dass ein Anrufer keine Messung durchführen möchte, ohne sie irgendwo zu speichern? Woher wissen Sie, dass sie keine Messung von einer anderen Quelle erhalten möchten? Woher wissen Sie, dass sie es nicht an einem anderen Ort als dem Gerät aufbewahren möchten? Es gibt gute Gründe, die Operationen zu entkoppeln. Bei einem bloßen Minimum, sollte jedes einzelne Stück sein verfügbar zu jedem Anrufer. Ich sollte nicht gezwungen sein, die Messung in eine Datei zu schreiben, wenn mein Anwendungsfall dies nicht erfordert.

Beispielsweise können Sie die Vorgänge wie folgt trennen.

IMeasurer hat eine Möglichkeit, die Messung abzurufen:

public interface IMeasurer
{
    IMeasurement Measure(int someInput);
}

Ihr Messtyp könnte einfach etwas Einfaches sein, wie ein stringoder decimal. Ich bestehe nicht darauf, dass Sie eine Schnittstelle oder Klasse dafür benötigen, aber das Beispiel hier wird dadurch allgemeiner.

IFileAccess hat eine Methode zum Speichern von Dateien:

interface IFileAccess
{
    void SaveFile(string fileContents);
}

Dann brauchen Sie eine Möglichkeit, eine Messung zu serialisieren. Integrieren Sie dies in die Klasse oder Schnittstelle, die eine Messung darstellt, oder verwenden Sie eine Dienstprogrammmethode:

interface IMeasurement
{
    // As part of the type
    string Serialize();
}

// Utility method. Makes more sense if the measurement is not a custom type.
public static string SerializeMeasurement(IMeasurement m)
{
    return ...
}

Es ist nicht klar, ob Sie diese Serialisierungsoperation noch getrennt haben.

Diese Art der Trennung verbessert Ihre API. Auf diese Weise kann der Anrufer entscheiden, was er wann benötigt, anstatt Ihre vorgefassten Vorstellungen über die Ausführung der E / A zu erzwingen. Anrufer sollten die Kontrolle haben, um eine gültige Operation auszuführen , unabhängig davon, ob Sie dies für nützlich halten oder nicht.

Sobald Sie für jede Operation eine eigene Implementierung haben, ist Ihre CreateDataFileMethode nur noch eine Abkürzung für

fileAccess.SaveFile(SerializeMeasurement(measurer.Measure()));

Bemerkenswert ist, dass Ihre Methode nur einen geringen Mehrwert bietet, wenn Sie dies alles erledigt haben. Die obige Codezeile ist für Ihre Anrufer nicht schwer direkt zu verwenden, und Ihre Methode dient höchstens der Bequemlichkeit. Es sollte und ist etwas optionales . Und das ist die richtige Art und Weise, wie sich die API verhält.


Sobald alle relevanten Teile herausgerechnet wurden und wir festgestellt haben, dass die Methode nur eine Annehmlichkeit ist, müssen wir Ihre Frage umformulieren:

Was wäre der häufigste Anwendungsfall für Ihre Anrufer?

Wenn es darum geht, den typischen Anwendungsfall des Messens und Beschreibens derselben Tafel ein wenig komfortabler zu gestalten, ist es durchaus sinnvoll, sie direkt in der BoardKlasse verfügbar zu machen :

public class Board : IMeasurer, IFileAccess
{
    // Interface methods...

    /// <summary>
    /// Convenience method to measure and immediate record measurement in
    /// default location.
    /// </summary>
    public void ReadAndSaveMeasurement()
    {
        this.SaveFile(SerializeMeasurement(this.Measure()));
    }
}

Wenn dies den Komfort nicht verbessert, würde ich mich überhaupt nicht um die Methode kümmern.


Dies ist eine bequeme Methode, die eine weitere Frage aufwirft.

Sollte die IFileAccessSchnittstelle über den Messtyp Bescheid wissen und wissen, wie er serialisiert werden kann? In diesem Fall können Sie eine Methode hinzufügen, um IFileAccess:

interface IFileAccess
{
    void SaveFile(string fileContents);
    void SaveMeasurement(IMeasurement m);
}

Jetzt machen die Anrufer einfach Folgendes:

fileAccess.SaveFile(measurer.Measure());

Das ist genauso kurz und wahrscheinlich klarer als Ihre Bequemlichkeitsmethode, wie in der Frage gedacht.

jpmc26
quelle
2

Der konsumierende Kunde sollte sich nicht mit einem Artikelpaar befassen müssen, wenn ein einzelner Artikel ausreicht. In Ihrem Fall tun sie es fast nicht, bis der Aufruf von CreateDataFile.

Die mögliche Lösung, die Sie vorschlagen, besteht darin, eine kombinierte abgeleitete Schnittstelle zu erstellen. Dieser Ansatz erfordert jedoch ein einzelnes Objekt, das beide Schnittstellen implementiert, was ziemlich einschränkend ist, wohl eine undichte Abstraktion, da es grundsätzlich an eine bestimmte Implementierung angepasst ist. Überlegen Sie, wie kompliziert es wäre, wenn jemand die beiden Schnittstellen in separaten Objekten implementieren möchte: Er müsste alle Methoden in einer der Schnittstellen als Proxy speichern, um zum anderen Objekt weiterzuleiten. (FWIW, eine andere Option besteht darin, nur die Schnittstellen zusammenzuführen, anstatt dass ein Objekt zwei Schnittstellen über eine abgeleitete Schnittstelle implementieren muss.)

Ein anderer Ansatz, der die Implementierung weniger einschränkt / diktiert, besteht darin, dass er IFileAccessmit einer IMeasurerIn-Komposition gepaart wird , sodass einer von ihnen an den anderen gebunden ist und diesen referenziert. (Dies erhöht etwas die Abstraktion von einem von ihnen, da es nun auch die Paarung darstellt.) Dann CreateDataFilekönnte beispielsweise nur einer der Verweise genommen IFileAccesswerden und der andere nach Bedarf erhalten. Ihre aktuelle Implementierung als Objekt, das beide Schnittstellen implementiert, dient lediglich return this;als Kompositionsreferenz, hier als Getter für IMeasurerin IFileAccess.

Wenn sich das Pairing zu einem bestimmten Zeitpunkt in der Entwicklung als falsch herausstellt, das heißt, dass manchmal ein anderer Measurer mit demselben Dateizugriff verwendet wird, können Sie dasselbe Pairing durchführen, jedoch auf einer höheren Ebene, was bedeutet, dass die zusätzliche Schnittstelle eingeführt wurde keine abgeleitete Schnittstelle sein, sondern eine Schnittstelle mit zwei Gettern, die einen Dateizugriff und einen Vermesser über die Komposition und nicht über die Ableitung koppeln. Der konsumierende Kunde hat dann einen Gegenstand, mit dem er sich befassen muss, solange die Paarung besteht, und bei Bedarf müssen einzelne Objekte bearbeitet werden (um neue Paarungen zu erstellen).


In einem anderen Punkt möchte ich fragen, wem es gehört CreateDataFileund wer dieser Dritte ist. Wir haben bereits einen konsumierenden Client CreateDataFile, der das besitzende Objekt / die besitzende Klasse von CreateDataFileund das IFileAccessund aufruft IMeasurer. Manchmal, wenn wir einen größeren Blick auf den Kontext werfen, können alternative, manchmal bessere Organisationen auftreten. Hier ist es schwierig, da der Kontext unvollständig ist, also nur Denkanstöße.

Erik Eidt
quelle
0

Einige haben darauf hingewiesen, dass CreateDataFiledies zu viel bedeutet. Ich könnte vorschlagen, dass stattdessen Boardzu viel getan wird, da der Zugriff auf eine Datei vom Rest des Boards ein separates Anliegen zu sein scheint.

Wenn wir jedoch davon ausgehen, dass dies kein Fehler ist, besteht das größere Problem darin, dass die Schnittstelle in diesem Fall vom Client definiert werden sollte CreateDataFile.

Das Prinzip der Schnittstellentrennung besagt, dass der Client nicht mehr von einer Schnittstelle abhängen muss, als er benötigt. Ausgehend von dieser anderen Antwort kann dies wie folgt umschrieben werden: "Eine Schnittstelle wird durch die Bedürfnisse des Kunden definiert."

Jetzt ist es möglich, diese clientspezifische Schnittstelle mit IFileAccessund zu erstellen, IMeasurerwie andere Antworten vermuten lassen. Letztendlich sollte dieser Client jedoch eine maßgeschneiderte Schnittstelle haben.

Xtros
quelle
@ Downvoter - Was ist daran falsch oder kann verbessert werden?
Xtros