从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)
以下是此方法的一组有效输出值:
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) {
...
}