Per questo esempio specifico di " può reimpostare una password ", consiglierei di utilizzare la composizione sull'ereditarietà (in questo caso, l'ereditarietà di un'interfaccia / contratto). Perché, facendo questo:
class Foo : IResetsPassword {
//...
}
Stai specificando immediatamente (al momento della compilazione) che la tua classe " può reimpostare una password ". Ma, se nel tuo scenario, la presenza della capacità è condizionata e dipende da altre cose, non puoi più specificare le cose al momento della compilazione. Quindi, suggerisco di fare questo:
class Foo {
PasswordResetter passwordResetter;
}
Ora, in fase di esecuzione, è possibile verificare se myFoo.passwordResetter != null
prima di eseguire questa operazione. Se vuoi separare ancora di più le cose (e prevedi di aggiungere molte più funzionalità), potresti:
class Foo {
//... foo stuff
}
class PasswordResetOperation {
bool Execute(Foo foo) { ... }
}
class SendMailOperation {
bool Execute(Foo foo) { ... }
}
//...and you follow this pattern for each new capability...
AGGIORNARE
Dopo aver letto alcune altre risposte e commenti dall'OP ho capito che la domanda non riguarda la soluzione compositiva. Quindi penso che la domanda sia su come identificare meglio le capacità degli oggetti in generale, in uno scenario come di seguito:
class BaseAccount {
//...
}
class GuestAccount : BaseAccount {
//...
}
class UserAccount : BaseAccount, IMyPasswordReset, IEditPosts {
//...
}
class AdminAccount : BaseAccount, IPasswordReset, IEditPosts, ISendMail {
//...
}
//Capabilities
interface IMyPasswordReset {
bool ResetPassword();
}
interface IPasswordReset {
bool ResetPassword(UserAccount userAcc);
}
interface IEditPosts {
bool EditPost(long postId, ...);
}
interface ISendMail {
bool SendMail(string from, string to, ...);
}
Ora proverò ad analizzare tutte le opzioni menzionate:
Secondo esempio OP:
if (account.CanResetPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
Supponiamo che questo codice stia ricevendo una classe di account di base (ad esempio: BaseAccount
nel mio esempio); questo è male poiché inserisce booleani nella classe base, inquinandolo con codice che non ha alcun senso essere lì.
Primo esempio OP:
if (account is IResetsPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
Per rispondere alla domanda, questo è più appropriato dell'opzione precedente, ma a seconda dell'implementazione infrangerà il principio L di solido, e probabilmente verifiche del genere verrebbero diffuse nel codice e renderebbe più difficile l'ulteriore manutenzione.
Risposta di CandiedOrange:
account.ResetPassword(authority);
Se questo ResetPassword
metodo viene inserito nella BaseAccount
classe, inquina anche la classe base con codice inappropriato, come nel secondo esempio di OP
Risposta del pupazzo di neve:
AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());
Questa è una buona soluzione, ma considera che le capacità sono dinamiche (e potrebbero cambiare nel tempo). Tuttavia, dopo aver letto diversi commenti di OP, credo che il discorso qui riguardi il polimorfismo e le classi definite staticamente (anche se l'esempio di account punta intuitivamente allo scenario dinamico). Ad esempio: in questo AccountManager
esempio il controllo per l'autorizzazione sarebbe una query a DB; nella domanda OP i controlli sono tentativi di lanciare gli oggetti.
Un altro suggerimento da parte mia:
Utilizzare il modello Metodo modello per ramificazioni di alto livello. La gerarchia di classi menzionata viene mantenuta così com'è; creiamo solo gestori più appropriati per gli oggetti, al fine di evitare cast e proprietà / metodi inappropriati che inquinano la classe base.
//Template method
class BaseAccountOperation {
BaseAccount account;
void Execute() {
//... some processing
TryResetPassword();
//... some processing
TrySendMail();
//... some processing
}
void TryResetPassword() {
Print("Not allowed to reset password with this account type!");
}
void TrySendMail() {
Print("Not allowed to reset password with this account type!");
}
}
class UserAccountOperation : BaseAccountOperation {
UserAccount userAccount;
void TryResetPassword() {
account.ResetPassword(...);
}
}
class AdminAccountOperation : BaseAccountOperation {
AdminAccount adminAccount;
override void TryResetPassword() {
account.ResetPassword(...);
}
void TrySendMail() {
account.SendMail(...);
}
}
È possibile associare l'operazione alla classe di account appropriata utilizzando un dizionario / hashtable oppure eseguire operazioni di runtime utilizzando i metodi di estensione, utilizzare la dynamic
parola chiave o come ultima opzione utilizzare solo un cast per passare l'oggetto account all'operazione (in in questo caso il numero di lanci è solo uno, all'inizio dell'operazione).