The question title is probably too abstract, so let me provide a particular example of what I have in mind:
There is a webservice that encapsulates a process of changing passwords for users of a distributed system. The webservice accepts user's login, his old password and a new password. Based on this input, it can return one of the following three results:
- In case user was not found, or his old password does not match, it will simply return with HTTP 403 Forbidden.
- Otherwise, it takes a new password and makes sure that it conforms to a password policy (e.g. it is long enough, contains a proper mix of letters and numbers, etc.). If it does not, it will return an XML describing why the password does not conform to the policy.
- Otherwise, it will change the password and return an XML containing an expiration date of the new password.
Now, I'd like to design a class, ideally with a single method, to encapsulate working with this webservice. My first shot was this:
public class PasswordManagementWebService
{
public ChangePasswordResult ChangePassword(string login, string oldPassword, string newPassword)
{
ChangePasswordResult result;
// send input to websevice, it's not important how; the httpResponse
// will contain a response from webservice
var httpResponse;
if (HasAuthenticationFailed(httpResponse)
{
throw new AuthenticationException();
}
else if (WasPasswordSuccessfullyChanged(httpResponse))
{
result = new ChangePasswordSuccessfulResult(httpResponse);
}
else
{
result = new ChangePasswordUnsuccessfulResult(httpResponse);
}
return result;
}
}
public abstract class ChangePasswordResult
{
public abstract bool WasSuccessful { get; }
}
public abstract class ChangePasswordSuccessfulResult
{
public ChangePasswordSuccessfulResult(HttpResponse httpResponse)
{
// initialize the class from the httpResponse
}
public override bool WasSuccessful { get { return true; } }
public DateTime ExpirationDate { get; private set; }
}
public abstract class ChangePasswordUnsuccessfulResult
{
public ChangePasswordUnsuccessfulResult(HttpResponse httpResponse)
{
// initialize the class from the httpResponse
}
public override bool WasSuccessful { get { return false; } }
public bool WasPasswordLongEnough { get; private set; }
public bool DoesPasswordHaveToContainNumbers { get; private set; }
// ... etc.
}
As you can see, I've decided to use separate classes for return cases #2 and #3 - I could have used a single class with a boolean, but it feels like a smell, the class would have no clear purpose. With two separate classes, an user of my PasswordManagementWebService
class now has to know which classes inherit from ChangePasswordResult
and to cast to a correct one based on the WasSuccessful
property. While I now do have a nice, laser-focused classes, I made a life of my users more difficult than it should be.
As for the case #1, I've just decided to throw an exception. I could have created a separate exception for the case #2, too, and only return something from the method when the password was successfully changed. However, this doesn't feel right - I don't think that a new password being invalid is a state exceptional enough to warrant throwing an exception.
I am not very sure how would I design things were there more than two un-exceptional result types from the webservice. Probably, I would change a type of WasSuccessful
property from boolean to an enum and rename it to ResultType
, adding a dedicated class inherited from ChangePasswordResult
for each possible ResultType
.
Finally, to the actual question: Is this design approach (i.e. having one abstract class and forcing clients to cast to a correct result based on a property) a correct one when dealing with problems like this? If yes, is there a way to improve it (perhaps with a different strategy for when to throw exceptions vs. return results)? If no, what would you recommend?
ChangePassword
to accept twoAction
s, one accepting theChangePasswordSuccessfulResult
and the otherChangePasswordUnsuccessfulResult
. Is that correct?Either
as-is and returnEither<ChangePasswordSuccessfulResult, ChangePasswordUnsuccessfulResult>
. The user can callmatch
with two lambdas to take a decision based on the type of result without doing any casting. The only change I'd make to that implementation ofEither
would be to give it aprivate
constructor and makeLeft
andRight
private inner classes so no one can add new subclasses and break it.match
method.