W tym konkretnym przykładzie „ można zresetować hasło ” zalecam użycie kompozycji zamiast dziedziczenia (w tym przypadku dziedziczenie interfejsu / kontraktu). Ponieważ robiąc to:
class Foo : IResetsPassword {
//...
}
Natychmiast określasz (w czasie kompilacji), że twoja klasa „ może zresetować hasło ”. Ale jeśli w twoim scenariuszu obecność zdolności jest warunkowa i zależy od innych rzeczy, nie możesz już więcej określać rzeczy w czasie kompilacji. Następnie sugeruję zrobienie tego:
class Foo {
PasswordResetter passwordResetter;
}
Teraz w czasie wykonywania możesz sprawdzić, czy myFoo.passwordResetter != null
przed wykonaniem tej operacji. Jeśli chcesz oddzielić rzeczy jeszcze bardziej (i planujesz dodać o wiele więcej możliwości), możesz:
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...
AKTUALIZACJA
Po przeczytaniu kilku innych odpowiedzi i komentarzy z OP zrozumiałem, że pytanie nie dotyczy rozwiązania kompozycyjnego. Myślę więc, że pytanie dotyczy tego, jak lepiej zidentyfikować możliwości obiektów w ogóle, w scenariuszu takim jak poniżej:
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, ...);
}
Teraz spróbuję przeanalizować wszystkie wymienione opcje:
Drugi przykład PO:
if (account.CanResetPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
Powiedzmy, że ten kod otrzymuje podstawową klasę konta (np .: BaseAccount
w moim przykładzie); jest to złe, ponieważ wstawia booleany do klasy podstawowej, zanieczyszczając go kodem, który w ogóle nie ma sensu.
Pierwszy przykład PO:
if (account is IResetsPassword)
((IResetsPassword)account).ResetPassword();
else
Print("Not allowed to reset password with this account type!");
Aby odpowiedzieć na to pytanie, jest to bardziej odpowiednie niż poprzednia opcja, ale w zależności od implementacji złamie zasadę L bryły i prawdopodobnie takie kontrole zostaną rozłożone w kodzie i utrudnią dalszą konserwację.
Anser CandiedOrange:
account.ResetPassword(authority);
Jeśli ta ResetPassword
metoda jest wstawiona do BaseAccount
klasy, wówczas również zanieczyszcza klasę podstawową nieodpowiednim kodem, jak w drugim przykładzie OP
Odpowiedź bałwana:
AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());
To dobre rozwiązanie, ale uważa, że możliwości są dynamiczne (i mogą się zmieniać z czasem). Jednak po przeczytaniu kilku komentarzy z OP myślę, że tutaj mowa jest o polimorfizmie i klasach zdefiniowanych statycznie (chociaż przykład kont intuicyjnie wskazuje na scenariusz dynamiczny). EG: w tym AccountManager
przykładzie sprawdzeniem uprawnień byłyby zapytania do DB; w pytaniu PO kontrole są próbami rzucenia obiektów.
Kolejna sugestia ode mnie:
Użyj wzorca metody szablonu dla rozgałęzień wysokiego poziomu. Wspomniana hierarchia klas jest zachowana bez zmian; tworzymy tylko bardziej odpowiednie procedury obsługi obiektów, aby uniknąć rzutowania i nieodpowiednich właściwości / metod zanieczyszczających klasę podstawową.
//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(...);
}
}
Można powiązać operację z odpowiednią klasą konta, używając słownika / tablicy hashtable, lub wykonać operacje w czasie wykonywania przy użyciu metod rozszerzenia, użyć dynamic
słowa kluczowego lub jako ostatnią opcję użyć tylko jednej rzutowania , aby przekazać obiekt konta do operacji (w w tym przypadku liczba rzutów jest tylko jedna, na początku operacji).