Le titre de la question est probablement trop abstrait, alors laissez-moi fournir un exemple particulier de ce que j'ai à l'esprit:
Il existe un service Web qui encapsule un processus de changement de mots de passe pour les utilisateurs d'un système distribué. Le WebService accepte la connexion de l'utilisateur, son ancien mot de passe et un nouveau mot de passe. Sur la base de cette entrée, il peut renvoyer l'un des trois résultats suivants:
J'aimerais maintenant concevoir une classe, idéalement avec une seule méthode, pour encapsuler de travailler avec ce Webservice. Mon premier coup était ceci:
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.
}
Comme vous pouvez le constater, j'ai décidé d'utiliser des classes séparées pour les cas de retour n ° 2 et 3 - J'aurais pu utiliser une seule classe avec un booléen, mais cela ressemble à une odeur, la classe n'aurait aucun but clair. Avec deux classes distinctes, un utilisateur de ma catégorie PasswordManagementWebService
doit maintenant savoir quelles classes hériter de ChangePasswordResult
et de se lancer à une bonne propriété sur la propriété WasSuccessful
. Bien que je puisse maintenant avoir une belle classe au laser, j'ai fait une vie de mes utilisateurs plus difficiles qu'il ne devrait l'être.
En ce qui concerne le cas n ° 1, je viens de décider de jeter une exception. J'aurais aussi pu créer une exception séparée pour le cas n ° 2, et ne renvoyez que quelque chose de la méthode lorsque le mot de passe était a changé avec succès. Cependant, cela ne se sent pas bien - je ne pense pas qu'un nouveau mot de passe non valide est un état suffisamment exceptionnel pour justifier une exception.
Je ne suis pas très sûr, comment puis-je concevoir des choses existait plus de deux types de résultat non exceptionnels du Webservice. Probablement, je changerais un type de WasSuccessful
propriété de Boolean à une énorme et renommez-la à ResultType
, ajout d'une classe dédiée héritée de ChangePasswordResult
pour chaque possible ResultType
pour chaque possible ] _.
Enfin, à la (question actuelle : est cette approche de conception (c'est-à-dire avoir une classe abstraite et forcer les clients à se lancer dans un résultat correct basé sur une propriété) une bonne face lors de problèmes de ce genre? Si oui, existe-t-il un moyen de l'améliorer (peut-être avec une stratégie différente pour lancer des exceptions vs résultats de retour)? Si non, que recommanderiez-vous?
Je ne souscrit pas à l'école de pensée qui dit que "les exceptions ne devraient être que pour des cas exceptionnels!" Les gens ont peur des exceptions pour une raison quelconque. Si vous ne pouvez pas faire ce que vous avez dit, vous allez faire, lancez une exception - même si c'est un échec courant ou attendu.
J'aime ça pour quelques raisons. Il est impossible pour l'appelant d'ignorer (quelqu'un pouvait facilement oublier d'inspecter la valeur de retour d'une méthode indiquant l'échec par un code de retour.) C'est simple (aucune valeur d'espace réservée spéciale pour les résultats manquants ou les hiérarchies qui nécessitent la sous-cuisson.) C'est atomique (soit Mon changement a réussi ou tu me dis de me perdre; je ne suis pas de doute sur lequel c'est.) C'est granulaire (vous pouvez attacher autant d'informations que vous préférez à l'exception et de lancer des exceptions différentes pour différentes échecs.)
Donc, je concevrais votre méthode comme celle-ci:
public void ChangePassword(string password)
{
HttpResponse response = RequestPasswordChange();
if (AuthFailed(response))
throw new AuthorizationException();
if (PasswordWasNotChanged(response))
throw new InvalidNewPasswordException(
WasPasswordLongEnough(response),
DidPasswordContainNumbers(response)
);
}
"Succe-ou-jet" est une belle règle de pouce et des personnes sont l'habitude, peu importe combien ils font signe leurs bras. Après tout, Dictionary<TKey, TValue>
jette KeyNotFoundException
lorsque vous ne vous en parlez pas. Une utilisation non exceptionnelle d'exceptions.