Passare l'oggetto due volte allo stesso metodo o consolidare con l'interfaccia combinata?


15

Ho un metodo che crea un file di dati dopo aver parlato con una scheda digitale:

CreateDataFile(IFileAccess boardFileAccess, IMeasurer boardMeasurer)

Qui boardFileAccesse boardMeasurersono la stessa istanza di un Boardoggetto che implementa sia IFileAccesse IMeasurer. IMeasurerviene utilizzato in questo caso per un singolo metodo che imposterà un pin sulla scheda attivo per effettuare una misurazione semplice. I dati di questa misurazione vengono quindi memorizzati localmente sulla scheda utilizzando IFileAccess. Boardsi trova in un progetto separato.

Sono giunto alla conclusione che CreateDataFilesta facendo una cosa eseguendo una misurazione rapida e quindi memorizzando i dati, e fare entrambi con lo stesso metodo è più intuitivo per qualcun altro che utilizza questo codice, quindi è necessario effettuare una misurazione e scrivere su un file come chiamate di metodo separate.

Per me, sembra imbarazzante passare lo stesso oggetto a un metodo due volte. Ho preso in considerazione l'idea di creare un'interfaccia locale IDataFileCreatorche si estenda IFileAccesse IMeasurerquindi abbia un'implementazione contenente Boardun'istanza che chiamerà semplicemente i Boardmetodi richiesti . Considerando che lo stesso oggetto board sarebbe sempre usato per la misurazione e la scrittura di file, è una cattiva pratica passare lo stesso oggetto a un metodo due volte? In tal caso, l'utilizzo di un'interfaccia locale e l'implementazione rappresentano una soluzione appropriata?


2
È difficile capire l'intento del tuo codice dai nomi che stai usando. Un'interfaccia denominata IDataFileCreator che viene passata a un metodo chiamato CreateDataFile è sbalorditiva. Sono in competizione per la responsabilità di conservare i dati? In quale classe è comunque CreateDataFile un metodo? La misurazione non ha nulla a che fare con i dati persistenti, tanto è chiaro. La tua domanda non riguarda il problema più grande che stai riscontrando con il tuo codice.
Martin Maat,

È mai immaginabile che il tuo oggetto di accesso al file e il tuo oggetto misuratore possano essere due oggetti diversi? Direi di si. Se lo cambi ora, dovrai ripristinarlo nella versione 2 che supporta l'esecuzione di misurazioni attraverso la rete.
user253751

2
Ecco un'altra domanda: perché, in primo luogo, l'accesso ai file di dati e gli oggetti di misurazione sono gli stessi?
user253751

Risposte:


40

No, va benissimo. Significa semplicemente che l'API è troppo ingegnerizzata rispetto alla tua attuale applicazione .

Ma ciò non dimostra che non ci sarà mai un caso d'uso in cui l'origine dati e il misuratore sono diversi. Il punto di un'API è offrire le possibilità del programmatore dell'applicazione, che non verranno utilizzate tutte. Non dovresti limitare artificialmente ciò che gli utenti API possono fare a meno che ciò non complica l'API in modo che la comprensibilità della rete diminuisca.


7

Concordo con la risposta di @ KilianFoth che questo va benissimo.

Tuttavia, se vuoi, puoi creare un metodo che accetta un singolo oggetto che implementa entrambe le interfacce:

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

Non vi è alcun motivo generale per cui gli argomenti debbano essere oggetti diversi e se un metodo richiedesse che gli argomenti fossero diversi, sarebbe un requisito speciale che il suo contratto dovrebbe chiarire.


4

Sono giunto alla conclusione che CreateDataFilesta facendo una cosa eseguendo una misurazione rapida e quindi memorizzando i dati, e fare entrambi con lo stesso metodo è più intuitivo per qualcun altro che utilizza questo codice, quindi è necessario effettuare una misurazione e scrivere su un file come chiamate di metodo separate.

Penso che questo sia il tuo problema, in realtà. Il metodo non sta facendo nulla. Esegue due operazioni distinte che coinvolgono l'I / O su dispositivi diversi , entrambi i quali vengono scaricati su altri oggetti:

  • Prendi una misura
  • Salva quel risultato in un file da qualche parte

Queste sono due diverse operazioni di I / O. In particolare, il primo non muta il file system in alcun modo.

In effetti, dovremmo notare che c'è un passaggio intermedio implicito:

  • Prendi una misura
  • Serializzare la misurazione in un formato noto
  • Salvare la misura serializzata in un file

L'API dovrebbe fornire ciascuna di queste separatamente in una forma. Come fai a sapere che un chiamante non vorrà prendere una misura senza memorizzarla da nessuna parte? Come fai a sapere che non vorranno ottenere una misurazione da un'altra fonte? Come fai a sapere che non vorranno memorizzarlo in un luogo diverso dal dispositivo? Vi sono buone ragioni per disaccoppiare le operazioni. Nel corso di una nuda minimo, ogni singolo pezzo dovrebbe essere disponibile a qualsiasi chiamante. Non dovrei essere costretto a scrivere la misura su un file se il mio caso d'uso non lo richiede.

Ad esempio, è possibile separare le operazioni in questo modo.

IMeasurer ha un modo per recuperare la misurazione:

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

Il tipo di misurazione potrebbe essere semplicemente qualcosa di semplice, come un stringo decimal. Non sto insistendo sul fatto che hai bisogno di un'interfaccia o di una classe per questo, ma rende l'esempio qui più generale.

IFileAccess ha qualche metodo per salvare i file:

interface IFileAccess
{
    void SaveFile(string fileContents);
}

Quindi è necessario un modo per serializzare una misurazione. Incorporalo nella classe o nell'interfaccia che rappresenta una misurazione o utilizza un metodo di utilità:

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 ...
}

Non è chiaro se questa operazione di serializzazione sia stata ancora separata.

Questo tipo di separazione migliora la tua API. Consente al chiamante di decidere di cosa ha bisogno e quando, anziché forzare le idee preconcette su quale I / O deve eseguire. I chiamanti dovrebbero avere il controllo per eseguire qualsiasi operazione valida , che tu ritenga utile o meno.

Una volta che hai implementazioni separate per ogni operazione, il tuo CreateDataFilemetodo diventa solo una scorciatoia per

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

In particolare, il tuo metodo aggiunge pochissimo valore una volta che hai fatto tutto questo. La suddetta riga di codice non è difficile da usare direttamente per i chiamanti e il tuo metodo è puramente per praticità al massimo. Dovrebbe essere ed è qualcosa di facoltativo . E questo è il modo corretto di comportarsi per l'API.


Una volta che tutte le parti rilevanti sono state prese in considerazione e abbiamo riconosciuto che il metodo è solo una comodità, dobbiamo riformulare la tua domanda:

Quale sarebbe il caso d'uso più comune per i tuoi chiamanti?

Se l'intero punto è rendere il caso d'uso tipico di misurare e scrivere sulla stessa lavagna leggermente più conveniente, allora ha perfettamente senso renderlo disponibile Boarddirettamente sulla classe:

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()));
    }
}

Se ciò non migliora la praticità, non mi preoccuperei affatto del metodo.


Questo essere un metodo di convenienza solleva un'altra domanda.

L' IFileAccessinterfaccia dovrebbe conoscere il tipo di misurazione e come serializzarlo? In tal caso, è possibile aggiungere un metodo a IFileAccess:

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

Ora i chiamanti fanno solo questo:

fileAccess.SaveFile(measurer.Measure());

che è altrettanto breve e probabilmente più chiaro del tuo metodo di praticità come concepito nella domanda.


2

Il cliente consumatore non dovrebbe avere a che fare con una coppia di articoli quando è sufficiente un singolo articolo. Nel tuo caso, quasi non lo fanno, fino all'invocazione di CreateDataFile.

La potenziale soluzione che suggerisci è quella di creare un'interfaccia derivata combinata. Tuttavia, questo approccio richiede un singolo oggetto che implementa entrambe le interfacce, il che è piuttosto vincolante, probabilmente un'astrazione che perde in quanto è sostanzialmente personalizzato per una particolare implementazione. Considera quanto sarebbe complicato se qualcuno volesse implementare le due interfacce in oggetti separati: dovrebbero inoltrare tutti i metodi in una delle interfacce per inoltrare all'altro oggetto. (FWIW, un'altra opzione è semplicemente unire le interfacce piuttosto che richiedere un oggetto deve implementare due interfacce tramite un'interfaccia derivata.)

Tuttavia, un altro approccio che è meno vincolante / dettante per l'implementazione è che IFileAccessè accoppiato con una IMeasurercomposizione, in modo che uno di essi sia legato e faccia riferimento all'altro. (Questo in qualche modo aumenta l'astrazione di uno di essi poiché ora rappresenta anche l'accoppiamento.) Quindi CreateDataFilepotrebbe prendere solo uno dei riferimenti, diciamo IFileAccess, e ottenere comunque l'altro secondo necessità. La tua attuale implementazione come oggetto che implementa entrambe le interfacce sarebbe semplicemente return this;per riferimento di composizione, qui il getter per IMeasurerin IFileAccess.

Se l'accoppiamento risulta essere falso ad un certo punto nello sviluppo, vale a dire a volte viene utilizzato un misuratore diverso con lo stesso accesso al file, è possibile eseguire lo stesso accoppiamento ma a un livello superiore, il che significa che l'interfaccia aggiuntiva introdotta sarebbe non essere un'interfaccia derivata, ma piuttosto un'interfaccia che ha due getter, accoppiando un accesso al file e un misuratore insieme tramite la composizione piuttosto che la derivazione. Il cliente che consuma ha quindi un elemento di cui occuparsi finché dura l'associazione e singoli oggetti da trattare (per comporre nuovi accoppiamenti) quando necessario.


In un'altra nota, potrei chiedere chi possiede CreateDataFilee la domanda va a chi è questa terza parte. Abbiamo già un client di consumo che invoca CreateDataFile, l'oggetto proprietario / classe di CreateDataFile, e IFileAccesse IMeasurer. A volte, quando osserviamo più ampiamente il contesto, possono apparire organizzazioni alternative, a volte migliori. Difficile fare qui poiché il contesto è incompleto, quindi solo spunti di riflessione.


0

Alcuni hanno accennato che CreateDataFilesta facendo troppo. Potrei suggerire che invece Boardsta facendo troppo, poiché l'accesso a un file sembra una preoccupazione separata dal resto della scheda.

Tuttavia, se assumiamo che questo non sia un errore, il problema maggiore è che l'interfaccia dovrebbe essere definita dal client, in questo caso CreateDataFile.

Il principio di segregazione dell'interfaccia afferma che il client non dovrebbe dipendere da più di un'interfaccia rispetto a ciò di cui ha bisogno. Prendendo in prestito la frase da questa altra risposta , questa può essere parafrasata come "un'interfaccia è definita da ciò di cui il cliente ha bisogno".

Ora, è possibile comporre questa interfaccia specifica per client usando IFileAccesse IMeasurercome suggeriscono altre risposte, ma alla fine questo client dovrebbe avere un'interfaccia su misura per essa.


@Downvoter - Che dire di questo non è corretto o può essere migliorato?
Xtros,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.