Чи повинні бути визначені можливості об'єкта виключно за допомогою інтерфейсів, які він реалізує?


36

isОператор C # і оператор Java instanceofдозволяють вам розгалужувати інтерфейс (або, ще більш чітко, його базовий клас), реалізований екземпляром об'єкта.

Чи доцільно використовувати цю функцію для розгалуження високого рівня на основі можливостей, які надає інтерфейс?

Або базовий клас повинен надавати булеві змінні для надання інтерфейсу для опису можливостей, які має об'єкт?

Приклад:

if (account is IResetsPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

vs.

if (account.CanResetPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Чи є їхні підводні камені для використання інтерфейсу для ідентифікації можливостей?


Цей приклад був саме цим, прикладом. Мені цікаво більш загальне застосування.


10
Подивіться два ваші приклади коду. Який із них читає?
Роберт Харві

39
Лише моя думка, але я б пішов із першим лише тому, що це гарантує те, що ти можеш сміливо подати відповідний тип. Другий передбачає, що CanResetPasswordце буде правдою лише тоді, коли щось реалізується IResetsPassword. Тепер ви створюєте властивість визначати те, що система типів повинна контролювати (і в кінцевому підсумку буде, коли ви попередньо виконувати команду).
Becuzz


14
Назва вашого запитання та орган запитання задають різні речі. Відповісти на заголовок: В ідеалі, так. Щоб вирішити питання: Якщо ви виявите, що ви перевіряєте тип і вручаєте кестинг вручну, ви, мабуть, неправильно використовуєте систему типу. Чи можете ви сказати нам конкретніше, як ви опинилися в цій ситуації?
MetaFight

9
Це питання може здатися простим, але стає досить широким, коли ви починаєте замислюватися над цим. Питання, які виникають у мене: Ви очікуєте додати нову поведінку до існуючих облікових записів або створити типи облікових записів з різною поведінкою? Чи всі типи рахунків мають подібну поведінку чи великі відмінності між ними? Чи знає приклад код про всі типи облікових записів або просто базовий інтерфейс IAccount?
Ейфорія

Відповіді:


7

Зауважуючи, що найкраще уникати зриву, якщо це можливо, тоді перша форма є кращою з двох причин:

  1. ви дозволяєте системі типу працювати для вас. У першому прикладі, якщо isпожежі, то пониження, безумовно, спрацює. У другій формі потрібно вручну переконатися, що IResetsPasswordдля цього властивості повертаються лише об'єкти, які реалізують
  2. крихкий базовий клас. Вам потрібно додати властивість до базового класу / інтерфейсу для кожного інтерфейсу, який ви хочете додати. Це громіздко і схильно до помилок.

Це не означає, що ви не можете дещо покращити ці проблеми. Наприклад, у базового класу може бути набір опублікованих інтерфейсів, в які ви можете перевірити включення. Але ви дійсно просто вручну реалізуєте частину існуючої системи, яка є зайвою.

До речі, мої природні переваги - це склад над спадщиною, але здебільшого, що стосується держави. Зазвичай успадкування інтерфейсу не так вже й погано. Крім того, якщо ви виявите, що реалізуєте ієрархію типу бідолахи, вам краще використовувати ту, яка вже є, оскільки компілятор допоможе вам.


Я також дуже віддаю перевагу композиційному типу. +1 за те, що я бачив, що цей конкретний приклад не використовує його належним чином. Хоча я б сказав, що обчислювальна типізація (як традиційне успадкування) є більш обмеженою, коли можливості сильно різняться (на відміну від способів їх реалізації). Звідси підхід взаємодії, який вирішує іншу проблему, ніж композиційна типізація або стандартне успадкування.
TheCatWhisperer

ви можете спростити чек так: (account as IResettable)?.ResetPassword();абоvar resettable = account as IResettable; if(resettable != null) {resettable.ResetPassword()} else {....}
Берін Лорич

89

Чи повинні бути визначені можливості об'єкта виключно за допомогою інтерфейсів, які він реалізує?

Можливості об’єктів взагалі не слід ідентифікувати.

Від клієнта, який використовує об'єкт, не потрібно нічого знати про те, як він працює. Клієнт повинен знати лише те, що може підказати об'єкту робити. Після того, як йому сказано, те, що робить об'єкт, - це не проблема клієнтів.

Тож, ніж

if (account is IResetsPassword)
    ((IResetsPassword)account).ResetPassword();
else
    Print("Not allowed to reset password with this account type!");

або

if (account.CanResetPassword)
    ((IResetsPassword)account).ResetPassword();
else
    Print("Not allowed to reset password with this account type!");

розглянути

account.ResetPassword();

або

account.ResetPassword(authority);

бо accountвже знає, чи це спрацює. Навіщо це запитувати? Просто скажіть, що ви хочете, і нехай він робить все, що буде робити.

Уявіть, що це робиться таким чином, що клієнту не байдуже, працював він чи ні, тому що це щось інше. Завданням клієнтів було лише зробити спробу. Зараз це зроблено, і з ним є інші справи. Цей стиль має багато імен, але ім'я, яке мені найбільше подобається, - це сказати, не питати .

Дуже спокусливо, коли пишеш клієнту, думаючи, що ти повинен простежувати все, і тому тягнеш до себе все, що ти думаєш, що ти повинен знати. Коли ви це робите, ви перевертаєте предмети назовні. Цініть своє незнання. Відсуньте деталі і дозвольте предметам займатися ними. Чим менше ви знаєте, тим краще.


54
Поліморфізм працює лише тоді, коли я не знаю і не цікавлюся саме тим, з чим я говорю. Тож спосіб я поводжусь із цією ситуацією - це обережно уникати її.
candied_orange

7
Якщо клієнтові дійсно потрібно знати, чи спроба вдалася, метод може повернути булевий характер. if (!account.AttemptPasswordReset()) /* attempt failed */;Таким чином клієнт знає результат, але уникає деяких питань із логікою рішення у питанні. Якщо спроба асинхронна, ви передасте зворотний дзвінок.
Radiodef

5
@DavidZ Ми обидва розмовляємо англійською мовою. Тож я можу вам сказати, що цей коментар двічі підтвердити. Це означає, що ви здатні це зробити? Чи потрібно мені знати, чи зможете ви, перш ніж я можу сказати вам це зробити?
candied_orange

5
@QPaysTaxes для всіх, хто вам відомий, обробник помилок був переданий під accountчас його створення. У цей момент можуть з'являтися спливаючі вікна, журнали, роздруківки та звукові сповіщення. Завдання клієнта - сказати «зроби це зараз». Для цього не потрібно робити більше. Не потрібно робити все в одному місці.
candied_orange

6
@QPaysTaxes Це можна було б вирішити в іншому місці та різними способами, це зайве для цієї розмови, і лише каламутить води.
Tom.Bowen89

17

Фон

Спадкування - це потужний інструмент, який виконує мету в об'єктно-орієнтованому програмуванні. Однак це не вирішує кожну проблему елегантно: іноді інші рішення є кращими.

Якщо ви подумаєте про свої ранні заняття з інформатики (якщо припустити, що у вас є ступінь CS), ви можете згадати професора, який дає вам абзац, в якому йдеться про те, що клієнт хоче робити програмним забезпеченням. Ваше завдання - прочитати абзац, визначити дійових осіб та дії, а також ознайомитись із приблизним описом того, що таке заняття та методи. Там будуть якісь підводи, які виглядають як вони важливі, але ні. Існує цілком реальна можливість неправильного тлумачення вимог.

Це важливий навик, з яким помиляються навіть найдосвідченіші з нас: правильно визначати вимоги та перекладати їх на машинні мови.

Твоє запитання

Виходячи з того, що ви написали, я думаю, що ви можете нерозуміти загальний випадок факультативних дій, які можуть виконувати класи. Так, я знаю, що ваш код - лише приклад, і вас цікавить загальна справа. Однак це здається, що ви хочете знати, як впоратися з ситуацією, коли певні підтипи об'єкта можуть виконувати дію, але інші підтипи не можуть.

Тільки тому, що такий об'єкт, як, наприклад, accountтип рахунку, не означає, що він перекладається на тип мовою ОО. "Тип" людською мовою не завжди означає "клас". У контексті облікового запису "тип" може тісніше співвіднести з "набором дозволів". Ви хочете скористатися обліковим записом користувача, щоб виконати дію, але ця дія може бути, а може і не бути в змозі виконати цей обліковий запис. Замість того, щоб використовувати успадкування, я б використовував делегат або маркер безпеки.

Моє рішення

Розглянемо клас облікового запису, який має кілька необов’язкових дій, які він може виконувати. Замість визначення "може виконувати дію X" за допомогою спадкування, чому б не повернути обліковому запису об'єкт-делегат (скидання пароля, відправник форми тощо) або маркер доступу?

account.getPasswordResetter().doAction();
account.getFormSubmitter().doAction(view.getContents());

AccountManager.resetPassword(account, account.getAccessToken());

Користь останнього варіанту є те , що якщо я хочу використовувати свої облікові дані , щоб скинути хто - то чужий пароль?

AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());

Система не тільки гнучкіша, я не лише знімаю касти типу, але й дизайн виразніший . Я можу це прочитати і легко зрозуміти, що це робить і як це можна використовувати.


TL; DR: це означає, що проблема XY . Як правило, стикаючись з двома варіантами, які є неоптимальними, варто зробити крок назад і подумати "що я насправді намагаюся досягти тут? Чи слід насправді замислюватися над тим, як зробити клавіатуру менш потворною, чи слід шукати способи повністю видалити набір тексту? "


1
+1 для дизайну пахне, навіть якщо ви не використовували фразу.
Джаред Сміт

як щодо випадків, коли пароль скидання має абсолютно різні аргументи залежно від типу? ResetPassword (pswd) vs ResetPasswordToRandom ()
TheCatWhisperer

1
@TheCatWhisperer Перший приклад - "змінити пароль", що є різною дією. Другий приклад - це те, що я описую у цій відповіді.

Чому ні account.getPasswordResetter().resetPassword();.
AnoE

1
The benefit to the last option there is what if I want to use my account credentials to reset someone else's password?.. легко. Ви створюєте об’єкт домену облікового запису для іншого облікового запису і використовуєте його. Статичні класи є проблематичними для тестування одиниць (цей метод за визначенням потребує доступу до деякого сховища, тому тепер ви не можете легко одинично перевірити будь-який код, який більше стосується цього класу) і найкраще його уникати. Варіант повернення якогось іншого інтерфейсу часто є хорошою ідеєю, але насправді не вирішує основну проблему (ви просто перенесли проблему на інший інтерфейс).
Во

1

Білл Веннер каже, що ваш підхід абсолютно чудовий ; перейти до розділу під назвою "Коли використовувати instanceof". Ви переходите до конкретного типу / інтерфейсу, який реалізує лише таку поведінку, тож ви абсолютно маєте право на вибірковість щодо цього.

Однак якщо ви хочете скористатися іншим підходом, існує багато способів голити яко.

Існує підхід поліморфізму; Ви можете стверджувати, що всі облікові записи мають пароль, тому всі облікові записи повинні бути принаймні спроби скинути пароль. Це означає, що в базовому класі облікових записів у нас повинен бути resetPassword()метод, який застосовуються всі облікові записи, але по-своєму. Питання полягає в тому, як повинен поводитися цей метод, коли його немає.

Він може повернути нікчемність і непомітно заповнити, скидає він пароль чи ні, можливо, беручи на себе відповідальність за друк внутрішньо повідомлення, якщо він не скидає пароль. Не найкраща ідея.

Він може повернути булевий сигнал із зазначенням успішного скидання. Увімкнувши цей булевий, ми можемо пов’язати помилку скидання пароля.

Він може повернути рядок із зазначенням результату спроби скидання пароля. Рядок може дати докладнішу інформацію про те, чому скидання не вдалося і чи може бути виведений.

Він може повернути об'єкт ResetResult, який передає більше деталей і поєднує всі попередні елементи повернення.

Він може повернути нікчемність і замість цього викинути виняток, якщо ви спробуєте скинути обліковий запис, який не має такої можливості (не робіть цього, оскільки використання обліку виключень для нормального контролю потоку є поганою практикою з різних причин).

Наявність canResetPassword()методу узгодження може здатися не найгіршим у світі, оскільки, навпаки, це статична здатність, вбудована в клас під час написання. Це підказка, чому метод підхід є поганою ідеєю, оскільки це говорить про те, що здатність є динамічною і що canResetPassword()може змінитися, що також створює додаткову можливість, що вона може змінитися між проханням дозволу і здійсненням дзвінка. Як було сказано в іншому місці, скажіть, а не просити дозволу.

Склад над успадкуванням може бути варіантом: у вас може бути заключне passwordResetterполе (або еквівалентний геттер) та еквівалентний клас (и), які ви можете перевірити на нуль, перш ніж викликати його. Хоча це і нагадує прохання дозволу, остаточність може уникнути будь-якого висновку про динамічний характер.

Ви можете подумати про зовнішнє функціонування у своєму власному класі, який міг би врахувати облік як параметр і діяти на ньому (наприклад resetter.reset(account)), хоча це також часто є поганою практикою (поширена аналогія - це кравець, який заходить у ваш гаманець, щоб отримати готівку).

На мовах, які володіють можливістю, ви можете використовувати мікси або риси, однак ви опинитесь в тому положенні, де ви почали, звідки ви могли перевіряти наявність цих можливостей.


Не всі облікові записи можуть мати пароль (з точки зору цієї конкретної системи все одно). Наприклад, рахунок мій третій учасник ... google, facebook, ect. Не сказати, що це не велика відповідь!
TheCatWhisperer

0

Для цього конкретного прикладу " можна скинути пароль ", я рекомендую використовувати склад над успадкуванням (у цьому випадку спадкування інтерфейсу / контракту). Тому що, роблячи це:

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ключове слово або в якості останньої опції використовувати лише один склад, щоб передати об'єкт облікового запису в операцію (в у цьому випадку кількість накладок на початку операції лише одна).


1
Чи вдасться просто завжди реалізувати метод "Execute ()", але іноді з цим нічого не робити? Він повертає бул, тож я здогадуюсь, це означає, що зробив це чи ні. Це повертає його до клієнта: "Я намагався скинути пароль, але цього не сталося (з будь-якої причини), тому зараз я повинен ..."

4
Наявність методів "canX ()" і "doX ()" є антипаттерном: якщо інтерфейс відкриває метод "doX ()", то краще мати змогу робити X, навіть якщо виконання X означає відсутність (наприклад, нульовий шаблон об'єкта) ). Це не повинно ніколи бути покладено на користувачів інтерфейс , щоб брати участь в тимчасовій зв'язку (викликати метод А перед методом Б) тому , що занадто легко отримати неправильні і ламати речі.

1
Наявність інтерфейсів не має ніякого відношення до використання композиції над успадкуванням. Вони просто представляють наявний функціонал. Те, як ця функціональність вступає в дію, не залежить від використання інтерфейсу. Що ви тут зробили - це використання спеціальної інтерфейсу без жодних переваг.
Міямото Акіра

Цікаво: відповідь, що голосує вгорі, говорить в основному те, що говорить мій коментар вище. Деякі дні вам просто пощастить.

@nocomprende - так, я оновив свою відповідь відповідно до кількох коментарів. Дякую за відгуки :)
Emerson Cardoso

0

Жоден із запропонованих вами варіантів не є хорошим ОО. Якщо ви пишете, якщо висловлювання навколо типу об’єкта, ви, швидше за все, робите OO неправильно (є винятки, це не один.) Ось простий відповідь OO на ваше запитання (може бути неправдивим C #):

interface IAccount {
  bool CanResetPassword();

  void ResetPassword();

  // Other Account operations as needed
}

public class Resetable : IAccount {
  public bool CanResetPassword() {
    return true;
  }

  public void ResetPassword() {
    /* RESET PASSWORD */
  }
}

public class NotResetable : IAccount {
  public bool CanResetPassword() {
    return false;
  }

  public void ResetPassword() {
    Print("Not allowed to reset password with this account type!");}
  }

Я змінив цей приклад, щоб відповідати тому, що робив початковий код. На підставі деяких коментарів, здається, люди зациклюються на тому, чи є це "правильним" конкретним кодом. Це не сенс цього прикладу. Вся перевантаження поліморфних по суті полягає в умовному виконанні різних реалізацій логіки на основі типу об'єкта. Те, що ви робите в обох прикладах, - це заклинювання рукою, що дає ваша мова як особливість. Коротше кажучи, ви можете позбутися від підтипів і помістити можливість скидання як булева властивість типу Account (ігнорування інших особливостей підтипів.)

Без більш широкого погляду на дизайн неможливо сказати, чи це хороше рішення для вашої конкретної системи. Це просто, і якщо він працює для того, що ви робите, вам, ймовірно, більше ніколи не доведеться багато про це думати, якщо хтось не вдасться перевірити CanResetPassword () перед викликом ResetPassword (). Ви також можете повернути булевий або відмовитись безшумно (не рекомендується). Це дійсно залежить від специфіки дизайну.


Не всі облікові записи можна скинути. Також, чи може обліковий запис мати більше функціональних можливостей, ніж перезавантажувати?
TheCatWhisperer

2
"Не всі облікові записи можна скинути." Не впевнений, чи це було написано перед редагуванням. Другий клас є NotResetable. "Можливо, обліковий запис матиме більше функціональних можливостей, ніж перезавантажуваний". Я б очікував, але це, мабуть, не важливо для цього питання.
JimmyJames

1
Це рішення іде в правильному напрямку, я думаю, оскільки це приємне рішення всередині ОО. Можливо, змінив би ім'я інтерфейсу на IPasswordReseter (або щось для цього), щоб розділити інтерфейси. IAccount може бути оголошено таким, що підтримує безліч інших інтерфейсів. Це дасть вам можливість мати класи з реалізацією, яка потім складається та використовується делегуванням об’єктів домену. @JimmyJames, мінорна нітрипка, на C # обом класам потрібно вказати, що вони реалізують IAccount. Інакше код виглядає дещо дивно ;-)
Міямото Акіра

1
Також перевірте коментар Snowman в одній з інших відповідей про CanX () та DoX (). Не завжди анти-шаблон, оскільки він використовує (наприклад, про поведінку інтерфейсу користувача)
Міямото Акіра

1
Ця відповідь починається добре: це погано ООП. На жаль, ваше рішення так само погано, оскільки воно також підриває тип системи, але кидає виняток. Гарне рішення виглядає інакше: не майте безпроблемних методів для типу. Рамка .NET фактично використовує ваш підхід для деяких типів, але це було однозначно помилкою.
Конрад Рудольф

0

Відповідь

Моя злегка впевнена відповідь на ваше запитання:

Можливості об’єкта слід визначати через себе, а не через реалізацію інтерфейсу. Статично, сильно набрана мова обмежить цю можливість, хоча (за задумом).

Додаток:

Розгалуження на тип об'єкта - це зло.

Рамбінг

Я бачу, що ви не додали c#тег, але запитуєте в загальному object-orientedконтексті.

Те, що ви описуєте, - це технічність статичних / сильних набраних мов, а не специфічна для "OO" взагалі. Ваша проблема випливає, серед іншого, з того, що ви не можете викликати методи на цих мовах, не маючи достатньо вузького типу під час компіляції (або через змінне визначення, параметр методу / визначення значення повернення, або явний перелік). Раніше я робив обидва ваші варіанти на таких мовах, і обидва мені здаються некрасивими.

У динамічно набраних мовах OO ця проблема не виникає через концепцію, яку називають "типом качки". Коротше кажучи, це означає, що ви в основному не декларуєте тип об’єкта ніде (крім випадків його створення). Ви надсилаєте повідомлення об’єктам, і об’єкти обробляють ці повідомлення. Вам не байдуже, чи дійсно об’єкт має метод цього імені. Деякі мови навіть мають загальний, загальнодоступний method_missingметод, який робить це ще більш гнучким.

Отже, такою мовою (наприклад, Ruby) ваш код стає:

account.reset_password

Період.

Воно accountабо скине пароль, викине виняток "відмовлено", або викине "не знаю, як обробити" reset_password ".

Якщо вам потрібно явним чином з будь-якої причини відгалуження, ви все одно зробите це на можливості, а не на класі:

account.reset_password  if account.can? :reset_password

(Де can?лише метод, який перевіряє, чи може об'єкт виконати якийсь метод, не викликаючи його.)

Зауважте, що хоча мої особисті переваги зараз є динамічно набраними мовами, це лише особисті переваги. У статично набраних мовах є і те, що відбувається для них, тому, будь ласка, не сприймайте цю розгубленість як базу проти статично набраних мов ...


Чудово, що тут є ваша перспектива! Ми з боку java / C # прагнемо забути, що існує інша гілка OOP. Коли я кажу своїм колегам, JS (за всіма своїми проблемами) багато в чому більше OO, ніж C #, вони сміються з мене!
TheCatWhisperer

Мені подобається C #, і я думаю, що це гарна загальна мова, але це моя особиста думка, що з точки зору ОО - це досить бідно. Як у особливостях, так і в практиці він, як правило, заохочує процедурне програмування.
TheCatWhisperer

2
Можливо, тестування на існування методу на мові, набраній качкою, є такою ж, як тестування на реалізацію інтерфейсу в статично типовому вигляді: ви виконуєте перевірку часу на наявність об'єкта певної здатності. Кожне оголошення методу фактично є власним інтерфейсом, ідентифікованим лише назвою методу, і його існування не може бути використане для отримання іншої інформації про об'єкт.
IMSoP

0

Здається, що ваші варіанти походять від різних мотиваційних сил. Перший, здається, хак, що використовується через погане моделювання об'єктів. Це продемонструвало, що ваші абстракції неправильні, оскільки вони порушують поліморфізм. Ймовірно, це порушує принцип відкритого закритого типу , що призводить до більш жорсткої зв'язку.

Ваш другий варіант може бути чужим з точки зору OOP, але кастинг типу мене бентежить. Якщо ваш обліковий запис дозволяє скинути його пароль, навіщо тоді вам потрібна набір тексту? Отже, якщо припустити, що ваш CanResetPasswordпункт є деякою основною діловою вимогою, яка є частиною абстракції облікового запису, ваш другий варіант - це нормально.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.