Для цього конкретного прикладу " можна скинути пароль ", я рекомендую використовувати склад над успадкуванням (у цьому випадку спадкування інтерфейсу / контракту). Тому що, роблячи це:
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 принцип твердості, і, ймовірно, перевірки, як це буде поширюватися через код і ускладнюють подальше обслуговування.
CanderedOrange's anser:
account.ResetPassword(authority);
Якщо цей ResetPassword
метод вставлено в BaseAccount
клас, він також забруднює базовий клас невідповідним кодом, як у другому прикладі OP
Відповідь Сніговика:
AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());
Це хороше рішення, але він вважає, що можливості динамічні (і можуть змінюватися з часом). Однак, прочитавши кілька коментарів з ОП, я думаю, що тут йдеться про поліморфізм та статично визначені класи (хоча приклад рахунків інтуїтивно вказує на динамічний сценарій). EG: у цьому 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
ключове слово або в якості останньої опції використовувати лише один склад, щоб передати об'єкт облікового запису в операцію (в у цьому випадку кількість накладок на початку операції лише одна).