Il titolo della domanda è probabilmente troppo astratto, quindi permettimi di fornire un esempio particolare di ciò che ho in mente:
Esiste un servizio web che incapsula un processo di modifica delle password per gli utenti di un sistema distribuito. Il webservice accetta l'accesso dell'utente, la sua vecchia password e una nuova password. Sulla base di questo input, può restituire uno dei seguenti tre risultati:
- Nel caso in cui l'utente non sia stato trovato o la sua vecchia password non corrisponda, verrà semplicemente restituita con HTTP 403 Proibito.
- Altrimenti, richiede una nuova password e si assicura che sia conforme a una politica di password (ad es. è abbastanza lunga, contiene un corretto mix di lettere e numeri, ecc.). In caso contrario, restituirà un XML che descrive il motivo per cui la password non è conforme al criterio.
- Altrimenti, cambierà la password e restituirà un XML contenente una data di scadenza della nuova password.
Ora mi piacerebbe progettare una classe, idealmente con un singolo metodo, per incapsulare il funzionamento con questo servizio web. Il mio primo colpo è stato questo:
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.
}
Come puoi vedere, ho deciso di utilizzare classi separate per i casi di restituzione n. 2 e n. 3 - Avrei potuto usare una singola classe con un booleano, ma sembra un odore, la classe non avrebbe uno scopo chiaro . Con due classi separate, un utente della mia classe PasswordManagementWebService
ora deve sapere quali classi ereditano da ChangePasswordResult
e per eseguire il cast su una classe corretta in base alla proprietà WasSuccessful
. Mentre ora ho delle belle lezioni focalizzate sul laser, ho reso la vita dei miei utenti più difficile di quanto dovrebbe essere.
Per quanto riguarda il caso n. 1, ho appena deciso di lanciare un'eccezione. Avrei potuto creare anche un'eccezione separata per il caso numero 2 e restituire solo qualcosa dal metodo quando la password è stata modificata con successo. Tuttavia, questo non mi sembra giusto - non credo che una nuova password non valida sia uno stato abbastanza eccezionale da giustificare il lancio di un'eccezione.
Non sono molto sicuro di come progetterei le cose se ci fossero più di due tipi di risultati non eccezionali dal webservice. Probabilmente cambierei un tipo di proprietà WasSuccessful
da booleano a enum e lo rinominerei in ResultType
, aggiungendo una classe dedicata ereditata da ChangePasswordResult
per ogni possibile ResultType
.
Infine, alla domanda attuale: questo approccio alla progettazione (ovvero che ha una classe astratta e obbliga i client a trasmettere a un risultato corretto basato su una proprietà) è corretto quando si affrontano problemi come questo ? Se sì, c'è un modo per migliorarlo (magari con una strategia diversa per quando generare eccezioni rispetto ai risultati di ritorno)? Se no, cosa consiglieresti?