6

パブリック API で関数呼び出しの結果を返すための一般的なパターンがいくつかあります。どちらが最善のアプローチであるかは明らかではありません。ベスト プラクティスに関する一般的なコンセンサスはありますか、または少なくとも 1 つのパターンが他のパターンよりも優れているという説得力のある理由はありますか?

更新パブリック API とは、依存アセンブリに公開されるパブリック メンバーを意味します。Web サービスとして公開されている API だけを指しているわけではありません。クライアントが .NET を使用していると仮定できます。

値を返すためのさまざまなパターンを説明するために、以下のサンプル クラスを作成し、それぞれに対する懸念を表す注釈を付けました。

これは少し長い質問ですが、これを検討したのは私だけではないと確信しており、この質問が他の人にとって興味深いものになることを願っています.

public class PublicApi<T>       //  I am using the class constraint on T, because 
    where T: class              //  I already understand that using out parameters
{                               //  on ValueTypes is discouraged (http://msdn.microsoft.com/en-us/library/ms182131.aspx)

    private readonly Func<object, bool> _validate;
    private readonly Func<object, T> _getMethod;

    public PublicApi(Func<object,bool> validate, Func<object,T> getMethod)
    {
        if(validate== null)
        {
            throw new ArgumentNullException("validate");
        }
        if(getMethod== null)
        {
            throw new ArgumentNullException("getMethod");
        }
        _validate = validate;
        _getMethod = getMethod;
    }

    //  This is the most intuitive signature, but it is unclear
    //  if the function worked as intended, so the caller has to
    //  validate that the function worked, which can complicates 
    //  the client's code, and possibly cause code repetition if 
    //  the validation occurs from within the API's method call.  
    //  It also may be unclear to the client whether or not this 
    //  method will cause exceptions.
    public T Get(object argument)
    {
        if(_validate(argument))
        {
            return _getMethod(argument);
        }
        throw new InvalidOperationException("Invalid argument.");
    }

    //  This fixes some of the problems in the previous method, but 
    //  introduces an out parameter, which can be controversial.
    //  It also seems to imply that the method will not every throw 
    //  an exception, and I'm not certain in what conditions that 
    //  implication is a good idea.
    public bool TryGet(object argument, out T entity)
    {
        if(_validate(argument))
        {
            entity = _getMethod(argument);
            return true;
        }
        entity = null;
        return false;
    }

    //  This is like the last one, but introduces a second out parameter to make
    //  any potential exceptions explicit.  
    public bool TryGet(object argument, out T entity, out Exception exception)
    {
        try
        {
            if (_validate(argument))
            {
                entity = _getMethod(argument);
                exception = null;
                return true;
            }
            entity = null;
            exception = null;   // It doesn't seem appropriate to throw an exception here
            return false;
        }
        catch(Exception ex)
        {
            entity = null;
            exception = ex;
            return false;
        }
    }

    //  The idea here is the same as the "bool TryGet(object argument, out T entity)" 
    //  method, but because of the Tuple class does not rely on an out parameter.
    public Tuple<T,bool> GetTuple(object argument)
    {
        //equivalent to:
        T entity;
        bool success = this.TryGet(argument, out entity);
        return Tuple.Create(entity, success);
    }

    //  The same as the last but with an explicit exception 
    public Tuple<T,bool,Exception> GetTupleWithException(object argument)
    {
        //equivalent to:
        T entity;
        Exception exception;
        bool success = this.TryGet(argument, out entity, out exception);
        return Tuple.Create(entity, success, exception);
    }

    //  A pattern I end up using is to have a generic result class
    //  My concern is that this may be "over-engineering" a simple
    //  method call.  I put the interface and sample implementation below  
    public IResult<T> GetResult(object argument)
    {
        //equivalent to:
        var tuple = this.GetTupleWithException(argument);
        return new ApiResult<T>(tuple.Item1, tuple.Item2, tuple.Item3);
    }
}

//  the result interface
public interface IResult<T>
{

    bool Success { get; }

    T ReturnValue { get; }

    Exception Exception { get; }

}

//  a sample result implementation
public class ApiResult<T> : IResult<T>
{
    private readonly bool _success;
    private readonly T _returnValue;
    private readonly Exception _exception;

    public ApiResult(T returnValue, bool success, Exception exception)
    {
        _returnValue = returnValue;
        _success = success;
        _exception = exception;
    }

    public bool Success
    {
        get { return _success; }
    }

    public T ReturnValue
    {
        get { return _returnValue; }
    }

    public Exception Exception
    {
        get { return _exception; }
    }
}
4

3 に答える 3

6
  • Get - 検証の失敗が予想外である場合、または呼び出し元がメソッドを呼び出す前に引数自体を検証できる場合は、これを使用します。

  • TryGet - 検証の失敗が予想される場合に使用します。TryXXX パターンは、.NET Framework で一般的に使用されているため、よく知られていると想定できます (例: Int32.TryParseまたはDictonary <TKey, TValue>.TryGetValue )。

  • 例外なしのTryGet - 例外は、デリゲートとしてクラスに渡されたコードのバグを示している可能性があります。これは、引数が無効な場合_validate、例外をスローする代わりに false を返し、_getMethod呼び出されないためです。

  • GetTupleGetTupleWithException - これまでに見たことのないもの。タプルは自己説明的ではないため、パブリック インターフェイスには適していないため、お勧めしません。

  • GetResult -_validate単純な bool よりも多くの情報を返す必要がある場合に使用します。私は例外をラップするためにそれを使用しません (参照: TryGet with out Exception )。

于 2011-07-21T19:32:57.043 に答える
1

「パブリック API」とは、API が制御外のアプリケーションによって消費され、それらのクライアント アプリがさまざまな言語/プラットフォームで記述されることを意味する場合、非常に基本的な型 (文字列、整数、小数など) を返して使用することをお勧めします。より複雑な型の JSON のようなもの。

クライアントがジェネリックをサポートするかどうかわからないため、パブリック API でジェネリック クラスを公開することはできないと思います。

パターン的には、SOAP ではなく REST に似たアプローチに傾倒します。Martin Fowler は、これが何を意味するかについての優れた記事の概要記事を持っています: http://martinfowler.com/articles/richardsonMaturityModel.html

于 2011-07-21T19:26:29.233 に答える