Para este exemplo específico de " pode redefinir uma senha ", eu recomendo o uso de composição sobre herança (neste caso, herança de uma interface / contrato). Porque, ao fazer isso:
class Foo : IResetsPassword {
//...
}
Você está especificando imediatamente (em tempo de compilação) que sua classe ' pode redefinir uma senha '. Mas, se no seu cenário, a presença do recurso for condicional e depender de outras coisas, você não poderá mais especificar as coisas no tempo de compilação. Então, sugiro fazer o seguinte:
class Foo {
PasswordResetter passwordResetter;
}
Agora, em tempo de execução, você pode verificar se myFoo.passwordResetter != nullantes de executar esta operação. Se você deseja desacoplar ainda mais as coisas (e planeja adicionar muito mais recursos), você pode:
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...
ATUALIZAR
Depois de ler outras respostas e comentários do OP, entendi que a pergunta não é sobre a solução composicional. Então, acho que a pergunta é sobre como identificar melhor as capacidades dos objetos em geral, em um cenário como o abaixo:
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, ...);
}
Agora, tentarei analisar todas as opções mencionadas:
Segundo exemplo do OP:
if (account.CanResetPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
Digamos que este código esteja recebendo alguma classe de conta base (por exemplo: BaseAccountno meu exemplo); isso é ruim, pois está inserindo booleanos na classe base, poluindo-o com código que não faz nenhum sentido estar lá.
Primeiro exemplo do OP:
if (account is IResetsPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
Para responder à pergunta, isso é mais apropriado do que a opção anterior, mas, dependendo da implementação, ele quebrará o princípio L de sólido, e provavelmente verificações como essa seriam espalhadas pelo código e dificultariam a manutenção.
O que outras pessoas estão dizendo
account.ResetPassword(authority);
Se esse ResetPasswordmétodo for inserido na BaseAccountclasse, também estará poluindo a classe base com código inadequado, como no segundo exemplo do OP
Resposta do boneco de neve:
AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());
Essa é uma boa solução, mas considera que os recursos são dinâmicos (e podem mudar com o tempo). No entanto, depois de ler vários comentários do OP, acho que a conversa aqui é sobre polimorfismo e classes definidas estaticamente (embora o exemplo de contas aponte intuitivamente para o cenário dinâmico). EG: neste AccountManagerexemplo, a verificação de permissão seria consultas ao DB; na pergunta do OP, as verificações são tentativas de converter os objetos.
Outra sugestão minha:
Use o padrão Method Method para ramificações de alto nível. A hierarquia de classes mencionada é mantida como é; criamos apenas manipuladores mais apropriados para os objetos, a fim de evitar projeções e propriedades / métodos inadequados que poluem a 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(...);
}
}
Você pode vincular a operação à classe de conta apropriada usando um dicionário / hashtable ou executar operações em tempo de execução usando métodos de extensão, usar dynamicpalavra-chave ou, como última opção, usar apenas uma conversão para passar o objeto de conta para a operação (em neste caso, o número de lançamentos é apenas um, no início da operação).