Должны ли возможности объекта идентифицироваться исключительно интерфейсами, которые он реализует?

36

isОператор C # и Javainstanceof позволяют вам ветвиться на интерфейсе (или, что еще более неуклюже, на его базовом классе), реализованном экземпляром объекта.

Уместно ли использовать эту функцию для ветвления высокого уровня на основе возможностей, предоставляемых интерфейсом?

Или базовый класс должен предоставлять логические переменные, чтобы обеспечить интерфейс для описания возможностей, которые имеет объект?

Пример:

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!");

Есть ли у них подводные камни для использования реализации интерфейса для идентификации возможностей?


Этот пример был просто примером. Я задаюсь вопросом о более общем применении.

TheCatWhisperer
источник
10
Посмотрите на ваши два примера кода. Какой из них более читабелен?
Роберт Харви
39
Просто мое мнение, но я бы пошел с первым только потому, что он гарантирует, что вы можете безопасно привести к подходящему типу. Второй предполагает, что CanResetPasswordэто будет верно только тогда, когда что-то реализуется IResetsPassword. Теперь вы заставляете свойство определять то, что система типов должна контролировать (и, в конечном счете, будет, когда вы преформуете приведение).
Becuzz
10
@nocomprende: xkcd.com/927
Роберт Харви
14
Ваше название вопроса и тело вопроса задают разные вещи. Ответить на заголовок: в идеале да. Чтобы обратиться к телу: Если вы проверяете тип и выполняете приведение вручную, вы, вероятно, неправильно используете свою систему типов. Можете ли вы рассказать нам более конкретно, как вы оказались в этой ситуации?
MetaFight
9
Этот вопрос может показаться простым, но он становится достаточно широким, когда вы начинаете думать об этом. Вопросы, которые приходят мне в голову: ожидаете ли вы добавить новое поведение в существующие учетные записи или создать типы учетных записей с другим поведением? Все ли типы учетных записей имеют одинаковое поведение или между ними есть большие различия? Код примера знает обо всех типах учетных записей или только базовом интерфейсе IAccount?
Euphoric

Ответы:

7

С учетом предостережения о том, что лучше избегать снижения рейтинга, если это возможно, тогда первая форма предпочтительнее по двум причинам:

  1. вы позволяете системе типов работать на вас. В первом примере, если isпожары, то удручающий будет определенно работать. Во второй форме вам нужно вручную убедиться, что только объекты, которые реализуют, IResetsPasswordвозвращают true для этого свойства.
  2. хрупкий базовый класс. Вам нужно добавить свойство в базовый класс / интерфейс для каждого интерфейса, который вы хотите добавить. Это громоздко и подвержено ошибкам.

Это не значит, что вы не можете улучшить эти проблемы. Например, у базового класса может быть набор опубликованных интерфейсов, в которых вы можете проверить включение. Но на самом деле вы просто вручную реализуете часть существующей системы типов, которая является избыточной.

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

Alex
источник
Я также очень предпочитаю композиционный набор текста. +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 уже знает, будет ли это работать. Зачем это спрашивать? Просто скажите ему, что вы хотите, и позвольте ему делать то, что он собирается делать.

Представьте, что это сделано таким образом, что клиенту все равно, сработало ли это или нет, потому что это что-то еще. Работа клиентов состояла только в том, чтобы сделать попытку. Это сделано сейчас, и есть другие вещи, с которыми нужно иметь дело. У этого стиля много имен, но имя, которое мне нравится больше всего, это сказать, не спрашивайте .

Когда вы пишете клиенту, очень заманчиво думать, что вы должны следить за всем и поэтому притягивайте к себе все, что, по вашему мнению, вам нужно знать. Когда вы делаете это, вы выворачиваете предметы наизнанку. Цени свое невежество. Отодвиньте детали и позвольте объектам справиться с ними. Чем меньше ты знаешь, тем лучше.

candied_orange
источник
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факт, что такой объект, как объект, имеет тип учетной записи, не означает, что он переводится в тип на языке OO. «Тип» в человеческом языке не всегда означает «класс». В контексте учетной записи «тип» может более тесно соотноситься с «набором разрешений». Вы хотите использовать учетную запись пользователя для выполнения действия, но это действие может или не может быть выполнено этой учетной записью. Вместо использования наследования я бы использовал делегат или токен безопасности.

Мое решение

Рассмотрим класс учетной записи, который имеет несколько дополнительных действий, которые он может выполнять. Вместо определения «может выполнить действие 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) против 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?.. легко. Вы создаете объект домена учетной записи для другой учетной записи и используете его. Статические классы проблематичны для модульного тестирования (этот метод по определению будет нуждаться в доступе к некоторому хранилищу, поэтому теперь вы не можете легко выполнить модульное тестирование любого кода, который больше касается этого класса), и его лучше избегать. Возможность возврата какого-либо другого интерфейса часто является хорошей идеей, но на самом деле она не решает основную проблему (вы просто перенесли проблему на другой интерфейс).
Voo
1

Билл Веннер говорит, что ваш подход абсолютно хорош ; перейдите к разделу «Когда использовать instanceof». Вы понижаете до определенного типа / интерфейса, который реализует только это конкретное поведение, поэтому вы абсолютно правы в этом вопросе.

Однако, если вы хотите использовать другой подход, есть много способов побрить яка.

Есть подход полиморфизма; Вы можете утверждать, что все учетные записи имеют пароль, поэтому все учетные записи должны, по крайней мере, иметь возможность попытаться сбросить пароль. Это означает, что в базовом классе учетных записей у нас должен быть resetPassword()метод, который реализуют все учетные записи, но по-своему. Вопрос в том, как должен вести себя этот метод, когда возможности отсутствуют.

Он может вернуть пустоту и выполнить молча, независимо от того, сбросил ли он пароль или нет, возможно, взяв на себя ответственность за внутреннюю распечатку сообщения, если он не сбросил пароль. Не самая лучшая идея

Он может вернуть логическое значение, указывающее, был ли сброс успешным. При включении этого логического значения мы можем связать сбой сброса пароля.

Он может вернуть строку, указывающую результат попытки сброса пароля. Строка может дать более подробную информацию о том, почему сброс не удалось, и может быть выведен.

Он может вернуть объект ResetResult, который передает больше деталей и объединяет все предыдущие возвращаемые элементы.

Он может вернуть пустоту и вместо этого выдать исключение, если вы попытаетесь сбросить учетную запись, у которой нет такой возможности (не делайте этого, так как использование обработки исключений для нормального управления потоком является плохой практикой по ряду причин).

Наличие canResetPassword()метода сопоставления может показаться не самым худшим в мире, поскольку по идее это статическая возможность, встроенная в класс при его написании. Это ключ к пониманию того, почему методный подход является плохой идеей, так как он предполагает, что возможность является динамической, и это canResetPassword()может измениться, что также создает дополнительную возможность, что она может измениться между запросом разрешения и выполнением вызова. Как уже упоминалось, говорите, а не спрашивайте разрешения.

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

Вы можете подумать о том, чтобы перенести функциональность в свой собственный класс, который мог бы использовать учетную запись в качестве параметра и воздействовать на нее (например, 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 принцип 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ключевое слово, или, как последний вариант, использовать только одно приведение , чтобы передать объект учетной записи в операцию (в в этом случае количество приведений только в начале операции).

Эмерсон Кардосо
источник
1
Будет ли работать просто всегда реализовывать метод «Execute ()», но иногда он ничего не делает? Это возвращает bool, так что я думаю, это означает, что он сделал это или нет. Это возвращает его клиенту: «Я пытался сбросить пароль, но этого не произошло (по какой-либо причине), поэтому теперь я должен…»
4
Наличие методов "canX ()" и "doX ()" является антипаттерном: если интерфейс предоставляет метод "doX ()", лучше иметь возможность делать X, даже если выполнение X означает отсутствие операций (например, шаблон нулевого объекта) ). Это не должно никогда быть возложено на пользователей интерфейс , чтобы участвовать во временной связи (вызвать метод А перед методом Б) потому , что слишком легко получить неправильные и ломать вещи.
1
Наличие интерфейсов не имеет отношения к использованию композиции над наследованием. Они просто представляют функциональность, которая доступна. То, как эта функциональность входит в игру, не зависит от использования интерфейса. То, что вы сделали здесь, - это использование специальной реализации интерфейса без каких-либо преимуществ.
Миямото Акира
Интересно: ответ с наибольшим количеством голосов говорит в основном то, что говорит мой комментарий выше. Иногда тебе просто везет.
@nocomprende - да, я обновил свой ответ в соответствии с несколькими комментариями. Спасибо за отзывы :)
Эмерсон Кардосо
0

Ни один из предложенных вами вариантов не является хорошим ОО. Если вы пишете операторы if вокруг типа объекта, вы, скорее всего, делаете 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 (). Вы также можете вернуть логическое значение или выполнить молчание (не рекомендуется). Это действительно зависит от специфики дизайна.

JimmyJames
источник
Не все учетные записи могут быть сброшены. Кроме того, может ли учетная запись обладать большей функциональностью, чем возможность перезапуска?
TheCatWhisperer
2
«Не все учетные записи могут быть сброшены». Не уверен, если это было написано до редактирования. Второй класс есть NotResetable. «Возможно, учетная запись обладает большей функциональностью, чем возможность повторной загрузки», - я ожидаю, но это не кажется важным для рассматриваемого вопроса.
JimmyJames
1
Это решение идет в правильном направлении, я думаю, поскольку оно обеспечивает хорошее решение внутри ОО. Вероятно, измените имя интерфейса имя на IPasswordReseter (или что-то в этом роде), чтобы отделить интерфейсы. IAccount может быть объявлен поддерживающим несколько других интерфейсов. Это даст вам возможность иметь классы с реализацией, которая затем будет составлена ​​и использована делегированием для объектов домена. @JimmyJames, незначительный прыщ, на C # оба класса должны будут указать, что они реализуют IAccount. В противном случае код выглядит немного странно ;-)
Миямото Акира
1
Кроме того, проверьте комментарий Snowman в одном из других ответов о CanX () и DoX (). Не всегда анти-паттерн, хотя, как он использует (например, по поведению пользовательского интерфейса)
Миямото Акира
1
Этот ответ начинается хорошо: это плохо ООП. К сожалению, ваше решение так же плохо, потому что оно также подрывает систему типов, но выдает исключение. Хорошее решение выглядит иначе: не используйте не реализованные методы для типа. .NET Framework фактически использует ваш подход для некоторых типов, но это было однозначно ошибкой.
Конрад Рудольф
0

Ответ

Мой слегка опрометчивый ответ на ваш вопрос:

Возможности объекта должны быть определены через себя, а не путем реализации интерфейса. Однако статически строго типизированный язык ограничит эту возможность (по замыслу).

Приложение:

Ветвление по типу объекта - зло.

бессвязный

Я вижу, что вы не добавили c#тег, но спрашиваете в общем object-orientedконтексте.

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

В динамически типизированных ОО-языках эта проблема не возникает из-за концепции, называемой «типизацией утки». Короче говоря, это означает, что вы в принципе нигде не объявляете тип объекта (кроме случаев его создания). Вы отправляете сообщения объектам, и объекты обрабатывают эти сообщения. Вам не важно, есть ли у объекта метод с таким именем. В некоторых языках даже есть общий, универсальный method_missingметод, который делает его еще более гибким.

Итак, на таком языке (например, Ruby) ваш код становится:

account.reset_password

Период.

Он accountлибо сбросит пароль, либо выдаст исключение «отказано», либо выдаст исключение «не знаю, как обрабатывать« сброс_пароля »».

Если по какой-либо причине вам необходимо выполнить явное ветвление, вы все равно сделаете это с возможностью, а не с классом:

account.reset_password  if account.can? :reset_password

(Где can?просто метод, который проверяет, может ли объект выполнить какой-либо метод, не вызывая его.)

Обратите внимание, что хотя мои личные предпочтения в настоящее время относятся к динамически типизированным языкам, это всего лишь личные предпочтения. В статически типизированных языках тоже есть что-то интересное, поэтому, пожалуйста, не воспринимайте это бессмысленно как удар по статически типизированным языкам ...

Anoe
источник
Здорово иметь вашу точку зрения здесь! Мы на стороне Java / C # склонны забывать, что существует другая ветвь ООП. Когда я говорю своим коллегам, JS (при всех его проблемах) во многих отношениях больше, чем C #, они смеются надо мной!
TheCatWhisperer
Я люблю C # и думаю, что это хороший язык общего назначения , но лично я считаю, что с точки зрения ОО он довольно беден. Как по функциям, так и по практике, это способствует процессуальному программированию.
TheCatWhisperer
2
Можно утверждать, что тестирование на наличие метода в языке с утиной типичностью аналогично тестированию на реализацию интерфейса в статически типизированном: вы выполняете проверку во время выполнения того, имеет ли объект определенную возможность. Каждое объявление метода - это, по сути, свой собственный интерфейс, идентифицируемый только именем метода, и его существование не может быть использовано для вывода какой-либо другой информации об объекте.
IMSoP
0

Кажется, что ваши варианты происходят от различных мотивационных сил. Первый, похоже, взломан из-за плохого моделирования объектов. Это продемонстрировало, что ваши абстракции неверны, так как они нарушают полиморфизм. Скорее всего, это нарушает принцип открытого закрытого типа , что приводит к более жесткому сцеплению.

Ваш второй вариант может быть хорошим с точки зрения ООП, но приведение типов смущает меня. Если ваша учетная запись позволяет вам сбросить свой пароль, зачем вам в этом случае приведение типов? Таким образом, предполагая, что ваше CanResetPasswordпредложение представляет собой фундаментальное бизнес-требование, являющееся частью абстракции учетной записи, ваш второй вариант в порядке.

Западло
источник