从C#方法返回不同类型

从C#方法返回不同类型,c#,oop,C#,Oop,我有一个方法: public ??? AuthManager.Login(Credentials credentials) 以下是此方法的一组有效输出值: 成功(+accountId) 失败:AccountLockedOut 失败:UsernameNotFound 失败:InvalidPassword(+失败的尝试计数) 根据返回类型,会向用户显示不同的视图(是的,AccountLockedOut的视图不同于InvalidPassword) 我可以说: public class LoginAt

我有一个方法:

public ??? AuthManager.Login(Credentials credentials)
以下是此方法的一组有效输出值:

  • 成功(+accountId)
  • 失败:AccountLockedOut
  • 失败:UsernameNotFound
  • 失败: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
    }
    
    我不喜欢这样,正在寻找更好的解决方案。首先,这导致一个部分初始化的对象,两个违反接口隔离原则,三个违反SRP

    更新:抛出异常也不是一个优雅的解决方案,因为我认为
    InvalidPassword
    不是一个异常。数据库连接失败是一个例外。Null参数是一个例外<代码>无效密码是有效的预期响应

    我认为更好的解决方案是创建类的层次结构:

    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#的

    有没有更好的方法对此进行建模?我已经有了几种有效的解决方案-现在我想要一个优雅的有效的解决方案。

    您可以退货

    公共元组AuthManager.Login(凭据){
    //在这里做你的事
    返回新元组(valueOfT1,valueOfT2);
    }
    
    如果结果类差异很大,并且每个类都需要单独的类,我认为您的解决方案是可以的。但我不确定。为每个结果尝试此课程:

    /// <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;
        }
    }
    
    //
    ///不可变,由服务器创建
    /// 
    类登录结果
    {
    /// 
    ///失败时为空
    /// 
    公共int?Id{get;private set;}
    /// 
    ///如果成功,则为空
    /// 
    公共字符串失败原因{get;private set;}
    /// 
    ///始终>=1
    /// 
    public int AttemptNumber{get;private set;}
    公共登录结果(int id,int尝试编号)
    {
    Id=Id;
    AttemptNumber=AttemptNumber;
    }
    public LoginResult(字符串原因,int attemptNumber)
    {
    失败原因=原因;
    AttemptNumber=AttemptNumber;
    }
    }
    

    我可以想象,您的身份验证逻辑可能非常复杂,
    Id
    FailReason
    AttemptNumber
    不仅仅是您需要的属性。在这种情况下,您需要为我们提供更具体的示例,如果必要,我们将尝试构建符合您逻辑的抽象。在这种特殊情况下-没有抽象的意义。

    如果您将
    loginatetresult
    类抽象化,那么您可以添加一个抽象属性
    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";
            }
        }
    }
    
        public interface ILoginContext
        {
            //Expose whatever properties you need to describe the login process, such as parameters and results
    
            void Login(Credentials credentials);
        }
    
        public sealed class AuthManager
        {
            public ILoginContext GetLoginContext()
            {
                return new LoginContext(this);
            }
    
            private sealed class LoginContext : ILoginContext
            {
                public LoginContext(AuthManager manager)
                {
                    //We pass in manager so that the context can use whatever it needs from the manager to do its job    
                }
                //...
            }
        }
    
    这样,您的登录方法就可以返回一个
    loginatestresult

    public LoginAttemptResult AuthManager.Login(Credentials credentials)
    {
        // do some stuff
    }
    
    然后,您的调用者只需调用您的
    loginatestresult.Message
    (或您需要它执行的任何其他操作):


    类似地,如果您需要基于子类型将某个其他方法与
    loginatestresult
    关联,您可以将其定义为基类中的抽象方法,并在子类中实现,另一种可能的方法是创建一个类来封装登录过程及其结果,如下所示:

    public class LoginSuccess : LoginAttemptResult
    {
        public override string Message 
        { 
            get
            {
                return "whatever you use for your login success message";
            }
        }
    }
    
        public interface ILoginContext
        {
            //Expose whatever properties you need to describe the login process, such as parameters and results
    
            void Login(Credentials credentials);
        }
    
        public sealed class AuthManager
        {
            public ILoginContext GetLoginContext()
            {
                return new LoginContext(this);
            }
    
            private sealed class LoginContext : ILoginContext
            {
                public LoginContext(AuthManager manager)
                {
                    //We pass in manager so that the context can use whatever it needs from the manager to do its job    
                }
                //...
            }
        }
    
    基本上,这个设计意味着登录已经成为一个足够复杂的操作,单个方法不再是合适的封装。我们需要返回一个复杂的结果,并且可能希望包含更复杂的参数。因为类现在负责行为,而不仅仅是表示数据,所以不太可能被视为违反SRP;它只是一个有点复杂的类,用于一个有点复杂的操作


    请注意,如果LoginContext具有自然的事务作用域,您还可以使其实现IDisposable。

    摘要:不返回值并对其进行解码,而是为Login提供一组处理程序,这样,
    Login
    将调用适当的回调(想想jQuery的
    ajax{success:…,error:}

    Login
    方法的使用者必须使用switch语句对响应进行解码。重构此代码以消除“switch”语句并消除自定义类型爆炸的一种方法是,不要要求登录方法返回一个有区别的联合,我们给登录方法一组thunk,每个响应一个thunk

    (微妙的一点)从技术上讲,我们并没有摆脱自定义类,我们只是用泛型替换它们,也就是说,我们用
    Action
    替换了
    InvalidPasswordFailedLogin{int failedAttemptCount}
    。这种方法还提供了一些有趣的机会,例如,可以更自然地异步处理登录。另一方面,测试变得更加模糊

    public class LoginResultHandlers {
        public Action<int> InvalidPassword { get; set; }
        public Action AccountLockedout { get; set; }
        public Action<AccountId> Success { get; set; }
    }
    
    public class AccountId {}
    
    public class AuthManager {
        public void Login(string username, string password, LoginResultHandlers handler) {
            // if (...
                handler.Success(new AccountId());
            // if (...
                handler.AccountLockedout();
            // if (...
                handler.InvalidPassword(2);
        }
    }
    
    public class Application {
        public void Login() {
            var loginResultHandlers = new LoginResultHandlers {
                    AccountLockedout = ShowLockedoutView,
                    InvalidPassword = (failedAttemptCount) => ShowInvalidPassword(failedAttemptCount),
                    Success = (accountId) => RedirectToDashboard(accountId)
            };
            new AuthManager().Login("bob", "password", loginResultHandlers);
        }
    
        private void RedirectToDashboard(AccountId accountId) {
            throw new NotImplementedException();
        }
    
        private void ShowInvalidPassword(int failedAttemptCount) {
            throw new NotImplementedException();
        }
    
        private void ShowLockedoutView() {
            throw new NotImplementedException();
        }
    }
    
    public类loginResultHandler{
    公共操作无效密码{get;set;}
    公共操作AccountLockedout{get;set;}
    公共操作成功{get;set;}
    }
    公共类AccountId{}
    公共类AuthManager{
    公共无效登录(字符串用户名、字符串密码、LoginResultHandler){
    //如果。。。
    handler.Success(新AccountId());
    //如果。。。
    handler.AccountLockedout();
    //如果。。。
    处理者。无效密码(2);
    }
    }
    公共类应用程序{
    公共无效登录(){
    var loginResultHandlers=新的loginResultHandlers{
    AccountLockedout=ShowLockedoutView,
    InvalidPassword=(failedAttemptCount)=>ShowInvalidPassword(failedAttemptCount),
    成功=(帐户ID)=>Redi
    
    // 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); 
    }
    
    Could not log you on at this time. Check that your username and/or password are correct, or please try again later.
    
    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; }
    }
    
    public static implicit operator bool(AuthResultBase result) {
        return result != null;
    }
    
    var result = authService.Auth(credentials);
    if (result.Success) {
        ...
    }
    
    if (result.Success != null) {
        ...
    }