Pull to refresh

Comments 38

(Эмм, полиморфизм через as? Больно же будет, особенно при модификациях.)


На мой взгляд, методы-расширения не стоит считать полноправной ОО-функциональностью в C#, это скорее та его часть, которая идет в функциональную часть. Если у вас есть некий интерфейс, экспонирующий некий (простите) метод, то я хочу иметь возможность этот метод определить в своей реализации этого интерфейса. Решение, когда этот метод внезапно уезжает в расширение, приводит к тому, что я не могу этого сделать.


Просто представьте себе, что я добавил новый наследник от IAuthCredentials. Что нужно сделать, чтобы для него продолжало работать IsValid?

Как только вы начали добавлять Ex и Info и плодить тонны интерфейсов — все поехало по швам. Экстеншены тут вообще не к месту.

В процессе аутентификации взаимодействуют аккаунты и креденшалы: IAccount и ICredentials.

IAccount — Сущности, которым разрешено проходить процесс аутентификации.
ICredentials — это набор параметров, секретов и тд., которые предъявляет вызывающая сторона.
ICredentialsValidator — валидирует конкретный тип креденшалов.

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

Весь ваш код сводится к набору:
User: IAccount

UsernamePasswordCredentials: ICredentials
TokenCredentials: ICredentials

UsernamePasswordCredentialsValidator: ICredentialsValidator
TokenCredentialsValidator: ICredentialsValidator

Ну и простенький ресолв валидаторов. Заодно валидаторы сразу могут получать ссылки на нужный сторадж и выставлять провалидированный аккаунт в ICredentials.
Расширять можно как угодно.

Что и требовалось доказать: совсем отказываться от наследования пока рано! :)
Интересная статья, но очень сложно воспринимать без иллюстраций. На шаге 2 вы определили 5 интерфейсов, 3 хелпера и 1 класс расширений. Мне пришлось их нарисовать, чтобы понять о чём идёт речь, потому что для воображения обычного человека это слишком круто.

Затем, на шаге 2.2 добавили ещё 1 интерфейс и переписали класс расширений с использованием as. Я согласен с lair, что это немного странное решение. А что если появятся ещё интерфейсы? Придётся допиливать этот класс расширений?

Затем на шаге 3 в проекте А добавили ещё 3 интерфейса, 2 реализации, 1 «инфраструктурный» интерфейс, 1 его реализацию и 1 фабрику.

Потом идет проект B, в котором добавляются уже 4 интерфейса, а не 3 как в проекте А. Соответственно 3 реализации, а не 2 как в проекте А. Я не понял чем вызвана эта асимметрия. Ну, и ещё «инфраструктура»: 1 интерфейс, 1 реализация, 1 фабрика.

Итого: 15 интерфейсов, 3 хелпера, 1 класс расширений, 7 классов реализаций, 2 фабрики. Даже нарисовав схему, я не очень понимаю что именно там происходит.

Может быть это решается более простыми способами? Внедрением зависимостей, чтобы не хардкодить варианты через as? Или, например, что вы думаете о примесях (mixin)? Есть интересная статья, правда на примере JavaScript, где примеси рассматриваются как более общий случай наследования, при котором не известен заранее родительский класс. Мне это кажется безумно интересным и по-моему это решает проблемы, описанные в вашей статье. Я писал очень немного про такой подход в этой статье, там есть один пример использования: категория множеств наследуется от абстрактной категории и к ней примешивается поведение полной и колполной категорий.
По большому счёту, примеси — синтаксический сахар для шаблонов. Пока ни в одном языке программирования шаблоны не решили поставленных задач, не породив при этом тонны новых проблем.
В той статье говорится, что есть разные способы реализовать примеси, а они предлагают следующий:

class BaseClass { }
class Mixin1<T> : T where T : new() { }
class Mixin2<T> : T where T : new() { }
class DerivedClass : Mixin2<Mixin1<BaseClass>> { }

Если какие-то примеси часто используются вместе, то их можно объединять в смеси:
class CompoundMixin<T> : Mixin2<Mixin1<T>> where T : new() { }

Т.е. примесь — это более общий случай наследования, при котором заранее не известен суперкласс.
Это уже метапрограммирование. И оно сохраняет все проблемы обычного наследования, только вдобавок ещё и отнимает у программиста любые гарантии по поведению кода.
Как минимум две проблемы это точно решает:
1) Это элегантный способ встроить в класс методы из нескольких классов. Проще чем шаблон делегирования.
2) Примеси не зависят от конкретного родительского класса. Не нужно пытаться заранее выстроить какую-то идеальную иерархию классов на все случаи в жизни. Эту иерархию можно подстраивать под каждый случай отдельно.

Лично мне в JavaScript такой подход помог решить некоторые проблемы, тут пример.
Для вас сверху — возможно. Тем не менее, это метапрограммирование, на выходе которого получаются обычные классы-наследники. Со всеми вытекающими проблемами. Например, traits не позволяют вам во множественное наследование. И не смогут.
Например, traits не позволят вам единообразно обращаться ко всем базовым классам, пережившим встраивание. Возможно, js гораздо толерантнее к подобным запросам, но C++ без плясок с шаблонами не позволит единообразно обращаться к элементам, а любые попытки хранить их единообразно закончатся как и должны: болью.
https://ideone.com/MZOUOk
Я уже очень много лет не писал на C# и C++. И оказывается мой пример на C# не компилируется, нельзя наследоваться от типа, указанного через параметр. Видимо, поэтому в C# есть расширения, которые вероятно позволяют немного иначе решать те же задачи.

А в C++ как-раз можно. Переписал ваш пример. Всё работает. Единственное, на мой взгляд, неправильно, что примесь обращается к каким-то свойствам расширяемой структуры. По идее, в примеси должна быть реализована какая-то самодостаточная функциональность, независящая от других классов. Но если это нужно, то можно сделать как в моём примере.
UFO just landed and posted this here
Да, но тут вообще непонятно зачем по сути разные объекты (со структурами bar и baz из примера) хранить в одном массиве. К обоим структурам примешивается traitFoo, но всё-равно структуры разные.
UFO just landed and posted this here
Если добавляется независимая функциональность, зачем её добавлять в класс? Для реализации независимой функциональности есть, простите за бранное слово, функция.
Смысл наследования именно в доступе к защищённым полям и перекрытии базовой функциональности. На плюсах это требует специальной подготовки самих базовых классов, что вы сделали. При этом разрушив базовую бизнес-логику, чего делать не должны были.
https://ideone.com/xmDK4b

То есть, опять же, это просто шаблоны, собирающие классы вместо вас. Кто-то говорит, что они безопаснее, чем простое наследование. Лично я смогу прострелить себе ногу даже из своего пальца, хватит желания и достаточного объёма обезболивающих.
Потом идет проект B, в котором добавляются уже 4 интерфейса, а не 3 как в проекте А. Соответственно 3 реализации, а не 2 как в проекте А. Я не понял чем вызвана эта асимметрия.

В проекте «A» две credential-сущности, а в проекте «B» — три.
Одно не является подмножеством другого.
Эти множества сущностей имеют только «пересечение» (в части условно стандартных Credential с id пользователя и токеном сессии).
Асимметрия проектов «A» и «B» как раз призвана продемонстрировать тезис статьи, когда в решении по разным сборкам разбросан набор похожих сущностей, решающих один класс задач.
trait THasEmail 
  implements IHasEmail 
{
  protected $email;
  public function getEmail()
  {
    return $this->email;
  }
}

class User {
  use THasEmail;
}

assert(User instanceof IHasEmail);


https://wiki.php.net/rfc/traits-with-interfaces

P.S. Писал комментарий с калькулятора, возможны неточности.

Зачем вообще так сложно?
Для задачи, сформулированной во вступлении, достаточно определить один интерфейс с единственным методом IsValid.
Это расходится с предыдущим текстом, где речь шла о двух методах.
С двумя методами все опять-таки проще — общая функциональность двух классов выносится в третий путем композиции (объекты двух классов содержат в себе ссылки на объекты третьего вместо наследования).
Изначальная аргументация столь же хаотична и противоречива — с точки зрения уменьшения дублирования кода композиция лучше наследования реализаций. Накладные расходы на вызов виртуальных методов связаны не с наследованием, а с полиморфизмом.
Предлагаемая реализация страдает перепроектированием — для иллюстрации достаточно трех классов и одного интерфейса.

OverEngeneering) Много кода для простой задачи
Так, подождите, я не понимаю, как можно одновременно разрешить и задачу динамической типизации в рантайме, и статической типизации во время компиляции, не реализовав два разных синтаксиса? И вопрос не в наследовании, композиция точно так же будет реализована через вызовы методов по указателю, если будет производиться в динамике.
Изначально в статье не была упомянута одна вещь — предлагаемый подход в большей степени применим для случаев, когда проекте в разных сборках уже есть похожие по своей функциональности классы, а в сборках верхнего уровня (в клиентском коде) осуществляется работа с этим классами схожим образом путем обращения к их свойствам и проведению некоторых проверок (copy-paste со всеми последствиями).

Каким образом лучше реализовать единообразие работы с этими классами, не производя переработку и рефакторинг самих классов?

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

Затем реализуем класс с методами расширения для новых интерфейсов, и во всех местах обращения к классам заменяем copy-paste некоторой работы с этими классами на вызов одного метода расширения.

Таким образом, предлагаемый подход применим для работы с legacy-кодом, когда необходимо быстро «починить» и реализовать единообразие работы с некоторым набором классов со схожими (в определенном разрезе) декларациями и функциональностью.

(Вопрос, каким образом в проекте может оказаться такой набор классов, вынесем за скобки.)

При разработке API проекта и иерархии классов с нуля следует применять другие подходы.
Каким образом при этом можно реализовать код без copy-paste, если два или более классов имеют один и тот же по смыслу метод, но немного с разной логикой — тема отдельного разговора.
Возможно, это тема новой статьи.
Я правильно понимаю, вы только что напыщенно описали паттерн «стратегия»?
Каким образом лучше реализовать единообразие работы с этими классами, не производя переработку и рефакторинг самих классов?
В данном случае представляется целесообразным объявить набор интерфейсов (свойства и методы интерфейсов будут повторять уже имеющиеся свойства и методы у классов), и объявить, что сущности реализуют эти интерфейсы.

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


Если не можете, то шаблон "адаптер" ваш друг.

Госсподи боже! Такое чувство, что люди вообще не понимают, о чём сами пишут.
UFO just landed and posted this here
Изначально ООП было задумано для моделирования биологических систем и игр. Сейчас его применяют для автоматизации бизнеса. По опыту скажу, что бизнес-процессы более простые, они не требуют многого того, что заложено в ООП.
Изначально ООП было задумано для моделирования биологических систем и игр

Это вы почему так считаете?


По опыту скажу, что бизнес-процессы более простые, они не требуют многого того, что заложено в ООП.

Например, чего?


(вообще, утверждение, что "бизнес-процессы более простые" — оно, конечно, громкое, да)

Это вы почему так считаете?

Когда изобрели первые языки ООП, типа small talk, я уже зарабатывал программированием. Одни из первых лекций по Оберону нам читал сам Вирт в НГУ

выдержка из https://habrahabr.ru/company/hexlet/blog/303754/
Я считал объекты чем-то вроде биологических клеток, и/или отдельных компьютеров в сети, которые могут общаться только через сообщения.
Одни из первых лекций по Оберону нам читал сам Вирт в НГУ

Кул, точную аттрибутированную цитату привести можете?


Я считал объекты чем-то вроде биологических клеток

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

точную аттрибутированную цитату привести можете?


Внезапная просьба. Вам зачем?
UFO just landed and posted this here
Что такое «пруф»? Если нужно непосредственое доказательство, то оплачивайте приезд Вирта, Страуструпа и других основателей в ваше заведение и с них трясите…

Чтобы понять, на основании чего — кроме своего личного мнения — вы утверждаете, что "изначально ООП было задумано для моделирования биологических систем и игр".


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

Тут особо моего мнения нет, но и ссылку привести не могу. Степень отражения модели и реальности строгому счету пока не поддается. Но посудите сами ООП — объектно-ориентировано, а бизнес просессы процессно-ориентированы. Какая-то разница уже намечается.
Но посудите сами ООП — объектно-ориентировано, а бизнес просессы процессно-ориентированы. Какая-то разница уже намечается.

Тот факт, что есть разница, не означает, что "что бизнес-процессы более простые, они не требуют многого того, что заложено в ООП".


Более того, хотя процессы и процессо-ориентированы, участвующие в них сущности (и акторы, кстати) часто прекрасно поддаются объектно-ориентированному подходу (собственно, ничего не мешает представить бизнес-процесс в виде обмена сообщениями).


Наконец, есть и третий взгляд на все это: ООП может быть слоем разработки ниже модельного уровня.

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

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

предлагаю закончит беседу
Sign up to leave a comment.

Articles