Для этого конкретного примера « могу сбросить пароль » я бы рекомендовал использовать композицию поверх наследования (в данном случае наследование интерфейса / контракта). Потому что, делая это:
class Foo : IResetsPassword {
//...
}
Вы сразу указываете (во время компиляции), что ваш класс « может сбросить пароль ». Но если в вашем сценарии наличие возможности является условным и зависит от других вещей, то вы больше не можете указывать что-либо во время компиляции. Затем я предлагаю сделать это:
class Foo {
PasswordResetter passwordResetter;
}
Теперь во время выполнения вы можете проверить, myFoo.passwordResetter != null
перед выполнением этой операции. Если вы хотите отделить вещи еще больше (и планируете добавить гораздо больше возможностей), вы можете:
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...
ОБНОВИТЬ
После прочтения некоторых других ответов и комментариев от ОП я понял, что вопрос не в композиционном решении. Поэтому я думаю, что вопрос заключается в том, как лучше определить возможности объектов в целом в сценарии, подобном приведенному ниже:
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, ...);
}
Теперь я попытаюсь проанализировать все упомянутые варианты:
OP второй пример:
if (account.CanResetPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
Допустим, этот код получает некоторый базовый класс учетной записи (например: BaseAccount
в моем примере); это плохо, поскольку он вставляет логические значения в базовый класс, загрязняя его кодом, который вообще не имеет смысла находиться там.
OP первый пример:
if (account is IResetsPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
Чтобы ответить на вопрос, это более уместно, чем предыдущий вариант, но в зависимости от реализации он нарушит L принцип solid, и, вероятно, подобные проверки будут распространяться по коду и усложняют дальнейшее обслуживание.
Андер из CandiedOrange:
account.ResetPassword(authority);
Если этот ResetPassword
метод вставлен в BaseAccount
класс, он также загрязняет базовый класс неподходящим кодом, как во втором примере OP
Снеговик ответ:
AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());
Это хорошее решение, но оно считает, что возможности являются динамическими (и могут меняться со временем). Тем не менее, после прочтения нескольких комментариев от OP, я предполагаю, что речь здесь идет о полиморфизме и статически определенных классах (хотя пример учетных записей интуитивно указывает на динамический сценарий). AccountManager
Например : в этом примере проверкой разрешения будут запросы к БД; в вопросе ОП проверки - это попытки бросить объекты.
Еще одно предложение от меня:
Используйте шаблонный метод для высокоуровневого ветвления. Упомянутая иерархия классов сохраняется как есть; мы только создаем более подходящие обработчики для объектов, чтобы избежать приведений и неуместных свойств / методов, загрязняющих базовый класс.
//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(...);
}
}
Вы можете привязать операцию к соответствующему классу учетной записи, используя словарь / хеш-таблицу, или выполнять операции во время выполнения, используя методы расширения, использовать dynamic
ключевое слово, или, как последний вариант, использовать только одно приведение , чтобы передать объект учетной записи в операцию (в в этом случае количество приведений только в начале операции).