Per questo specifico esempio di " puoi reimpostare una password ", ti consigliamo di utilizzare la composizione sull'ereditarietà (in questo caso, l'ereditarietà di un'interfaccia / contratto) . Perché, in questo modo:
class Foo : IResetsPassword {
//...
}
Stai specificando immediatamente (in fase di compilazione) che la classe ' può reimpostare una password '. Ma, se nel tuo scenario, la presenza della capacità è condizionata e dipende da altre cose, allora non puoi più specificare le cose in fase di compilazione. Quindi, ti 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 disaccoppiare le cose ancora di più (e hai intenzione 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...
UPDATE
Dopo aver letto alcune altre risposte e commenti da OP ho capito che la domanda non riguarda la soluzione compositiva. Quindi penso che la domanda riguardi come identificare meglio le capacità degli oggetti in generale, in uno scenario come il seguente:
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 citate:
OP secondo esempio:
if (account.CanResetPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
Diciamo che questo codice sta ricevendo alcune classi di account di base (es: BaseAccount
nel mio esempio); questo non va bene dato che sta inserendo i booleani nella classe base, inquinandolo con un codice che non ha assolutamente senso essere lì.
Primo esempio di 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 rispetto all'opzione precedente, ma a seconda dell'implementazione interromperà il principio di Solid, e probabilmente controlli come questo sarebbero diffusi attraverso il codice e renderebbe più difficile l'ulteriore manutenzione.
Anomalia di CandiedOrange:
account.ResetPassword(authority);
Se questo metodo ResetPassword
è inserito in BaseAccount
class, allora sta anche inquinando 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 ritiene che le capacità siano dinamiche (e potrebbero cambiare nel tempo). Tuttavia, dopo aver letto diversi commenti da OP, credo che il discorso qui riguardi il polimorfismo e le classi definite staticamente (anche se l'esempio degli account indica intuitivamente lo scenario dinamico). EG: in questo esempio di AccountManager
il controllo per il permesso sarebbe interrogato su DB; nella domanda OP i controlli sono tentativi di lancio degli oggetti.
Un altro suggerimento da parte mia:
Usa un modello di metodo Template per la ramificazione di alto livello. La gerarchia di classe menzionata viene mantenuta così com'è; creiamo solo gestori più appropriati per gli oggetti, al fine di evitare calchi e proprietà / metodi inappropriati per la polarizzazione della 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 o eseguire operazioni di runtime utilizzando i metodi di estensione, utilizzare la parola chiave dynamic
oppure utilizzare l'ultima opzione solo un cast per passare l'oggetto account all'operazione (in questo caso il numero di cast è solo uno, all'inizio dell'operazione).