Здравствуйте, Воронков Василий, Вы писали: ВВ>>>В итоге окажется, что для того, чтобы реализовать один нужный мне интерфейс мне нужно реализовать еще 25 ненужных методов путем throw NotImplementedException, SV.>>И это ОЧЕНЬ хорошо, поскольку когда клиент вашего кода вызовет не нужный ВАМ, но нужный ЕМУ метод, и метод выкинет NotImplementedException, то долго не придется искать, кого бить по наглой рыжей морде. Что касается нужности, все наоборот: контрактную часть описывает архитектор системы, а "орлы" ее реализуют. И не дело "орлов" принимать решения на уровне архитектуры, по крайней мере, не посоветовавшись с остальными. ВВ>Ничего не понял. В моем случае как раз никто не получит по морде. Потому что клиент моего кода явно задекларирует те интерфейсы, которые ему ДЕЙСТВИТЕЛЬНО нужны, а не получит бомбу с NotImplementedException.
Бомбу получить можно всегда, если в команде есть саперы. Которые тихой сапой.
Дело вот в чем. Большая система состоит обычно из разных частей. Один имплементирует мессенджерские клиенты, другой — их использование, третий — увязывает двух первых. Интерфейсы для того и нужны, чтобы это увязывание гарантировано работало. Чтобы не было как на этой картинке:
Скрытый текст
Что делать этому третьему, когда вы ему вернете мессенджер, в котором по каким-то своим причинам ЯВНО не стали поддерживать отправку текста, а его клиенты столь же ЯВНО требуют избегать таких ситуаций? Особенно замечательно будет, когда этот третий — приложение, которое пишется раньше всего, а первые два — плагины. Что делать автору приложения? Вот он и вводит КОНТРАКТЫ, которые остальные должны соблюдать, если не хотят по наглой рыжей морде. SV.>>Как с архитектором я бы с вами работать не стал. ВВ>ОК, если у нас откроются вакансии, то я вас не позову.
Возьмите хотя бы чай подавать А еще я козликом вышиваю. И это самое... мастер. На фендер-стратокастер.
Здравствуйте, Воронков Василий, Вы писали:
SV.>>Затем, что хороший ОО-код отражает объективную реальность, а плохой ОО-код — своего автора. В жизни есть мессенджеры, которые умеют отправлять тексты, но не умеют файлы, например, СМСный гейт, но нет таких мессенджеров, которые умеют отправлять файлы, но не умеют тексты. Мой код это отражает. Ваш — нет.
ВВ>Я не знаю, что именно отражает ваш код, и какие именно мессенджеры существуют в жизни — а вы же по ходу уверены, что точно и наверняка знаете какие абстракции будут правильны в 100% случаев. Вот только в 100% эти абстракции оказываются неправильными.
Это бывает, да. Но для того и меню Refactor, чтобы в момент осознания несогласованности вашей объектной модели с жизнью его нажимать. И если не лениться и рефакторить код каждый раз, когда это вылезает, то да, НА ПРАКТИКЕ в 100% случаев ваши абстракции будут, как вы выразились, "правильными" (я же имел в виду их неискуственность => удобочитаемость => поддерживаемость).
В первоначальном примере у меня как раз рассматривался случай, когда появляется какой-нибудь DropBoxClient, контракт становится невалидным, его переписывают и компилятор показывает все вытекающие из этого ошибки. Для простоты я его стер. Зря, наверное.
>Потому что ОО код никакого отношения к объективной реальности не имеет.
Чем меньше не имеет, тем хуже (его читателям).
ВВ>Но дело даже не в этом. Я предложил подумать и высказать реальные причины, почему что-то реализовано так, а не иначе. Вы же в ответ выдали "ОО-код отражает объективную реальность". Не стоило, право, и начинать
Я привел конкретный пример и, как мог, объяснил, почему он... нет, не реализован, а ЗАПРОЕКТИРОВАН так, а не иначе. В том-то и дело, что реализовать всегда можно хоть так, хоть иначе, и все будет работать. Теперь я понимаю, почему вы не понимаете наследование интерфейсов. Оно не вам нужно, а тому, кто ваш код будет читать, править, развивать, фиксить баги, строить на его основе плагины и так далее.
Хотите голосовалку создам, с вашим интерфейсом и моим? И пусть vox populi скажет, с чьей ОМ ему работать больше по душе. Ставки УЖЕ сделаны, оба интерфейсных куска кода написаны, все ходы записаны.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Здравствуйте, b-3, Вы писали:
b-3>>И всё же без него нельзя обойтись, т.к. это единственный способ (в mainstream, во всяком случае) выразить средствами языка отношение "общее-частность".
ВВ>Почему единственное? Интерфейс — всегда общее. Реализация — всегда частность. Понятно, что из интерфейсов можно построить некую иерархию, но зачем? Можно просто разобрать эту иерархию, и все будет работать точно так же. Собственно, зачем интерфейсы по отношению друг к другу должны находиться в отношении "общее-частностность"? Что это дает?
Это даёт проверку отношения "общее-частность" компилятором и IDE. "Разобрали" иерархию — только на тестирование можно надеяться. Минус бонусы в виде рефакторинга, минус оптимизация по скорости.
А ещё, в коде появится куча неприятных мест, где объект получаем по одному интерфейсу, якобы частному, а затем динамически приводим к "другому", который "общий"... вернее, раньше был "общим", но там появился эээ нюанс — да бог с ним! с молитвой так сказать. Почему вообще наобум приводим? Да потому что костыли, потому что разные разработчики у разных кусков кода, а ещё какой-то вася влепил IMyInterface2 в целях расширения, а ещё у нас сплошной TDD при выключённом мозге... И будет проверка в рантайме. И падения в таких местах у вас будут вылезать как Access Violation в программе на Delphi — вроде можно их все отловить, а что-то никак не получается
Конкретно в шарпе ещё можно нарваться на ошибку, когда IBase.Foo() — это одна реализация, а IChild.Foo() — другая. Вам будет очень весело ловить баг, когда вы "разобрали иерархию", а кто-то воткнул два одноимённых метода.
З.Ы. Я вполне осознаю, что и с наследованием можно аналогично влететь, потому что компилятор контракт интерфейса всё равно полностью не проверяет... но шансов меньше. Тут принцип один — максимально верифицировать код и статикой (типизация, code contracts), и тестированием. Грубо говоря, дублирование вместо наследования оправдано только в том случае, если оно резко сокращает объём юнит-тестов. Даже не реальных тестов, а воображаемого "полного набора тестов", проверяющего без халтуры все-все требования к коду.
А ещё бывает, что дублирование интерфейсов оправдано моделью версионирования/форматом выпуска ПО.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Реализация интерфейсов классами — да. Но сами-то интерфейсы зачем друг от друга наследовать? В случае с классами — это хотя бы виртуальные функции и перегрузка. А с интерфейсами? Что дает наследование?
Это и есть полиморфизм на наследовании и виртуальных методах. Вполне может быть иерархия интерфейсов, ортогональная иерархии реализующих их классов. Для чего вообще нужны иерархии? Для распределения функциональности. Вот на каждом уровне иерархии своя функциональность — от общей к частной.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Зачем это делать? Есть какие-то причины для этого, кроме экспериментов над людьми? ВВ>Мне достаточно задекларировать список интерфейсов, которые я хочу получить — все.
Это для одного генерика. А если у нас абстрактные алгоритмы для многих генериков? А если возникает ситуация, когда генерик определяет базовый класс? Ведь так нельзя.
Здравствуйте, SV., Вы писали:
ВВ>>Я не знаю, что именно отражает ваш код, и какие именно мессенджеры существуют в жизни — а вы же по ходу уверены, что точно и наверняка знаете какие абстракции будут правильны в 100% случаев. Вот только в 100% эти абстракции оказываются неправильными. SV.>Это бывает, да. Но для того и меню Refactor, чтобы в момент осознания несогласованности вашей объектной модели с жизнью его нажимать. И если не лениться и рефакторить код каждый раз, когда это вылезает, то да, НА ПРАКТИКЕ в 100% случаев ваши абстракции будут, как вы выразились, "правильными" (я же имел в виду их неискуственность => удобочитаемость => поддерживаемость).
Вы предлагаете рефакторить код, каждый раз когда меняются требования? Что-то я не улавливаю суть. А если нельзя рефакторить, и код уже в продакшине? Вообще такая роскошь как рефактор *интерфейсов* далеко не всегда доступна. Закладываться на это при дизайне — я бы лично не стал.
Может, стоит подойти с другого конца и постараться сделать код более устойчивым к изменениям? Например, не делать предположений — "все мессенджеры, умеющие отсылать файлы, должны уметь отсылать текст". Меня пугает такой постулат, вводимый на уровне дизайна. Более того, непонятно, что он дает.
Вы привели пример, в котором клиентский код знает какой *тип* у сообщения, которое он собирается отослать. Но он не знает, какой мессенджер будет отсылать это сообщение. Наиболее очевидное решение здесь — вводить "перегрузку" по типу сообщения. Необходимость же введения *зависимости* между типами сообщений для меня далеко не очевидна. И совершенно не следует из примера.
SV.>В первоначальном примере у меня как раз рассматривался случай, когда появляется какой-нибудь DropBoxClient, контракт становится невалидным, его переписывают и компилятор показывает все вытекающие из этого ошибки. Для простоты я его стер. Зря, наверное.
Неудивительно, что вы его стерли. Он же ломает вашу абстракцию.
>>Потому что ОО код никакого отношения к объективной реальности не имеет. SV.>Чем меньше не имеет, тем хуже (его читателям).
Нет никакой объективной реальности.
Есть люди, которые придумывают "что" должно быть.
Есть люди, которые придумывают "как" это должно быть реализовано.
И это — разные люди.
В первой итерации проекта вам сказали — у нас есть мессенджер. Вы решили — мессенджер это что-то "типа скайпа". Во второй итерации оказалось, что дропбокс — это тоже внезапно мессенджер. Но в вашу систему абстракций это не вписывается. Рефакторить? Но ведь код уже в продакшине.
На самом деле в отношении любого программного обеспечения можно сделать два утверждения:
1) Наш код содержит ошибки
2) Наши абстракции неправильные
Ведь 1) это же банальность, правда? Вы же не будете разрабатывать программную систему исходя из "давайте сделаем так, чтобы была 100% гарантия отсутствия ошибок". Это невозможно. Правильнее думать в другом направлении — что мы будем делать при возникновении ошибок, как снизить вред от ошибок и т.п. и пр.
Так вот в отношении абстракций — это такая же банальность. Наши абстракции неправильные. Не стоит думать о том, как мы можем сделать их "правильными". Мы не можем. Потому что "ложки нет", и объективной реальности тоже. Есть меняющиеся требования, и в текущий момент времени мы просто не обладаем достаточной информацией, чтобы сделать абстракции правильными. Мы никогда не обладаем достаточной информацией.
Поэтому вместо того, чтобы пытаться сделать абстракции максимально удовлетворяющие какой-то "объективной реальности", логичнее заранее подготовиться к ситуации "объективная реальность оказалась не такой, как мы считали вначале".
Нету "единственно правильного" решения. Вообще нет никакого "правильного решения". Но те или иные решения могут принести больше или меньше вреда при дальнейшем развитии системы. Вот именно в таком разрезе эту проблему и стоит рассматривать.
ВВ>>Но дело даже не в этом. Я предложил подумать и высказать реальные причины, почему что-то реализовано так, а не иначе. Вы же в ответ выдали "ОО-код отражает объективную реальность". Не стоило, право, и начинать SV.>Я привел конкретный пример и, как мог, объяснил, почему он... нет, не реализован, а ЗАПРОЕКТИРОВАН так, а не иначе. В том-то и дело, что реализовать всегда можно хоть так, хоть иначе, и все будет работать. Теперь я понимаю, почему вы не понимаете наследование интерфейсов. Оно не вам нужно, а тому, кто ваш код будет читать, править, развивать, фиксить баги, строить на его основе плагины и так далее.
Вы не объяснили. Наследование не является средством документирования — как раз напротив, оно может с такой же легкость запутать "читающего", как и помочь ему. Я не вижу как разработчику скайпа поможет то, что вы отнаследовали интерфейсы. Эти интерфейсы слишком абстрактные, чтобы можно было из них что-то вынести. А вот явное декларирование юзкейсов — отсылка сообщений, типы сообщений — вполне потянет на документацию.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Собственно, сабж. Что это дает-то в принципе? На первый взгляд кажется, что это вообще скорее вредно.
Вы абсолютно правы. Я убежден, что наследование интерфейсов ничего не дает. Это просто костыль, оставшийся от старых времен. Наследование вообще ни в каком виде не нужно, потому что порождает фундаментальное уродство под названием "приведение типов".
Вот, специально зарегистрировался даже, чтобы высказаться.
ВВ>> Если метод полиморфный, то нужные интерфейсы просто навешиваются в констрейнтах.
DG>методы сейчас нельзя перегрузить по констрейнтам.
DG>Как будет записываться перегруженный метод S1 если ICollection и IEnumerable независимы?: DG>
Здравствуйте, AlexRK, Вы писали:
ARK>Вы абсолютно правы. Я убежден, что наследование интерфейсов ничего не дает. Это просто костыль, оставшийся от старых времен. Наследование вообще ни в каком виде не нужно, потому что порождает фундаментальное уродство под названием "приведение типов".
Ок. Что взамен?
... << RSDN@Home 1.2.0 alpha 5 rev. 52 on Windows 7 6.1.7601.65536>>
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, AlexRK, Вы писали:
ARK>>Вы абсолютно правы. Я убежден, что наследование интерфейсов ничего не дает. Это просто костыль, оставшийся от старых времен. Наследование вообще ни в каком виде не нужно, потому что порождает фундаментальное уродство под названием "приведение типов".
AVK>Ок. Что взамен?
Вопрос очень широко поставлен, уточните пожалуйста более детально, что вас интересует — синтаксис, семантика, "абстрактно вообще" или "конкретно в С#", или еще что-то, я попробую ответить.
Вообще взамен предлагаются интерфейсы без наследования и генерик-методы с констраинтами на несколько интерфейсов. Плюс отделяемые элементы для расшаривания функционала, не имеющие состояния — traits, которые можно использовать в качестве строительных кирпичиков при создании классов (вместо наследования реализации). Ну это так, мои мысли.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Собственно, сабж. Что это дает-то в принципе? На первый взгляд кажется, что это вообще скорее вредно.
Я подозреваю, что эта тема навеяна недавно введенными в Ela классами. Я полностью поддерживаю мнение о том, что практика из ФП себя оправдывает: чтобы мне реализовать какую-то функцию над данными, мне достаточно гарантий того, что нужные (используемые мною) операции были однозначно определены, а внутреннее устройство данных меня не касается. Это хорошее средство для повторного использования кода. И я не в восторге от того, что в современном мэйнстриме исторически накопилось столько всего для решения фактически одних и тех же задач: классы, интерфейсы, трейтсы, миксины, джненерики, ещё всякие implicit-ы выдумывают, чтобы с этим как-то ужиться, да могут ещё и макросами всё это помазать. Но в последнее время начинают появляться рациональные зёрна. В этой ветке
Здесь ReadWriter лишь указывает на то, что в него включены интерфейсы Reader и Writer (могут быть также и свои операции), наследование здесь косвенное. Фактически, это аналог хаскелевских деклараций вида:
class (Eq a, Show a) => C a where ...
Здесь есть пример реализации функции интерфейса для структуры SectionReader. В Go нет явных секций типа "implementation" или "instance", поэтому у него свои особенности в декларациях, вроде нет необходимости всегда всё реализовывать, т.е. не нужны "NotImplementedException". А в функции "some_func" нет необходимости писать в каких-нибудь вариантах:
— func some_func(rw (Reader, Writer) ) {...}
— func some_func(rw Reader|Writer) {...}
— func some_func(rw a) where a Reader, Writer {...}
Короче говоря, я лишь хочу сказать о том, что в мэйнстриме вроде намечаются сдвиги в нужную сторону, но нирваны пока не видно. У Go свои тараканы, подает надежды новый Rust, но там тоже заметны свои заморочки, да и классы вроде собираются вводить, имхо, нафиг там ненужные. Беда пока...
Здравствуйте, AlexRK, Вы писали:
ARK>Вопрос очень широко поставлен, уточните пожалуйста более детально, что вас интересует — синтаксис, семантика, "абстрактно вообще" или "конкретно в С#", или еще что-то, я попробую ответить.
Меня интересует, что будет взамен наследования контрактов, т.е. какой механизм динамической диспетчеризации в языке будет основным, и что взамен наследования реализаций, т.е. как реиспользовать готовые реализации контрактов?
ARK>Вообще взамен предлагаются интерфейсы без наследования и генерик-методы с констраинтами на несколько интерфейсов.
Это не решает проблемы динамической диспетчеризации никак.
... << RSDN@Home 1.2.0 alpha 5 rev. 52 on Windows 7 6.1.7601.65536>>
Здравствуйте, PSV100, Вы писали:
ВВ>>Собственно, сабж. Что это дает-то в принципе? На первый взгляд кажется, что это вообще скорее вредно. PSV>Я подозреваю, что эта тема навеяна недавно введенными в Ela классами.
Ну в общем да, только то, что там называется "классами" — это протоколы Кложура с чуть более гибкой схемой диспатча. У которых в свою очередь ноги растут из тайпклассов. При этом протоколы Кложура не наследуются, а вот в Хаскелле наследование есть.
Причем эти два примера показательны. Протоколы есть нечто чрезвычайно упрощенное, тайпклассы же, напротив, нечто чрезвычайно усложненное. Замечаем при этом, что в Кложуре, где нет наследования протоколов, нет и системы типов — следовательно, и не нужно пытаться выражать какие-то "объединения" интерфейсов, бороться с ограничениями системы типов, все и так работает.
Вот мне и интересно, чем может быть оправдано введение наследования *на самом деле*. Положим, у нас есть "интерфейсы":
IFractional в общем логично отнаследовать от INum, тут спору нет. Но... зачем? Что мы теряем, если этого наследования не будет?
Кто-нибудь сможет реализовать IFractional без INum. Ну и что? Я понимаю, что с точки зрения общепринятых, так сказать, норм и того, что написано в учебниках это как бы плохо. Но плохо ли?
Все ведь сводится просто к тому, что описание "типа", который я хочу получить, будет выглядеть как (INum IFractional), а не просто IFractional.
PSV>Короче говоря, я лишь хочу сказать о том, что в мэйнстриме вроде намечаются сдвиги в нужную сторону, но нирваны пока не видно. У Go свои тараканы, подает надежды новый Rust, но там тоже заметны свои заморочки, да и классы вроде собираются вводить, имхо, нафиг там ненужные. Беда пока...
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Что это дает-то в принципе?
Указывает, что все объекты, реализующие один интерфейс, реализуют также и второй.
Как иначе сделать что-нибудь типа GUI-библиотеки, где всё, по чему можно клацнуть, имеет координаты и метод отрисовки, но не всё, что можно отобразить, отзывается на события?
Здравствуйте, AndrewVK, Вы писали:
ARK>>Вопрос очень широко поставлен, уточните пожалуйста более детально, что вас интересует — синтаксис, семантика, "абстрактно вообще" или "конкретно в С#", или еще что-то, я попробую ответить. AVK>Меня интересует, что будет взамен наследования контрактов, т.е. какой механизм динамической диспетчеризации в языке будет основным, и что взамен наследования реализаций, т.е. как реиспользовать готовые реализации контрактов?
Ну т.е. наследование нужно для подключения реализации? Неужели это без наследования никак не сделать?
Здравствуйте, DarkGray, Вы писали:
ВВ>> Если метод полиморфный, то нужные интерфейсы просто навешиваются в констрейнтах. DG>методы сейчас нельзя перегрузить по констрейнтам. DG>Как будет записываться перегруженный метод S1 если ICollection и IEnumerable независимы?: DG>
Ох, это вы на очень узкий путь вступили
То, что вы хотите описать в рамках системы типов шарпа вообще не описывается. Потому что на самом деле та же проекция должна выглядеть так:
Сейчас же на практике аналогичное почему-то всегда возвращает IEnumerable. Строго говоря, работает это примерно так:
public interface IMapable<E1>
{
IMapable<E2> Map<E2>(Func<E1,E2> fun);
}
И это не самый худший вариант. Почему? Хотя бы потому что здесь мы вводим некую абстракцию. Эта абстракция может быть полезной. Например, мы сможем спроецировать последовальность, не зная конкретного типа этой последовательности. Мы действительно выделили некоторую абстракцию — *сворачиваемость* коллекции.
В вашем же случае интерфейсы играют по сути роль конкретных типов, для того, чтобы пользоваться вашими методами, нужно знать вполне конкретный тип. Причем интерфейсы там даже вредны. Мне вот неочевидно, с чего это ICollection проецируется в LazyCollection.
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, AlexRK, Вы писали:
ARK>>Вопрос очень широко поставлен, уточните пожалуйста более детально, что вас интересует — синтаксис, семантика, "абстрактно вообще" или "конкретно в С#", или еще что-то, я попробую ответить.
AVK>Меня интересует, что будет взамен наследования контрактов, т.е. какой механизм динамической диспетчеризации в языке будет основным, и что взамен наследования реализаций, т.е. как реиспользовать готовые реализации контрактов?
Для динамической диспетчеризации достаточно только концепции интерфейса, зачем здесь наследование? Интерфейс и несколько реализаций — классов. Или я чего-то не понял?
Взамен наследования реализации я писал выше — traits. Обзор концепции — http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf
ARK>>Вообще взамен предлагаются интерфейсы без наследования и генерик-методы с констраинтами на несколько интерфейсов.
AVK>Это не решает проблемы динамической диспетчеризации никак.
Здравствуйте, Roman Odaisky, Вы писали:
RO>Здравствуйте, Воронков Василий, Вы писали:
ВВ>>Что это дает-то в принципе?
RO>Указывает, что все объекты, реализующие один интерфейс, реализуют также и второй.
RO>Как иначе сделать что-нибудь типа GUI-библиотеки, где всё, по чему можно клацнуть, имеет координаты и метод отрисовки, но не всё, что можно отобразить, отзывается на события?
А зачем это вообще нужно?
RO>В ООП очень важно уметь наследовать интерфейсы.
Я думал над этим и пришел к выводу, что это не нужно вообще.
Разумеется, в идеальном мире, в котором весь код можно переписать.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Ну в общем да, только то, что там называется "классами" — это протоколы Кложура с чуть более гибкой схемой диспатча. У которых в свою очередь ноги растут из тайпклассов. При этом протоколы Кложура не наследуются, а вот в Хаскелле наследование есть.
ВВ>Причем эти два примера показательны. Протоколы есть нечто чрезвычайно упрощенное, тайпклассы же, напротив, нечто чрезвычайно усложненное. Замечаем при этом, что в Кложуре, где нет наследования протоколов, нет и системы типов — следовательно, и не нужно пытаться выражать какие-то "объединения" интерфейсов, бороться с ограничениями системы типов, все и так работает.
ВВ>Вот мне и интересно, чем может быть оправдано введение наследования *на самом деле*. Положим, у нас есть "интерфейсы":
ВВ>
ВВ>IFractional в общем логично отнаследовать от INum, тут спору нет. Но... зачем? Что мы теряем, если этого наследования не будет? ВВ>Кто-нибудь сможет реализовать IFractional без INum. Ну и что? Я понимаю, что с точки зрения общепринятых, так сказать, норм и того, что написано в учебниках это как бы плохо. Но плохо ли?
ВВ>Все ведь сводится просто к тому, что описание "типа", который я хочу получить, будет выглядеть как (INum IFractional), а не просто IFractional.
Лично я двумя руками за упрощение. Я не вижу никакого кайфа в иерархии интерфейсов, как таковой. И война с ограничениями системы типов пусть останется для Хаскеля, это его стихия. Проблема в том, что действительно иногда необходимо где-то выразить сущность типа именно как "объединение" (INum IFractional). Имхо, в языках с динамической типизацией, как та же Кложура, возможно, это не так восстребовано. А для Ela есть идеи как указать такие "объединения" ? (сорри, если глупость спрашиваю, я с языком знакомился уже давненько и "по диагонали", и недавно видел здесь инфу про появление "протоколов")