10

私は方法を持っています:

public ??? AuthManager.Login(Credentials credentials)

このメソッドの有効な出力値のセットは次のとおりです。

  1. 成功 (+accountId)
  2. 失敗: AccountLockedOut
  3. 失敗: UsernameNotFound
  4. 失敗: InvalidPassword (+失敗した試行回数)

戻り値の型に応じて、さまざまなビューがユーザーに表示されます (はい、AccountLockedOut のビューは InvalidPassword とは異なります)。

私は行くことができます:

public class LoginAttemptResult {
    public bool Succeeded { get; set; }
    public AccountId AccountId { get; set; } // for when success
    public LoginAttemptResultEnumType Result { get;set; } // Success, Lockedout, UsernameNotFound, InvalidPassword  
    public int FailedAttemptCount { get; set; } // only used for InvalidPassword
}

私はこれが好きではなく、より良い解決策を探しています。第 1 に、これは部分的に初期化されたオブジェクトになり、2 つはインターフェース分離の原則に違反し、3 つは SRP に違反します。

更新:例外をスローすることもエレガントな解決策ではありません。なぜならInvalidPassword、それは例外ではないからです。失敗した DB 接続は例外です。null 引数は例外です。InvalidPassword有効な予想される応答です。

より良い解決策は、クラスの階層を作成することだと思います:

abstract class LoginAttemptResult
    sealed class LoginSuccess : LoginAttemptResult { AccountId }
    abstract class LoginFailure : LoginAttemptResult
        sealed class InvalidPasswordLoginFailure : LoginFailure { FailedAttemptCount }
        sealed class AccountLockedoutLoginFailure : LoginFailure

メソッドの呼び出し元は、次のLoginようにする必要があります。

if (result is LoginSuccess) { 
    ..."welcome back mr. account id #" + (result as LoginSuccess).AccountId
}
else if (result is InvalidPasswordLoginFailure ) { 
    ..."you failed " + (result as InvalidPasswordLoginFailure).FailedAttemptCount + " times"
}

このアプローチには(概念的に)何も問題はありません(付属のクラスの数を除いて)。

このアプローチで他に何が問題なのですか?

このアプローチは、本質的に F# の判別共用体 (DU)であることに注意してください。

これをモデル化するより良い方法はありますか?すでに機能するソリューションがいくつかありますが、機能するエレガントなソリューションが必要です。

4

7 に答える 7

4

結果クラスが大幅に異なり、それぞれに個別のクラスが必要な場合、ソリューションは問題ないと思います。しかし、それについてはよくわかりません。結果ごとにこのクラスを試してください。

/// <summary>
/// Immutable, created by the server
/// </summary>
class LoginResult
{
    /// <summary>
    /// Null in the case of failure
    /// </summary>
    public int? Id { get; private set; }

    /// <summary>
    /// Null in the case of success
    /// </summary>
    public string FailReason { get; private set; }

    /// <summary>
    /// Always >= 1
    /// </summary>
    public int AttemptNumber { get; private set; }

    public LoginResult(int id, int attemptNumber)
    {
        Id = id;
        AttemptNumber = attemptNumber;
    }

    public LoginResult(string reason, int attemptNumber)
    {
        FailReason = reason;
        AttemptNumber = attemptNumber;
    }
}

Id認証ロジックは非常に複雑になる可能性があり、必要なプロパティだけFailReasonではないと想像できます。AttemptNumberこの場合、より具体的な例を提示する必要があります。必要に応じて、ロジックに適合する抽象化を構築しようとします。この特定のケースでは、抽象化の意味がありません。

于 2013-05-03T15:45:34.117 に答える
0

これは、私のすべての要件 (読みやすさ、テストのしやすさ、発見のしやすさ、および審美性) を満たすソリューションです。

コード (実装は元のタスクとほとんど変わらないことに注意してください。ただし、概念はそのままです):

public class AuthResult {
    // Note: impossible to create empty result (where both success and failure are nulls).
    // Note: impossible to create an invalid result where both success and failure exist.
    private AuthResult() {}
    public AuthResult(AuthSuccess success) {
        if (success == null) throw new ArgumentNullException("success");
        this.Success = success;
    }
    public AuthResult(AuthFailure failure) {
        if (failure == null) throw new ArgumentNullException("failure");
        this.Failure = failure;
    }
    public AuthSuccess Success { get; private set; }
    public AuthFailure Failure { get; private set; }
}

public class AuthSuccess {
    public string AccountId { get; set; }
}

public class AuthFailure {
    public UserNotFoundFailure UserNotFound { get; set; }
    public IncorrectPasswordFailure IncorrectPassword { get; set; }
}

public class IncorrectPasswordFailure : AuthResultBase {
    public int AttemptCount { get; set; }
}

public class UserNotFoundFailure : AuthResultBase {
    public string Username { get; set; }
}

AuthResult関数範囲の異質で階層的な性質がどのように正しくモデル化されているかに注目してください。

そして、次の暗黙の演算子を追加すると:

public static implicit operator bool(AuthResultBase result) {
    return result != null;
}

次のように結果を使用できます。

var result = authService.Auth(credentials);
if (result.Success) {
    ...
}

これは(ほぼ間違いなく)以下よりも優れています。

if (result.Success != null) {
    ...
}
于 2013-09-10T16:23:40.720 に答える
0

タプルを返すことができます

public Tuple<T1,T2> AuthManager.Login(Credentials credentials){
//do your stuff here
return new Tuple<T1,T2>(valueOfT1,valueOfT2);
}
于 2013-05-03T15:42:16.353 に答える
0

セキュリティ API はそれほど多くの情報を公開すべきではありません。あなたが投稿した API は、攻撃者がアカウントを乗っ取ろうとするのを助ける以外に、クライアントに有益な情報を提供しません。ログイン方法は、パス/フェイル情報と、必要な承認メカニズムに渡すことができるトークンのみを提供する必要があります。

// used by clients needing to authenticate
public interfac ISecurity {
  AuthenticationResponse Login(Credentials credentials);
}

// the response from calling ISecurity.Login
public class AuthenticationResponse {

  internal AuthenticationResponse(bool succeeded, AuthenticationToken token, string accountId) {
    Succeeded = succeeded;
    Token = token;
  }

  // if true then there will be a valid token, if false token is undefined
  public bool Succeeded { get; private set; }

  // token representing the authenticated user.
  // document the fact that if Succeeded is false, then this value is undefined
  public AuthenticationToken Token { get; private set; }

}

// token representing the authenticated user. simply contains the user name/id
// for convenience, and a base64 encoded string that represents encrypted bytes, can
// contain any information you want.
public class AuthenticationToken {

  internal AuthenticationToken(string base64EncodedEncryptedString, string accountId) {
    Contents = base64EncodedEncryptedString;
    AccountId = accountId;
  }

  // secure, and user can serialize it
  public string Contents { get; private set; }

  // used to identify the user for systems that aren't related to security
  // (e.g. customers this user has)
  public string AccountId { get; private set; }

}


// simplified, but I hope you get the idea. It is what is used to authenticate
// the user for actions (i.e. read, write, modify, etc.)
public interface IAuthorization {
  bool HasPermission(AuthenticationToken token, string permission); 
}

この API にはログイン試行がないことに気付くでしょう。クライアントは、ログインに関連するルールを気にする必要はありません。ISecurityインターフェイスの実装者は、ログインの試行に遅れずについていく必要があり、資格情報のセットが正常に渡されたが、試行回数を超えた場合は失敗を返す必要があります。

失敗時の単純なメッセージは、次の行に沿って何かを読み取る必要があります。

Could not log you on at this time. Check that your username and/or password are correct, or please try again later.
于 2013-05-03T17:34:53.653 に答える
0

クラスを抽象化する場合は、子クラスに強制的に実装させるLoginAttemptResult抽象プロパティを追加できます。Message

public abstract class LoginAttemptResult
{        
    public abstract string Message { get; }

    // any other base methods/properties and abstract methods/properties here
}

次に、あなたの子供たちは次のようになります。

public class LoginSuccess : LoginAttemptResult
{
    public override string Message 
    { 
        get
        {
            return "whatever you use for your login success message";
        }
    }
}

これで、Login メソッドは単にLoginAttemptResult

public LoginAttemptResult AuthManager.Login(Credentials credentials)
{
    // do some stuff
}

そして、あなたの発信者はあなたのLoginAttemptResult.Message(またはあなたがそれをするために必要な他のこと)を呼び出すだけです:

var loginResult = AuthManager.Login(credentials);
var output = loginResult.Message;

同様に、別のメソッドをLoginAttemptResultベースの子型に関連付ける必要がある場合は、それを基本クラスで抽象メソッドとして定義し、子クラスに実装してから、まったく同じ方法で呼び出すことができます。

于 2013-05-03T15:47:40.987 に答える