web-dev-qa-db-ja.com

オブジェクトを同じメソッドに2回渡すか、結合されたインターフェイスで統合しますか?

デジタルボードと通信した後にデータファイルを作成する方法があります。

CreateDataFile(IFileAccess boardFileAccess, IMeasurer boardMeasurer)

ここで、boardFileAccessboardMeasurerは、BoardIFileAccessの両方を実装するIMeasurerオブジェクトの同じインスタンスです。この場合、IMeasurerは、ボード上の1つのピンをアクティブに設定して簡単な測定を行う単一の方法に使用されます。この測定のデータは、IFileAccessを使用してボードにローカルに保存されます。 Boardは別のプロジェクトにあります。

CreateDataFileは、迅速な測定とデータの保存によって1つのことを行っているという結論に達しました。同じ方法で両方を行う方が、このコードを使用する他の人にとっては直感的であり、測定し、個別のメソッド呼び出しとしてファイルに書き込みます。

私には、同じオブジェクトをメソッドに2回渡すのは面倒です。私は、IDataFileCreatorIFileAccessを拡張するローカルインターフェースIMeasurerを作成し、必要なものだけを呼び出すBoardインスタンスを含む実装を作成することを検討しましたBoardメソッド。同じボードオブジェクトが測定とファイルの書き込みに常に使用されることを考えると、同じオブジェクトをメソッドに2回渡すのは悪い習慣ですか?もしそうなら、ローカルインターフェースと実装を使用することは適切な解決策ですか?

15
pavuxun

いいえ、これで問題ありません。それは単に、APIが過剰に設計されていることを意味します現在のアプリケーションに関して

しかし、これはneverデータソースと測定者が異なるユースケースがあることを証明していません。 APIのポイントは、アプリケーションプログラマの可能性を提供することであり、そのすべてが使用されるわけではありません。 APIが複雑になってネットの理解度が低下しない限り、ユーザーが実行できるAPIを人為的に制限しないでください。

40
Kilian Foth

@ KilianFothの回答 に同意します。これで問題ありません。

それでも、必要に応じて、両方のインターフェースを実装する単一のオブジェクトを取るメソッドを作成できます。

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

引数が異なるオブジェクトである必要があるという一般的な理由はありません。メソッドで引数が異なる必要がある場合、それは特別な要件であり、契約で明確にする必要があります。

7
Nat

CreateDataFileは、迅速な測定とデータの保存によって1つのことを行っているという結論に達しました。同じ方法で両方を行う方が、このコードを使用する他の人にとっては直感的であり、測定し、個別のメソッド呼び出しとしてファイルに書き込みます。

これが実際の問題だと思います。メソッドは、1つのことをしないです。 異なるデバイスへのI/Oを含む2つの異なる操作を実行しており、どちらも他のオブジェクトにオフロードしています:

  • 測定値を取得する
  • その結果をどこかのファイルに保存します

これらは2つの異なるI/O操作です。特に、最初のものはファイルシステムを変更しません。

実際、暗黙の中間ステップがあることに注意する必要があります。

  • 測定値を取得する
  • 測定値を既知の形式にシリアル化する
  • シリアル化された測定をファイルに保存する

APIはこれらのそれぞれを何らかの形で個別に提供する必要があります。発信者がどこにも保存せずに測定を望まないことをどのようにして知っていますか?彼らが別のソースから測定値を取得したくないことをどのようにして知っていますか?デバイス以外の場所に保存しないようにするにはどうすればよいですか。操作を分離するのには十分な理由があります。 bareの最小値で、個々のピースはすべての呼び出し元に利用可能である必要があります。私のユースケースがそれを必要としない場合、私は測定をファイルに書き込むことを強いられるべきではありません。

例として、次のように操作を分離できます。

IMeasurerには、測定値を取得する方法があります。

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

測定タイプは、stringdecimalのような単純なものかもしれません。そのためのインターフェースやクラスが必要だと主張しているわけではありませんが、ここでは例をより一般的にしています。

IFileAccessには、ファイルを保存する方法がいくつかあります。

interface IFileAccess
{
    void SaveFile(string fileContents);
}

次に、測定値をシリアル化する方法が必要です。それを測定値を表すクラスまたはインターフェースに組み込むか、ユーティリティメソッドを用意します。

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

このシリアル化操作がまだ分離されているかどうかはまだわかりません。

このような分離により、APIが向上します。これにより、callerは、I/Oの実行に関する先入観を強制するのではなく、必要なものとタイミングを決定できます。 呼び出し元は、便利かどうかにかかわらず、valid操作を実行するためのコントロールを持っている必要があります。

操作ごとに個別の実装を作成すると、CreateDataFileメソッドは単に

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

特に、このすべてを行った後のメソッドの付加価値はほとんどありません。上記のコード行は、呼び出し元が直接使用することは難しくありません。また、メソッドは、純粋に便宜上のものです。それはあるべきであり、何かoptionalです。そして、それはAPIが動作する正しい方法です。


関連するすべての部分が除外され、メソッドが便利であることが確認できたら、質問を言い換える必要があります。

発信者の最も一般的な使用例は何ですか?

全体のポイントが、同じボードからの測定と同じボードへの書き込みの典型的な使用例をもう少し便利にすることである場合、Boardクラスで直接使用できるようにすることは完全に理にかなっています。

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

これで利便性が向上しない場合は、このメソッドをまったく使用しません。


これは便利な方法であるため、もう1つ質問があります。

IFileAccessインターフェイスは、測定タイプとそれをシリアル化する方法を知っている必要がありますか?その場合は、IFileAccessにメソッドを追加できます。

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

今、呼び出し側はこれを行うだけです:

fileAccess.SaveFile(measurer.Measure());

これは、質問で考えられているように、あなたの便利な方法と同じくらい短く、おそらくより明確です。

4
jpmc26

消費するクライアントは、単一のアイテムで十分な場合、アイテムのペアを処理する必要はありません。あなたの場合、CreateDataFileが呼び出されるまで、ほとんど動作しません。

あなたが提案する潜在的な解決策は、結合された派生インターフェースを作成することです。ただし、このアプローチでは、両方のインターフェイスを実装する単一のオブジェクトが必要です。これは、基本的に特定の実装に合わせてカスタマイズされるという点で、おそらく制約があり、漏れやすい抽象化です。誰かが別々のオブジェクトに2つのインターフェースを実装したいとしたら、どれほど複雑かを考えてみてください。他のオブジェクトに転送するために、インターフェースの1つですべてのメソッドをプロキシする必要があります。 (FWIW、別のオプションは、1つのオブジェクトが派生インターフェースを介して2つのインターフェースを実装する必要があるのではなく、インターフェースをマージすることです。)

ただし、実装への制約や指示が少ない別のアプローチは、コンポジション内でIFileAccessIMeasurerとペアになっているため、一方が他方にバインドされて参照されるようにすることです。 (これはペアリングも表すため、そのうちの1つの抽象化が多少向上します。)次に、CreateDataFileIFileAccessなどの参照の1つだけを取得し、必要に応じて他の参照を取得できます。 。両方のインターフェースを実装するオブジェクトとしての現在の実装は、単にreturn this;は、コンポジションリファレンスです。ここでは、IMeasurerIFileAccessのゲッターです。

開発のある時点でペアリングが偽であることが判明した場合、つまり、同じファイルアクセスで別のメジャーが使用される場合がありますが、これと同じペアリングをより高いレベルで実行できます。つまり、追加のインターフェイスを導入すると、派生インターフェースではなく、2つのゲッターを備えたインターフェースであり、派生ではなく構成を介してファイルアクセスと測定をペアにします。次に、消費側クライアントは、ペアリングが保持されている限り、1つのアイテムに関係し、必要に応じて(新しいペアリングを構成するために)処理する個々のオブジェクトを持ちます。


別のメモとして、CreateDataFileの所有者を尋ねると、このサードパーティが誰であるかがわかります。 CreateDataFileCreateDataFileの所有オブジェクト/クラス、およびIFileAccessIMeasurerを呼び出す消費クライアントがすでにいくつかあります。状況をより大きな視点で見ると、別の、時にはより良い組織が現れることがあります。コンテキストが不完全であるため、ここで行うのは困難です。

2
Erik Eidt

CreateDataFileがあまりにも多くのことをしていると指摘する人もいます。代わりにBoardが多すぎることをお勧めします。ファイルへのアクセスは、ボードの他の部分とは別の問題のように思えます。

ただし、これが間違いではないと想定した場合、より大きな問題は、インターフェイスをクライアントが定義する必要があることです(この場合はCreateDataFile)。

インターフェース分離の原則 は、クライアントが必要以上のインターフェースに依存する必要がないことを示しています。 this other answer からフレーズを借用すると、これは「クライアントが必要とするものによってインターフェイスが定義される」と言い換えることができます。

現在、他の回答が示唆するように、IFileAccessIMeasurerを使用してこのクライアント固有のインターフェースを作成することは可能ですが、最終的に、このクライアントはそれに合わせたインターフェースを持つ必要があります。

0
Xtros