Здравствуйте, Baiker, Вы писали:
Q>>Основная мотивация, стоящая за default interface methods — чтобы можно было добавлять методы в интерфейс, не ломая компиляцию существующих имплементоров.
B>Хм... а это что за идиотизм?? С чего бы это изменённый предок "не должен" ломать наследников? ЕЩЁ КАК ДОЛЖЕН!! B>Если у тебя класс "ГеомФигура" и ты помимо "Площадь" добавил метод "Периметр", все наследники обязаны реализовать правильный метод "Периметр"! B>Собственно, потому интерфейс и является КОНТРАКТОМ, что там нет "отлынивающих классов" — любой из наследников ОБЯЗАН корректно реализовать контракт.
Когда у вас будет библиотека хотя бы с сотней тысяч пользователей, попробуйте поломать базовый интерфейс.
А если счёт идёт на миллионы строк кода как в интерфейсах .NET?
Представьте, например, что у вас есть большой проект где каждая команда отвечает за свою часть продукта.
Теперь, вы меняете базовый интерфейс, нужно поменять код во всех частях проекта, а для этого придётся ещё и получить одобрение на изменение от всех команд.
Эта функциональность позволяет решить конкретную проблему с наименьшими проблемами.
Если вам нужно, чтобы можно было вызывать без приведения к интерфейсу, используйте класс.
Здравствуйте, _NN_, Вы писали:
_NN>Представьте, например, что у вас есть большой проект где каждая команда отвечает за свою часть продукта. _NN>Теперь, вы меняете базовый интерфейс, нужно поменять код во всех частях проекта, а для этого придётся ещё и получить одобрение на изменение от всех команд.
Ну да, а так по тихому поменяли контракт. Правда у нас теперь "Периметр()" круга равен "Периметр()" квадрата (возвращаясь к примеру выше), но это мелочи, ведь билд не ломается.
Плюс, теперь можно валить на собесах вопросом "чем отличается интерфейс от класса"
Здравствуйте, Doc, Вы писали:
Doc>Здравствуйте, _NN_, Вы писали:
_NN>>Представьте, например, что у вас есть большой проект где каждая команда отвечает за свою часть продукта. _NN>>Теперь, вы меняете базовый интерфейс, нужно поменять код во всех частях проекта, а для этого придётся ещё и получить одобрение на изменение от всех команд.
Doc>Ну да, а так по тихому поменяли контракт. Правда у нас теперь "Периметр()" круга равен "Периметр()" квадрата (возвращаясь к примеру выше), но это мелочи, ведь билд не ломается.
В каком плане поменяли контракт ?
Вот у нас был код:
public interface IA
{
void F();
}
static class X
{
public UseIAF(IA a)
{
a.F();
}
}
class A : IA
{
public void F() { .. .}
}
Добавляем метод по умолчанию.
Можем использовать G, которого раньше не было.
public interface IA
{
void F();
void G() { }
}
static class X
{
public UseIAF(IA a)
{
a.F();
}
public UseIAG(IA a)
{
a.G();
}
}
В реализации интерфейса ничего не меняется как и ожидалось.
class A : IA
{
public void F() { .. .}
}
Какую проблему вы здесь видите ?
Doc>Плюс, теперь можно валить на собесах вопросом "чем отличается интерфейс от класса"
Интерфейс всё ещё остается интерфейсом.
Наследоваться от двух классов нельзя в отличии от реализации множества интерфейсов.
Здравствуйте, _NN_, Вы писали:
_NN>В каком плане поменяли контракт ?
Добавление метода в интерфейс. Это изменение контракта (даже если оно non breaking).
_NN>Какую проблему вы здесь видите ?
В этом теоретическом примере вижу следующие проблемы
— интерфейс более не абстракция.
— интерфейс знает о реализациях, иначе откуда такая уверенность что дефолтный G() подходит для всех реализаций интерфейса.
— если по какой-то причине G() гарантированно подходит для любой реализации, то он может вполне себе быть extension методом.
— программист реализации может просто не заметить добавления G(), который будет не корректный для его реализации. Но в проекте IA.G() рано или поздно кто-то вызовет.
_NN>Интерфейс всё ещё остается интерфейсом. _NN>Наследоваться от двух классов нельзя в отличии от реализации множества интерфейсов.
Только это не основное свойство интерфейса. Интерфейс в первую очередь это абстракция, а теперь можно затянуть в него реализацию.
Не лучше было бы для таких кейсов ввести что-то типа "abstract base class" который бы и реализовывал такое поведение (по сути ведя себя как интерфейс с дефолтными методами)? А пользователю библиотеки уже выбирать наследоваться от интерфейса или такого класса.
Здравствуйте, Doc, Вы писали:
Doc>- интерфейс более не абстракция. Doc>- интерфейс знает о реализациях, иначе откуда такая уверенность что дефолтный G() подходит для всех реализаций интерфейса. Doc>- если по какой-то причине G() гарантированно подходит для любой реализации, то он может вполне себе быть extension методом. Doc>- программист реализации может просто не заметить добавления G(), который будет не корректный для его реализации. Но в проекте IA.G() рано или поздно кто-то вызовет.
Все эти проблемы присущи и абстрактному классу, на который ниже предлагается заменить такие интерфейсы
Doc>Только это не основное свойство интерфейса. Интерфейс в первую очередь это абстракция, а теперь можно затянуть в него реализацию. Doc>Не лучше было бы для таких кейсов ввести что-то типа "abstract base class" который бы и реализовывал такое поведение (по сути ведя себя как интерфейс с дефолтными методами)?
В C# нет множественного наследования, поэтому примерно всё делают через интерфейсы и не всегда интерфейс можно заменить на абстрактный класс.
Такой вот костыль приделали, чтобы работало и плевать, что это идеологически неправильно и портит идею интерфейсов.
Doc>А пользователю библиотеки уже выбирать наследоваться от интерфейса или такого класса.
Весь прикол в том, что я даю API в виде условного интерфейса IPlugin, через который подтягиваю в приложение сторонние dll.
Если разработчики будут выбирать что реализуют, то мне у себя писать отдельные обработчики для интерфейсов и отдельные для классов?
А дальше я обновляю интерфейс для плагинов и у пользователей лотерея, в которой половина старых плагинов работает, т.к. наследовались от класса, а вторая половина на интерфейсе сидела и поломались?
Здравствуйте, karbofos42, Вы писали:
Doc>>- интерфейс более не абстракция. Doc>>- интерфейс знает о реализациях, иначе откуда такая уверенность что дефолтный G() подходит для всех реализаций интерфейса. Doc>>- если по какой-то причине G() гарантированно подходит для любой реализации, то он может вполне себе быть extension методом. Doc>>- программист реализации может просто не заметить добавления G(), который будет не корректный для его реализации. Но в проекте IA.G() рано или поздно кто-то вызовет. K>Все эти проблемы присущи и абстрактному классу, на который ниже предлагается заменить такие интерфейсы
Абстрактный класс не интерфейс. Он поддерживает какой-то уровень абстракции, но при этом обладает реализацией и состоянием. Поэтому пункты выше в большей части к нему не применимы.
K>В C# нет множественного наследования, поэтому примерно всё делают через интерфейсы и не всегда интерфейс можно заменить на абстрактный класс. K>Такой вот костыль приделали, чтобы работало и плевать, что это идеологически неправильно и портит идею интерфейсов.
— Можно было бы реализовать не трогая интерфейсы через абстрактные классы.
— У фичи достаточно узкая область применения, чтобы говорить что это замена множественного наследования.
— Но раз вы сами говорите что обсуждаемая фича — костыль, то какой смысл дальше обсуждать. Хороший код/фичу костылем не назовут
Здравствуйте, Doc, Вы писали:
Doc>Абстрактный класс не интерфейс. Он поддерживает какой-то уровень абстракции, но при этом обладает реализацией и состоянием. Поэтому пункты выше в большей части к нему не применимы.
Применимы. Уровень абстракции достаточно условный и абстрактный класс в общем случае не обязан чем-то отличаться от интерфейсов.
Есть Stream и унаследованные от него MemoryStream, FileStream и какой-нибудь NetworkStream.
Много вот Stream знает про реализацию всего этого добра?
Метод расширения не является аналогом фичи по той причине, что он для всех реализаций интерфейса/наследников класса будет одинаковый,
а человекам нужно поведение по умолчанию с возможность перегрузки для конкретных реализаций.
Если в абстрактном классе добавить виртуальный метод с реализацией, то его добавление так же никто не заметит и не узнает, что можно теперь что-то перегрузить.
Doc>- Можно было бы реализовать не трогая интерфейсы через абстрактные классы. Doc>- У фичи достаточно узкая область применения, чтобы говорить что это замена множественного наследования.
Было бы множественное наследование — думаю, что и фичу бы не добавляли, т.к. было бы доступно это всё через классы сделать.
По сути эта фича приближает интерфейсы к абстрактным классам и есть смысл это использовать там, где не получается взять классы.
Doc>- Но раз вы сами говорите что обсуждаемая фича — костыль, то какой смысл дальше обсуждать. Хороший код/фичу костылем не назовут
Ну, я просто не согласен, что у фичи есть полная альтернатива в языке.
Какой-нибудь "override new" для меня тоже костыль и я бы в язык это не добавлял, как и реализацию методов в интерфейсах.
Только разработчики языка почему-то со мной не советуются
Здравствуйте, karbofos42, Вы писали:
K>Применимы. Уровень абстракции достаточно условный и абстрактный класс в общем случае не обязан чем-то отличаться от интерфейсов.
Тот факт что у абстрактного класса может быть состояние уже меняет все. Как раз свести абстрактный класс к интерфейсу это не общий а частный случай.
K>Есть Stream и унаследованные от него MemoryStream, FileStream и какой-нибудь NetworkStream. K>Много вот Stream знает про реализацию всего этого добра?
Не должен знать.
K>Метод расширения не является аналогом фичи по той причине, что он для всех реализаций интерфейса/наследников класса будет одинаковый, K>а человекам нужно поведение по умолчанию с возможность перегрузки для конкретных реализаций.
Это от дизайна зависит можно использовать расширение или нет.
K>Было бы множественное наследование — думаю, что и фичу бы не добавляли, т.к. было бы доступно это всё через классы сделать.
Здравствуйте, Doc, Вы писали:
Doc>Тот факт что у абстрактного класса может быть состояние уже меняет все. Как раз свести абстрактный класс к интерфейсу это не общий а частный случай.
А такой интерфейс имеет состояние или нет?
interface IA
{
int Value {get;set;}
}
Doc>Это от дизайна зависит можно использовать расширение или нет.
Что от дизайна зависит? Возможность перегрузки метода расширения для разных типов, подобно виртуальным методам?
Doc>Да не про множественное наследование эта фича.
Да ладно?
Doc>Это про добавление методов без ломки совместимости.
И зачем же тогда эту фичу добавили в интерфейсы, если можно было спокойно вместо интерфейса брать класс и там это всё работало бы без изменений языка?
Здравствуйте, karbofos42, Вы писали:
Doc>>Это про добавление методов без ломки совместимости.
K>И зачем же тогда эту фичу добавили в интерфейсы, если можно было спокойно вместо интерфейса брать класс и там это всё работало бы без изменений языка?
Тут уже был разбор как сделать множественное наследование чтобы не было проблем как с виртуальным наследованием в плюсах.
Можете поискать в раздели Философии.
Попробую найти.
Конкретно здесь .NET не умеет множественное наследование классов.
А множественное наследование интерфейсов умеет.
Почему единственным вариантом добавлять новую функциональность не ломая миллионы строк кода это методы по умолчанию в интерфейсе.
Как уже указали в Java аналогичная проблема и такое же решение.
Здравствуйте, _NN_, Вы писали:
_NN>Почему единственным вариантом добавлять новую функциональность не ломая миллионы строк кода это методы по умолчанию в интерфейсе. _NN>Как уже указали в Java аналогичная проблема и такое же решение.
Ну, вот в реальной жизни это действительно помогает?
Или спустя некоторое время вылезают неоднозначности и в итоге люди что-то городят своё?
Не лучше ли было не язык коверкать, а написать какой-нибудь фреймворк для плагинов, который бы генерировал нужные прокси и вызывал все эти дефолтные методы?
Что вот делать банально с переименованием метода?
Держать в итоге в интерфейсе и старое имя и новое? А если я просто в enum добавлю пару значений, то как быть? А если не добавлю, а изменю старые, т.к. решил теперь поддерживать Flags?
Эта фича же достаточно узкой выглядит и, на мой взгляд, через неё удобно быстро выкатить новые фишки в продакшон, а вот как это потом поддерживать и дальше расширять?
У меня просто не было задач, где это было бы нужно, потому на практике не использовал и тут чисто теоретический взгляд.
Здравствуйте, karbofos42, Вы писали:
K>Здравствуйте, _NN_, Вы писали:
K>Эта фича же достаточно узкой выглядит и, на мой взгляд, через неё удобно быстро выкатить новые фишки в продакшон, а вот как это потом поддерживать и дальше расширять? K>У меня просто не было задач, где это было бы нужно, потому на практике не использовал и тут чисто теоретический взгляд.
Эта фича, особенно в jave, гибрид сишарповых extension methods и с++ специализации.
Т.к. состояние класса в интерфейсе недоступно, реализация дефолтового метода может опираться только на существующий интерфес, что делает ее по возможностям полным аналогом extension method.
Но, конкретные реализации интерфейса, могут уже использовть внутренний стейт и переопределять дефолтовую реализацию более оптимальным образом.
Для c# выглядит оверкиллом. Хотя второй вариант может быть полезен.
interface ICountableEnumerable {
bool next();
int aCount() {
int times = 0;
while(next()) {
times++;
}
return times;
}
}
class List: ICountableEnumerable {
int aCount() { return size; } // более оптимальная реализация
}
interface ICollectionWithFeatures {
IReadonlyCollection asReadonly() {
var copy = ...;// копируем исходный набор данных, что бы call-site не "сломался"return copy;
}
}
class ReadonlyList: ICollectionWithFeatures, IReadonlyCollection {
IReadonlyCollection asReadonly() {
return this; // можно не копировать, т.к. мы не даем методов модификации текущей коллекции
}
}
Здравствуйте, vsb, Вы писали:
vsb>java: types test.Test.I1 and test.Test.I2 are incompatible; vsb> class test.Test.TestClass inherits unrelated defaults for method() from types test.Test.I1 and test.Test.I2
Здравствуйте, Codealot, Вы писали:
C>Была, есть и будет есть. Не будет ее только в одном подклассе случаев — когда ты добавляешь метод, для которого можно написать дефолтную реализацию, которая имеет смысл по условиям задачи.
Я правильно понимаю, что это реинкарнация (второй аккаунт) сами знаете кого?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Baiker, Вы писали:
B>Это в джамшутской терминологии "ломались". В нормальной: "все наследники получали корректирующую ошибку о недореализованном методе".
Увижу еще раз такой стиль общения, уйдешь в баню на долго. Пока не денёк.
Что касается обсуждаемого вопроса, в МС всё сделали правильно. Основная проблема в разработке сложного ПО — объем кода и сложность его поддержки, развития. Такое решение не создаёт лишние сложности, а значит упрощает жизнь. Каждый интерфейс может получить нужную ему дефолтную реализацию без лишних телодвижений со стороны программиста. Нам не придется бегать по классам и добавлять реализации вручную.
Если не устраивает эта фича, можно завести базовый класс с виртуальным методом и переопределять его там где надо.
B>Спасибо, б%%%! Мало нам таймбомб — давайте теперь ещё и эти.
Несёшь форменный бред.
B>Не надо про УСЛОВНЫЕ СЛУЧАИ. Есть процесс разработки. Если у тебя есть библиотека и она обновилась, там всегда есть breaking changes. Хочешь — не обновляйся и ничего дореализовывать не надо. Хочешь свежачок — компильни, проверь ошибки, дореализуй контракт. Всё. Просто как день. Здесь не идёт вопрос об обратной совместимости — это эволюция продукта, где допустимо улучшение, требующее правки чужих исходников.
Случай не условный, а самый что не наесть конкретный. Сам недавно пользовался этой фичей — очень удобно. Просто не стоит путать публичный интерфейс класса и интерфейсы им реализуемые. Это могут быть разные реализации. Ты и в ручную можешь сделать несколько явных реализаций и публичный метод с тем же именем.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Baiker, Вы писали:
B>Я с большим подозрением стал относиться к "фичам" C# после версии 8.0; Такое ощущение, что все адекваты махнули рукой и сказали "А, лепите что хотите, я устал спорить!".
Создавай отдельные темы по отдельным фичам (как эта) — обсудим.
Лично мне не нравятся не фичи, а непоследовательность их появления и эволюционист. Авторы языка явно добавляли фичи по мере собственного созревания.
Судя по твоей критике ты пока что до этих фич не дорос.
B>Только так можно объяснить поток ОЧЕВИДНО СПОРНЫХ фич, которые обсуждаются хрен знает кем, одабриваются ещё более странными персонами и выкатываются в релиз без широкого обсуждения на общедоступных сайтах. (почему не LJ? Facebook? StackOverflow? Да потому что ТАМ НЕЛЬЗЯ ЗАТКНУТЬ РОТ ОППОНЕНТАМ! )
Процесс обсуждения фич на гитхабе довольно открытый. На СтекОверфлоу обсуждать что либо нельзя, там другой формат. Про Facebook вообще молчу. Не профильная говнопомойка забаненная у нас в стране за русофобию.
B>Унылые, обидчивые посредственности
Боюсь они даже о твоем существовании не подозревают.
B>выкатывают очередной высер их банального мозга
Истерика какая то, а толку — 0.
B>(или того хуже — плохо понятые фичи других языков) и начинается шапкозакидательство тех, кто смеет этих посредственностей критиковать. Более того — количество критики вообще никак не влияет на последущее решение — фича просто делается по задумке тупого индуса-придумщика и далее преподносится как очередной "прорыв". Я видел кучи обсуждений на мелкомягком междусобойчике в github и ты никогда не доведёшь свою мысль до их замкнутого разума.
У меня лично много претензий к C#. Но направление развития языка правильное. Все добавленные фичи — полезны. Моя главная претензия в том, что они добавлены эволюционным путём. Люди не проектировали язык как единое целое. По сему иногда встречаются недоработки. Но это явно не тот случай. Дефолтные реализации для интерфейсов сделаны не плохою.
B>Вот почему Nemerle — наше всё. Всегда можно общедоступно обсудить спорные моменты и более того — даже самому реализовать так, как ты считаешь нужным! Да, бывают в жизни и "две правды" сразу, но никто не имеет права принуждать тебя использовать "чужую" правду. У кого какие задачи — тот пусть себе и пилит "наименее спорный вариант" фичи.
В Немерле, к сожалению, такой фичи нет. Добавил бы, а не уставил здесь истерику. Тогда по праву мог бы говорить, что вот правильная реализации.
B>Вот дефолтовые методы в интерфейсах — я думал, это что-то полезное! Оказалось, высер.
Очень даже удобно. Недавно применил в реальном боевом проекте чтобы не писать кучу кода. В интерфейсе (который уже был) свойство выставил в одно значение, а в нужных классах реализации переопределил на обратное. Сэкономил море времени.
В том что эта фича не добавляет публичного метода, а создает явные реализации проблемы не вижу. Работа с членами интерфейса должна вестись через интерфейс. Иначе не нужен интерфейс, а нужно пользоваться наследованием реализаций и виртуальными методами.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, _NN_, Вы писали:
_NN>Какую проблему вы здесь видите ?
в том что оно не упало, и есть проблема и теперь мы по тихому используем a.G() вообще не понимая что там, и даже не зная что нам надо его реализовать
не практике будет так:
* обновили библиотеку, ничего не упало, все работает
* спустя время кто-то из команды в интелисенсе найдет этот новый метод, заиспользует,
* если метод имеет какую то дефолтную реализацию — может даже покажется в начале что отлично работает
* спустя еще время потратят хренову тучу времени чтобы понять что мы вообще этот метод не реализовали, и отрабатывает какая то дефолтная заглушка
если уж очень хочется, как альтернатива, могли позволить указывать дефолтные методы, компиляция бы не падала, но всегда кидало бы NotImplemented exception