Para este ejemplo específico de " puede restablecer una contraseña ", recomendaría usar la composición sobre la herencia (en este caso, la herencia de una interfaz / contrato). Porque, al hacer esto:
class Foo : IResetsPassword {
//...
}
Inmediatamente está especificando (en tiempo de compilación) que su clase ' puede restablecer una contraseña '. Pero, si en su escenario, la presencia de la capacidad es condicional y depende de otras cosas, entonces ya no puede especificar las cosas en tiempo de compilación. Entonces, sugiero hacer esto:
class Foo {
PasswordResetter passwordResetter;
}
Ahora, en tiempo de ejecución, puede verificar si myFoo.passwordResetter != nullantes de realizar esta operación. Si desea desacoplar las cosas aún más (y planea agregar muchas más capacidades), podría:
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...
ACTUALIZAR
Después de leer algunas otras respuestas y comentarios de OP, entendí que la pregunta no es sobre la solución compositiva. Así que creo que la pregunta es sobre cómo identificar mejor las capacidades de los objetos en general, en un escenario como el siguiente:
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, ...);
}
Ahora, intentaré analizar todas las opciones mencionadas:
OP segundo ejemplo:
if (account.CanResetPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
Digamos que este código está recibiendo una clase de cuenta básica (por ejemplo: BaseAccounten mi ejemplo); Esto es malo ya que está insertando booleanos en la clase base, contaminándolo con código que no tiene ningún sentido estar allí.
OP primer ejemplo:
if (account is IResetsPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
Para responder a la pregunta, esto es más apropiado que la opción anterior, pero dependiendo de la implementación romperá el principio L de sólido, y probablemente las comprobaciones de este tipo se extenderían a través del código y dificultarían más el mantenimiento.
Respuesta de CandiedOrange:
account.ResetPassword(authority);
Si este ResetPasswordmétodo se inserta en la BaseAccountclase, también está contaminando la clase base con código inapropiado, como en el segundo ejemplo de OP
La respuesta de Snowman:
AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());
Esta es una buena solución, pero considera que las capacidades son dinámicas (y pueden cambiar con el tiempo). Sin embargo, después de leer varios comentarios de OP, supongo que la charla aquí es sobre el polimorfismo y las clases estáticamente definidas (aunque el ejemplo de cuentas apunta intuitivamente al escenario dinámico). EG: en este AccountManagerejemplo, la verificación de permiso sería consultas a DB; En la pregunta OP, las comprobaciones son intentos de lanzar los objetos.
Otra sugerencia mía:
Use el patrón Método de plantilla para ramificaciones de alto nivel. La jerarquía de clases mencionada se mantiene como está; solo creamos controladores más apropiados para los objetos, a fin de evitar conversiones y propiedades / métodos inapropiados que contaminan la clase 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(...);
}
}
Puede vincular la operación a la clase de cuenta adecuada mediante el uso de un diccionario / tabla hash, o realizar operaciones en tiempo de ejecución utilizando métodos de extensión, utilizar una dynamicpalabra clave o, como última opción, utilizar solo un reparto para pasar el objeto de la cuenta a la operación (en en este caso, el número de lanzamientos es solo uno, al comienzo de la operación).