Здравствуйте, LaptevVV, Вы писали:
LVV>Механизм C# и Java — не хотим вводить отдельную конструкцию интерфейса.
А зря.
Мое предложение — множественное наследование интерфейсов и отсутствие наследования реализаций. Т.е. классы вообще не наследуются. А для реюза кода придумать способ упрощения агрегации.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 7 6.1.7601.65536>>
Здравствуйте, Трололоша, Вы писали:
VD>>В Обероне найден фатальный недостаток? Т>Полагаю классический случай NIH
Кончайте брюзжать, в вузах как раз и полагается делать всё самим, а не гундосить: "Всё уже придумано, надо только пользоваться". Это же здорово — сделать всё самому!
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, LaptevVV, Вы писали:
LVV>Кстати, ровно тот же подход предлагали оберонцы! Наследование интерфейсов, а вместо наследования реализации использовать композицию... :)
Здравствуйте, AndrewVK, Вы писали:
AVK>Мое предложение — множественное наследование интерфейсов и отсутствие наследования реализаций. Т.е. классы вообще не наследуются. А для реюза кода придумать способ упрощения агрегации.
наследование реализации — это и есть упрощение агрегации.
Здравствуйте, AndrewVK, Вы писали:
V>>Агрегация по ссылке недалеко ушла от обычной связи разнородных сущеностей AVK>Зачем это натягивание презерватива на глобус?
ИМХО, игнорировать происходящее при этом и есть натягивание непонятно чего непонятно на что. В случае связи по ссылке агрегирование превращается в условность.
V>>. А агрегация по значению имеет ровно те же эффекты, что обычное наследование AVK>Докажи. Причем в обобщенной форме, потому что я никакой специфики реализации агрегации не оговаривал. Вывод то твой совсем не очевиден.
В обощенной форме можно прямо сейчас посмотреть в С++, где допустимо отнаследоваться от параметра-типа шаблонного класса. Всё тоже самое.
AVK>Ну и какая такая уникальная ценность у наследования классов, которую невозможно обеспечить более безопасными средствами?
Ценность в лаконичности. Ценность в том, что работает ОО-парадигма в дизайне: ищем общее и обыгрываем частное. Любое автоматическое делегирование лишь усилит все твои опасности, т.к. к имеющемуся автоматическому делегированию конкретной базе добавится точно такое же делегирование, только уже к произвольной. Угу, включая null. Это мы еще не обсуждали +1 косвенность на каждой операции делегирования... а сколько их будет в реальных проектах? От 5 до 10-ти? Это столько раз надо будет разыменовать ссылки, чтобы прочесть поле тривиального типа?
И я уже приводил пример VB/VBA. Ты фактически предлагаешь ограничится техникой наподобие VB/VBA, хотя всё мировое сообщество считало VB.Net шагом вперед.
V>> ИМХО, если без особого фанатизма, то локальные иерархии объектов очень даже удобны. Просты, лаконичны, беспроблемны. AVK>1) Вовсе не просты. Наследованием ты на ровном месте порождаешь весьма высокую связность, как по внутренностям, так, что еще печальнее, по публичному контракту. И устранить это вообще никак невозможно.
Это смотря как каркас дизайна устроен. Если интерфейсы каркаса даны "сверху", то подробности конкретных типов-реализаторов никого не интересуют и вообще не видны.
AVK>2) Про лаконичность вообще смешно. Чтобы подмешать функциональность целый класс плодить вовсе необязательно.
В тру-ООП обязательно. Кастомные конструкторы Джавы в пример. Функциональщина дотнета — это тоже сахар над ООП унутре.
AVK>3) Беспроблемность тоже сродни беспроблемности ручного управления памяти. Она достигается только при соблюдении 100500 условий, которые компилятором не контролируются и даже не поощряются. И все это без каких либо бенефитов от такого слонопотамистого и негибкого механизма с кучей побочки.
Ты упустил весь контекст относительно локальности иерархий. То бишь, условий не 100500, а ровно одно — видимость подробностей. Просто "Мост" я уже 3-й раз напоминаю.
Вопрос: а интерфейсы ты тоже наследовать не будешь? А если будешь — в чем принципиальная разница?
V>> Насчет "спрятать от остального кода" — пользуемся паттерном "Мост" и всех делов
AVK>То есть куча синтаксического мусора продолжает пухнуть.
В легковесной его форме можно обойтись без интерфейсов, только на абстрактных классах. И да, этот мост нужен только в публичной части модулей.
AVK>Особенно эротично получается, когда мост тоже нужно сделать довольно богатым по контракту (передать дерево с разнотипными узлами, к примеру). А когда нам надо оторвать виртуальный метод о иерархии? Будем визитор ваять и раздувать ворох пустого кода еще больше? При этом, при грамотном подходе, всей этой фигни можно избежать, всего то оторвав наследование контрактов от наследования реализаций.
Кто тебе мешает это сделать прямо сейчас и посмотреть, во что это выльется? Плавали, знаем. Это работает только на самом высокоуровневом слое "клея", где относительно немного сущностей, м/у которыми заведомо сильная связь. А если же стремиться к твоей цели — низой связанности... то т как-то упустил из виду, что весь внутренний дизайн в случае явного агрегирования — это и будет сплошной "Мост", как он было в проектах на VB. Что простейший паттерн "шаблонный метод" у тебя должен будет трансформироваться в намного более сложный в обслуживании "стратегию", что визиторы у тебя будут сами по себе мостом и/или одновременно адаптерами к 2-м (минимум к 2-м) отношениям пар ничего не знающих друг о друге интерфейсов. Вот распиши ради смеха визитор в условиях агрегации независимых типов как со стороны посетителя, так и со стороны посещаемого... — это же застрелиться. Обычное делегирование, которое могло бы быть выполнено с помощью синтаксического сахара компилятора — уппсс, уже не работает, как только нам нужно от агрегированного объекта получать хоть какой-то callback, как в двойной диспетчеризации... ведь для обеспечения работы калбэка надо будет выполнить уже "обратное" агрегирование. И так на каждый малейших чих. И что самое опасное, при обратном агрегировании мы можем потерять самое ценное в визиторе — конкретный тип узла. Таки ооочень рекомендую набросать визитор в условиях агрегирования, когда агрегируемый объект ничего не знает о посетителе (как ты хотел).
Здравствуйте, AndrewVK, Вы писали:
AVK>Мое предложение — множественное наследование интерфейсов и отсутствие наследования реализаций. Т.е. классы вообще не наследуются. А для реюза кода придумать способ упрощения агрегации.
Я тоже за эту идею. И хочу еще одну штуку, тесно связанную с отсутствием наследования реализаций. Я хочу избавиться от new ... и "конструкторов" как таковых. Методы "создания" объектов — это "обычные" с точки зрения всего остального кода статические методы.
Сама идея вполне естественно возникает в тех языках, где функция — объект первого класса. Периодически во всякие трансформаторы (вроде map и т.п.) хочется скормить конструктор объекта. А нельзя . Чисто теоретически, можно сделать синтаксическую конструкцию вида "map (new SomeClass) ... и это будет работать. Но останется еще одна проблема — перегрузка конструктора по имени и "неравноправие" конструкторов. Пример:
public class Rectangle {
public Rectangle(int x, int y, int width, int height) {...}
public static Rectangle createByPoints(int x1, int y1, int x2, int y2) { return new Rectangle(x1, y1, x2-x1, y2-y1); }
}
Проверки и т.п. можно добавить. createByPoints можно с двумя точками сделать, но я не хочу (для данного примера). Логичное решение здесь — сделать еще статический метод createByPointAndSize, а конструктор сделать приватным. Но все равно остается лишний код конструктора и обертка к нему. Немного, но писать все же лень.
Объясню, почему фича связана с наследованием реализаций. Классический "конструктор" в языках с наследованием реализаций на самом деле является инициализатором. Он не создает новый объект, а инициализирует существующий. Создать объект он не может потому, что не знает точную структуру объекта (может создаваться его наследник). Поэтому выделение объекта переложили на "пользователя" конструктора, и в коде создали оператор new. Синтаксически, наверное, можно было генерировать статические методы для создания объектов, но полностью от конструкторов отказаться не получилось бы (не ясно, где вызов родительского инициализатора, а где — создание нового объекта). Без наследования реализации любой "конструктор" теперь точно знает структуру объекта, который нужно создавать. Поэтому может и выделять память.
Как примерно это может выглядеть:
public class Rectangle {
public constructor byPointAndSize(int x, int y, int width, int height) { this.x = x; this.y = y; ... }
public static Rectangle byPoints(int x1, int y1, int x2, int y2) { return Rectange.byPointsAndSize(x1, y1, x2-x1, y2-y1); }
public constructor square(int x, int y, int width) {
initialize byPointAndSize(x, y, width, width);
}
}
Конструктор (constructor) — это с точки зрения всего остального кода static <T> метод. T — класс, внутри которого объявлен конструктор. Все остальные модификаторы (доступ и т.п.) — как обычно. Внутри метода ведет себя почти как instance method (т.е. имеет this). Почти, потому что имеет специальную форму — вызов других инициализаторов ininialize(аналог существующих вызовов других конструкторов из конструктора). Соответственно, при генерации и выполнении сначала выделяется память и затем вызывается инициализатор (с уже существующим this). initialize — вызов другого инициализатора (без выделения памяти!). На вызов initialize можно навесить те же ограничения, которые навешиваются на вызов других конструкторов из конструктора (например, может быть только первым statement в конструкторе).
Итого. Вроде бы реализуемо. И получаются "first class constructors", которые являются обычными статическими методами. По синтаксису в простейших случаях можно сделать очень похожим на "стандартные" конструкторы, вместо new Point(3, 5) будет Point.new(3,5). Так что простые сценарии подобный подход не усложняет.
Здравствуйте, vdimas, Вы писали:
V>>>Если этот контракт виртуальный/абстрактный, то контроль за происходящим в конкретном месте уже не тот. AVK>>Ничего не понял
V>А что тут непонятного?
Вообще ничего не понятно.
V> Например, подменить базовый класс в обычном наследовании никак, а в агрегировании можно подменить чем угодно.
Это смотря как агрегирование будет реализовано. Никаких проблем запретить подмену или сделать ее такой, чтобы намерение нужно было выражать явно нет.
AVK>>Классический IoC никакой агрегации не предполагает и опысывает внешние связи разнородных сущностей.
V>Агрегация по ссылке недалеко ушла от обычной связи разнородных сущеностей
Зачем это натягивание презерватива на глобус? Практический то вывод какой из твоих замысловатых построений?
V>. А агрегация по значению имеет ровно те же эффекты, что обычное наследование
Докажи. Причем в обобщенной форме, потому что я никакой специфики реализации агрегации не оговаривал. Вывод то твой совсем не очевиден.
AVK>>Чем меньше возможности сделать бяку, тем лучше, вне зависимости от квалификации разработчика.
V>Дык, не спорю, главное, чтобы не в ущерб.
Ну и какая такая уникальная ценность у наследования классов, которую невозможно обеспечить более безопасными средствами?
V> ИМХО, если без особого фанатизма, то локальные иерархии объектов очень даже удобны. Просты, лаконичны, беспроблемны.
1) Вовсе не просты. Наследованием ты на ровном месте порождаешь весьма высокую связность, как по внутренностям, так, что еще печальнее, по публичному контракту. И устранить это вообще никак невозможно.
2) Про лаконичность вообще смешно. Чтобы подмешать функциональность целый класс плодить вовсе необязательно.
3) Беспроблемность тоже сродни беспроблемности ручного управления памяти. Она достигается только при соблюдении 100500 условий, которые компилятором не контролируются и даже не поощряются. И все это без каких либо бенефитов от такого слонопотамистого и негибкого механизма с кучей побочки.
V> Насчет "спрятать от остального кода" — пользуемся паттерном "Мост" и всех делов
То есть куча синтаксического мусора продолжает пухнуть. Особенно эротично получается, когда мост тоже нужно сделать довольно богатым по контракту (передать дерево с разнотипными узлами, к примеру). А когда нам надо оторвать виртуальный метод о иерархии? Будем визитор ваять и раздувать ворох пустого кода еще больше? При этом, при грамотном подходе, всей этой фигни можно избежать, всего то оторвав наследование контрактов от наследования реализаций.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 7 6.1.7601.65536>>
Здравствуйте, Философ, Вы писали:
AVK>>Ну это ты не представляешь. А вообще можно, к примеру, было передать единственному классу в конструктор 1-2 функции. Ф>нафига это городить?
Городить? Я тебе предлагаю вместо сотни классов сделать один — экономия в размере кода и количестве сущностей будет огромная. Работать со 100 однотипными наследниками весьма некомфортно.
Ф>если единственная цель — чистота концепции, то это не мой путь.
Единственная цель дизайна — сделать написание и поддержание кода максимально дешевым. Все остальное от лукавого.
Ф>главное — простота и понятность решения, лёгкость сопровождения.
Ты считаешь, что 100 классов легче сопровождать, чем один?
AVK>>Если абсолютно одинаковые функции применялись не в одном месте (хотя при сотне вариантов такое, видимо, было редко) Ф>не понял
Что не понял? Если у тебя каждый из сотни классов инстанцируется ровно в одном месте (или в нескольких, но рядом), то проще передавать функции по месту. Если нет — вынести вызов конструктора в статический метод. Архитектура, в которой сотня однотипных классов, каждый из которых создается в разных местах, это вообще что то трудновообразимое. И уж точно это не про "простота и понятность решения, лёгкость сопровождения".
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 7 6.1.7601.65536>>
Здравствуйте, maxkar, Вы писали:
M>Здравствуйте, AndrewVK, Вы писали:
AVK>>Мое предложение — множественное наследование интерфейсов и отсутствие наследования реализаций. Т.е. классы вообще не наследуются. А для реюза кода придумать способ упрощения агрегации.
M>Я тоже за эту идею. И хочу еще одну штуку, тесно связанную с отсутствием наследования реализаций. Я хочу избавиться от new ... и "конструкторов" как таковых. Методы "создания" объектов — это "обычные" с точки зрения всего остального кода статические методы.
Ты будешь смеяться, но конструкторов у нас нет с самого начала...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>Предлагаю пообсуждать, какой механизм множественного наследования мог бы быть полезен, и одновременно — прост для изучения.
Наследование интерфейсов + синтаксический сахар для упрощенного делегирования.
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, LaptevVV, Вы писали:
LVV>>Механизм C# и Java — не хотим вводить отдельную конструкцию интерфейса.
AVK>А зря.
AVK>Мое предложение — множественное наследование интерфейсов и отсутствие наследования реализаций. Т.е. классы вообще не наследуются. А для реюза кода придумать способ упрощения агрегации.
Кстати, ровно тот же подход предлагали оберонцы! Наследование интерфейсов, а вместо наследования реализации использовать композицию...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, Философ, Вы писали:
Ф>Наследование реализации — вещь полезная, т.к. позволяет экономить усилия.
Наследование реализации — зло. В одном из проектов было примерно 8 уровней наследования, причем довольно часто оно применялось только за тем, что в наследуемом классе был подходящий метод. И делали такое не студенты, а вполне взрослые дядьки. В таком коде, если где-то обнаруживается бага, починить ее стоит совершенно адских усилий.
Ф>Это один из способов не копипастить.
Здравствуйте, Философ, Вы писали:
Ф>Наследование реализации — вещь полезная, т.к. позволяет экономить усилия.
У нее слишком много побочных эффектов и она идет в одном флаконе с наследованием интерфейсов.
Ф>Однажды я сначала руками написал около сотни практически не отличающихся наследников (все они определяли ровно два небольших метода, объявленных в базовом классе как абстрактные), а потом, когда надоело писать руками, написал для них генератор. Ф>Сейчас даже не представляю, как всё это могло бы быть сделано без возможности наследовать реализацию.
Ну это ты не представляешь. А вообще можно, к примеру, было передать единственному классу в конструктор 1-2 функции. Если абсолютно одинаковые функции применялись не в одном месте (хотя при сотне вариантов такое, видимо, было редко) — конструктор вынести в статический метод-фабрику.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 7 6.1.7601.65536>>
Q>Является ли такое наследование реализации злом, и что предлагается вместо него?
Походу, однозначным злом не является. Но это стерильный случай. На практике возможность наследования реализации часто приводит к менее безобидным конструкциям.
Предлагается, на сколько я понимаю, выделить интерфейс сущности, которая делает Q> abstract void PerformSubstep1(); Q> abstract void PerformSubstep2();
и уже ее реализации подсовывать.
V>>Это смотря как на эвенты подписываться. А то ведь можно и через EventDescriptor. AVK>Ну то есть ты понимаешь, что обсервер обсерверу рознь. А между наследованием и агрегацией, пусть и с маппингом агрегируемого объекта на внешний интерфейс, разница существенно больше.
Я вижу разницу только в случае абстрактного интерфейса агрегируемого объекта. Но это, в некотором роде опасная штука дизайна сама по себе... в том смысле, что это ведь основа многих структурных паттернов и возможные эффекты от такого дизайна могут быть неожиданными для разработчика, если применение сих паттернов было "случайным". ИМХО, любое DI должно быть элементом сознательного решения некоего сценария, а не навязанным сверху ограничением.
=================
Это я насчет того, что куча неких локальных типов с сильной связанностью для некоей локальной задачи по мне ни разу не зло. Связанность по-прежнему может регулироваться самим дизайном основных объектов и сценариев их взаимного использования в технике уже имеющегося мейнстримового ООП. Проблемы нет, ведь требуемое кол-во архитекторов по-прежнему резко меньше требуемого кол-ва програмистов "бизнес-логики". ИМХО, где у разработчика ОО-иерархии возникнут проблемы в текущей мейнстримовой технике, они так же возникнут в любой технике. Мои некоторые наблюдения из разработки на VB тому пример — половина программистов на VB даже толком не понимало, как работает низлежащий COM/OLE, но это им ничуть не мешало быть достаточно полезными для типовых бизнес-задач. А всё потому, что на VB нельзя разработать COM-интерфейс. Эти интерфейсы разрабатывались гораздо более грамотными людьми на COM-IDL и давались "сверху" как каркас. То бишь, ограничивать надо не грамотных, а безграмотных.
Здравствуйте, Qbit86, Вы писали:
Q>Двойная диспетчеризация делается двумерной табличкой функций (хэш-мэп, отображающий пару типов на операцию над ними).
В случае агрегирования возникают ньюансы и комбинаторные соцетания сценариев.
Ты, похоже, упустил, что в случае обычного наследования мы имеем отношение потомок:база как 1:1, а в случае агрегирования не по значению, а по ссылке — 1:oo.
Возникает вопрос: и что тогда считать типом "узла"? Правильно, это уже будет зависеть от сценария. Самый любопытный сценарий тот, когда тип узла определяется типом агрегирумого объекта (развернув иерархию ты увидишь классический "мост" в этом сценарии), но агрегированный объект ничего у нас не знает о посетителе, ведь тот находится на другой стороне "моста". Именно этот сценарий ради смеха я предложил расписать, как он будет выглядеть на визиторе. Расписать этот сценарий несложно, но он покажет всё убожество техники агрегирования, если применять эту технику "просто лишь бы было". Агрегирование по ссылке и так слишком часто используется в структурных паттернах, по-сути являясь их фундаментом... Но эти паттерны обычно решают какую-то полезную задачу, то бишь оправданы. Использовать же агрегацию без того, чтобы эта техника решала некий полезный момент дизайна будет источником дополнительных проблем, как и любой другой оверкилл... на мой взгляд это будет несерьезный подход к проектированию.
Здравствуйте, maxkar, Вы писали:
M>Сама идея вполне естественно возникает в тех языках, где функция — объект первого класса. Периодически во всякие трансформаторы (вроде map и т.п.) хочется скормить конструктор объекта. А нельзя .
Добро пожаловать на светлую сторону Nemerle
using System.Console;
using Nemerle.Collections;
class A
{
public this(i : int) { WriteLine(i); }
}
def aObjects = [1,2,3,4].Map(A);
Раз уж речь зашла о нашей разработке, то обращаюсь к сообществу за советом.
В языке еще не реализовано наследование.
Если с одиночным наследованием все ясно — практически во всех языках одно реализовано одинаково на основе принципа подстановки и виртуальных функциях.
А вот множественное наследование еще не устоялось.
В качестве примера: С++, C# и Java, Компонентный паскаль, Эйфель.
Про первые три все знают. В КП множественное наследие просто запрещено.
В Эйфеле интересное решение: есть механизм переименования наследуемых имен в классе-наследнике.
Механизм С++ нам не нравится понятно почему.
Механизм C# и Java — не хотим вводить отдельную конструкцию интерфейса.
Механизм Эйфеля — уж больно нестандартный.
Компонентный паскаль — радикальное решение.
Предлагаю пообсуждать, какой механизм множественного наследования мог бы быть полезен, и одновременно — прост для изучения.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, Qbit86, Вы писали:
Q>Здравствуйте, LaptevVV, Вы писали:
LVV>>Механизм C# и Java — не хотим вводить отдельную конструкцию интерфейса.
Q>Можно ввести отдельную конструкцию для другой разновидности наследования: CZ: Multiple Inheritance Without Diamonds
Спасибо! Очень интересно!
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>Раз уж речь зашла о нашей разработке, то обращаюсь к сообществу за советом.
Я что-то пропустил — о какой разработке речь?
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, LaptevVV, Вы писали:
LVV>>Механизм C# и Java — не хотим вводить отдельную конструкцию интерфейса.
AVK>А зря.
AVK>Мое предложение — множественное наследование интерфейсов и отсутствие наследования реализаций. Т.е. классы вообще не наследуются. А для реюза кода придумать способ упрощения агрегации.
Вот и я о том же — исследовать придется разные механизмы на уровне реализации и на уровне применения.
Про интерфейсы мы думали. Но о том, чтобы совсем от наследования классов отказаться — в голову не пришло.
Радикально как-то выглядит — во всех языках одиночное наследование классов есть и реализовано практически одинаково.
За предложение — спасибо! Поанализируем.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
LVV>Предлагаю пообсуждать, какой механизм множественного наследования мог бы быть полезен, и одновременно — прост для изучения.
А у меня встречный вопрос. Что вас денуло новый язык ваять? В Обероне найден фатальный недостаток?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, LaptevVV, Вы писали:
LVV>>Предлагаю пообсуждать, какой механизм множественного наследования мог бы быть полезен, и одновременно — прост для изучения.
VD>А у меня встречный вопрос. Что вас денуло новый язык ваять? В Обероне найден фатальный недостаток?
1. Раз уж среду делаем — так сразу все и сделать. Язык в том числе...
2. Хотелось разнообразного синтаксиса в рамках одной среды. Мы одной кнопочкой переключаем русский-английский (русский — важно для девственников-программистов), и синтаксис языка. Хочешь — обероноподобный, а хочешь — сиобразный...
3. Хотелось прояснить для себя некоторые вопросы — вот в частности с наследованием. Кроме того, пацан в процессе реализации интерпретатора наткнулся на интересную штуку: оказалось, ссылочную семантику и семантику значений можно реализовать в рамках одного язвка. И появилась возможность одной кнопочкой переключать это дело. Более того, если сделать API, то можно и в процессе интерпретации переключать эту семантику.
4. Просто интересно — пацану, в первую очередь.
5. Была мысля сделать в языке один контейнер вместо множества разных. Но пока отложили — вернемся в том году после опытной эксплуатации.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, Философ, Вы писали:
Ф>>отсутствие наследования реализации в множественном наследовании?
AVK>Вообще отсутствие наследования реализации. Либо, в крайнем случае, отсутствие совмещенного в одном механизме наследования интерфейсов и реализаций.
Наследование реализации — вещь полезная, т.к. позволяет экономить усилия.
Это один из способов не копипастить.
Кстати, шаблонный метод — зло или нет?
Однажды я сначала руками написал около сотни практически не отличающихся наследников (все они определяли ровно два небольших метода, объявленных в базовом классе как абстрактные), а потом, когда надоело писать руками, написал для них генератор.
Сейчас даже не представляю, как всё это могло бы быть сделано без возможности наследовать реализацию.
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, Философ, Вы писали:
Ф>Кстати, шаблонный метод — зло или нет?
Присоединяюсь к вопросу про шаблонный метод. (Сам хотел спросить, но поленился.)
Пользуясь случаем, хочу задать вопрос по книжке Саттера—Александреску «Стандарты программирования на С++». Есть там два правила. (Свой экземпляр остался на работе, так что точных номеров не скажу.) Одно предписывает предпочитать абстрактные классы в API, другое — использовать идиому NVI. Нет ли здесь противоречия?
Здравствуйте, Privalov, Вы писали:
P>Наследование реализации — зло. P>Наследование реализации ничем не лучше копипасты.
Пример паттерна шаблонный метод на псевдокоде.
// Базовый библиотечный класс.class Base
{
public:
void PerformFixedAlgorithmWithOverridableSubsteps()
{
CheckSomePreconditions();
if (condition1)
PerformSubstep1();
while (condition2)
PerformSubstep2();
}
private:
// Эти методы вызываются, но не определяются в базовом классе.
abstract void PerformSubstep1();
abstract void PerformSubstep2();
}
// Производный пользовательский класс.class Derived
{
private:
// Эти методы определяются, но не вызываются в производном классе.
override void PerformSubstep1() { ... }
override void PerformSubstep2() { ... }
}
// Использование производного пользовательского класса.
Derived d;
d.PerformFixedAlgorithmWithOverridableSubsteps();
Является ли такое наследование реализации злом, и что предлагается вместо него?
Здравствуйте, Privalov, Вы писали:
Ф>>Наследование реализации — вещь полезная, т.к. позволяет экономить усилия. P>Наследование реализации — зло. В одном из проектов было примерно 8 уровней наследования, причем довольно часто оно применялось только за тем, что в наследуемом классе был подходящий метод. [...]
Только не надо ставить всё с ног на голову. В восьмиярусном наследовании, сделанном абы как, сиречь: "только за тем, что в наследуемом классе был подходящий метод" главное зло — не наследование, а это самое "только за тем ...".
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!
Здравствуйте, AndrewVK, Вы писали:
AVK>Ну это ты не представляешь. А вообще можно, к примеру, было передать единственному классу в конструктор 1-2 функции. Если абсолютно одинаковые функции применялись не в одном месте (хотя при сотне вариантов такое, видимо, было редко) — конструктор вынести в статический метод-фабрику.
Это по сути ручная реализация таблицы виртуальных функций. Эдак вообще никакого наследования или динамического полиморфизма via vtable не надо — знай себе передавай пачки функций в ктор, примерно как делали наши пращуры на Си.
Здравствуйте, Qbit86, Вы писали:
Q>Это по сути ручная реализация таблицы виртуальных функций.
С чего это она ручная? Указатель на функцию ничуть не менее виртуален, нежели виртуальная функция. Если писать меньше надо — какой смысл тащить туда наследование?
Q> Эдак вообще никакого наследования или динамического полиморфизма via vtable не надо — знай себе передавай пачки функций в ктор, примерно как делали наши пращуры на Си.
Или как делают в некоторых функциональных языках сейчас. Наследование интерфейсов нужно далеко не только для того, чтобы сгруппировать пачку функций. Наследование интерфейсов это прежде всего каркас дизайна. И оно вполне допустимо для обрисовывания структуры решения. А вот сотня наследников, отличающихся на 1-2 маленьких функции это крайне фиговый дизайн.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 7 6.1.7601.65536>>
> Y>На практике возможность наследования реализации часто приводит к менее безобидным конструкциям. > > Вы наверное хотели написать "В моей практике" ? > > не надо переносить свой опыт на остальных.
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, Философ, Вы писали:
Ф>>Наследование реализации — вещь полезная, т.к. позволяет экономить усилия.
AVK>Ну это ты не представляешь. А вообще можно, к примеру, было передать единственному классу в конструктор 1-2 функции.
нафига это городить?
если единственная цель — чистота концепции, то это не мой путь.
главное — простота и понятность решения, лёгкость сопровождения.
AVK>Если абсолютно одинаковые функции применялись не в одном месте (хотя при сотне вариантов такое, видимо, было редко)
не понял
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, Геннадий Васильев, Вы писали:
VD>>>В Обероне найден фатальный недостаток? Т>>Полагаю классический случай NIH
ГВ>Кончайте брюзжать, в вузах как раз и полагается делать всё самим, а не гундосить: "Всё уже придумано, надо только пользоваться". Это же здорово — сделать всё самому!
Ну я как бы большой любитель сделать всё сам vs разгребать чужие говны навороты, но тут мне сама цель не понятна. Как обучающий проект с возможным практическим применением — да, но как сразу практический проект — "ну нипанятна же!" (С)
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, Философ, Вы писали:
AVK>>>Ну это ты не представляешь. А вообще можно, к примеру, было передать единственному классу в конструктор 1-2 функции. Ф>>нафига это городить?
AVK>Городить? Я тебе предлагаю вместо сотни классов сделать один — экономия в размере кода и количестве сущностей будет огромная. Работать со 100 однотипными наследниками весьма некомфортно.
а с одним(?) чудовищного размера классом просто?
Ф>>если единственная цель — чистота концепции, то это не мой путь.
AVK>Ты считаешь, что 100 классов легче сопровождать, чем один?
пофигу, хоть 100, хоть 1000 — вряд-ли кому придёт в голову править генерируемый код.
а потом, да: если в классе будет 1,0E+03 полей и столько же свойств, то это значительно хуже чем 1,0E+02 классов.
AVK>>>Если абсолютно одинаковые функции применялись не в одном месте (хотя при сотне вариантов такое, видимо, было редко) Ф>>не понял
AVK>Что не понял? Если у тебя каждый из сотни классов инстанцируется ровно в одном месте (или в нескольких, но рядом), то проще передавать функции по месту. Если нет — вынести вызов конструктора в статический метод.
наиболее интересный вопрос здесь: где эти самые "функции" будут находиться, откуда их брать, чтоб в конструктор их передавать?
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, Философ, Вы писали:
Ф>а с одним(?) чудовищного размера классом просто?
А зачем один чудовищного размера? Я такого не предлагал.
AVK>>Ты считаешь, что 100 классов легче сопровождать, чем один? Ф>пофигу, хоть 100, хоть 1000 — вряд-ли кому придёт в голову править генерируемый код.
Знаешь, если код можно сгенерировать, то очень часто его можно вообще не генерировать.
Ф>наиболее интересный вопрос здесь: где эти самые "функции" будут находиться, откуда их брать, чтоб в конструктор их передавать?
Я не могу ответить тебе на этот вопрос, потому что не знаю подробностей. Ты спросил, чем заменить наследование, я тебе ответил. А в контексте генерации кода вообще можно без какого либо наследования обходится — дизайн и читабельность генеренного кода не особо важны, если это не публичные контракты.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 7 6.1.7601.65536>>
Здравствуйте, LaptevVV, Вы писали:
AVK>>Мое предложение — множественное наследование интерфейсов и отсутствие наследования реализаций. Т.е. классы вообще не наследуются. А для реюза кода придумать способ упрощения агрегации. LVV>Кстати, ровно тот же подход предлагали оберонцы! Наследование интерфейсов, а вместо наследования реализации использовать композицию...
Плюс они предлагали функциональный тип-делегаты, чтобы оно хоть как-то взлетело.
Здравствуйте, LaptevVV, Вы писали:
LVV>3. Хотелось прояснить для себя некоторые вопросы — вот в частности с наследованием. Кроме того, пацан в процессе реализации интерпретатора наткнулся на интересную штуку: оказалось, ссылочную семантику и семантику значений можно реализовать в рамках одного язвка. И появилась возможность одной кнопочкой переключать это дело. Более того, если сделать API, то можно и в процессе интерпретации переключать эту семантику.
Чего? Это же радикально меняет поведение кода.
Единственный случай, когда такое преобразование не скажется на поведении это когда объекты, которым переключают семантику неизменяемые.
Если же они изменяемый то это
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, AndrewVK, Вы писали:
V>>Любое автоматическое делегирование методов агрегату будет делать ровно то же самое. AVK>Не обязательно. Для примера — как думаешь, что порождает большую связность — встроенные в CLR эвенты или IObservable из 4+ фреймворка?
Это смотря как на эвенты подписываться. А то ведь можно и через EventDescriptor.
Здравствуйте, vdimas, Вы писали:
AVK>>Не обязательно. Для примера — как думаешь, что порождает большую связность — встроенные в CLR эвенты или IObservable из 4+ фреймворка?
V>Это смотря как на эвенты подписываться. А то ведь можно и через EventDescriptor.
Ну то есть ты понимаешь, что обсервер обсерверу рознь. А между наследованием и агрегацией, пусть и с маппингом агрегируемого объекта на внешний интерфейс, разница существенно больше.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 7 6.1.7601.65536>>
Здравствуйте, vdimas, Вы писали:
V>Я вижу разницу только в случае абстрактного интерфейса агрегируемого объекта.
Агрегирование и делается исключительно через публичный контракт агрегируемого объекта. Иначе это не агрегация, а профанация.
V> Но это, в некотором роде опасная штука дизайна сама по себе... в том смысле, что это ведь основа многих структурных паттернов и возможные эффекты от такого дизайна могут быть неожиданными для разработчика, если применение сих паттернов было "случайным".
Смысла фразы не уловил.
V> ИМХО, любое DI должно быть элементом сознательного решения некоего сценария, а не навязанным сверху ограничением.
При чем тут DI?
V>Это я насчет того, что куча неких локальных типов с сильной связанностью для некоей локальной задачи по мне ни разу не зло.
Наследование мейнстримное нелокально в принципе. И, за исключением приватного наследования С++, фигурирует в публичном контракте наследника в обязательном порядке.
Хуже того, сам механизм наследования оправдан в случае довольно крупных кусков архитектуры, для мелких он слишком много требует синтаксических танцев. В результате на практике мы наблюдаем прежде всего попытки построения дизайна приложения, а не решение локальных задач.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 7 6.1.7601.65536>>
Здравствуйте, WolfHound, Вы писали:
WH>Здравствуйте, LaptevVV, Вы писали:
LVV>>3. Хотелось прояснить для себя некоторые вопросы — вот в частности с наследованием. Кроме того, пацан в процессе реализации интерпретатора наткнулся на интересную штуку: оказалось, ссылочную семантику и семантику значений можно реализовать в рамках одного язвка. И появилась возможность одной кнопочкой переключать это дело. Более того, если сделать API, то можно и в процессе интерпретации переключать эту семантику. WH>Чего? Это же радикально меняет поведение кода.
Естественно!!!!
Дык для того и делалось, чтобы учни могли попробовать в учебной среде и обнаружить существенную разницу...
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, LaptevVV, Вы писали:
WH>>Чего? Это же радикально меняет поведение кода. LVV>Естественно!!!! LVV>Дык для того и делалось, чтобы учни могли попробовать в учебной среде и обнаружить существенную разницу...
Честно говоря воспитательный эффект весьма сомнительный.
Не понимаю, что может понять ученик глядя на то, как отлаженная программа начала глючить.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
WH>Здравствуйте, LaptevVV, Вы писали:
WH>>>Чего? Это же радикально меняет поведение кода. LVV>>Естественно!!!! LVV>>Дык для того и делалось, чтобы учни могли попробовать в учебной среде и обнаружить существенную разницу... WH>Честно говоря воспитательный эффект весьма сомнительный. WH>Не понимаю, что может понять ученик глядя на то, как отлаженная программа начала глючить.
Ну дык а препод-то на что?
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, AndrewVK, Вы писали:
V>>Я вижу разницу только в случае абстрактного интерфейса агрегируемого объекта. AVK>Агрегирование и делается исключительно через публичный контракт агрегируемого объекта. Иначе это не агрегация, а профанация.
Если этот контракт виртуальный/абстрактный, то контроль за происходящим в конкретном месте уже не тот.
V>> Но это, в некотором роде опасная штука дизайна сама по себе... в том смысле, что это ведь основа многих структурных паттернов и возможные эффекты от такого дизайна могут быть неожиданными для разработчика, если применение сих паттернов было "случайным". AVK>Смысла фразы не уловил.
Слишком много степеней свободы появляется... ИМХО, типобезопасный абстрактный интерфейс — это контракт только наполовину, только в плане сигнатур методов, где обещание насчет соблюдение семантики контракта сугубо вербальное и никак иначе.
V>> ИМХО, любое DI должно быть элементом сознательного решения некоего сценария, а не навязанным сверху ограничением. AVK>При чем тут DI?
Если агрегируемый объект абстрактен, то это смахивает на классическое DI после IoC.
V>>Это я насчет того, что куча неких локальных типов с сильной связанностью для некоей локальной задачи по мне ни разу не зло. AVK>Наследование мейнстримное нелокально в принципе. И, за исключением приватного наследования С++, фигурирует в публичном контракте наследника в обязательном порядке.
Да вроде не мешает, есть же классический паттерн "Мост", который обыгрывает разницу м/у внутренней иерархией и публичной.
AVK>Хуже того, сам механизм наследования оправдан в случае довольно крупных кусков архитектуры, для мелких он слишком много требует синтаксических танцев. В результате на практике мы наблюдаем прежде всего попытки построения дизайна приложения, а не решение локальных задач.
ИМХО, всё-равно это от квалификации разработчика зависит. Для некоторого уровня разработчика сам факт более-менее надежного решения прикладной задачи уже неплох. Более опытный коллега может сделать затем ревью и рефакторинг. Так вот, первого разработчика обсуждаемое не спасет, а второму банально не нужно.
Здравствуйте, vdimas, Вы писали:
AVK>>Агрегирование и делается исключительно через публичный контракт агрегируемого объекта. Иначе это не агрегация, а профанация.
V>Если этот контракт виртуальный/абстрактный, то контроль за происходящим в конкретном месте уже не тот.
Ничего не понял
AVK>>Смысла фразы не уловил.
V>Слишком много степеней свободы появляется... ИМХО, типобезопасный абстрактный интерфейс — это контракт только наполовину, только в плане сигнатур методов, где обещание насчет соблюдение семантики контракта сугубо вербальное и никак иначе.
Вопрос сложности декларации контрактов — совершенно отдельный. При чем тут проблемы мейнстримного наследования?
V>>> ИМХО, любое DI должно быть элементом сознательного решения некоего сценария, а не навязанным сверху ограничением. AVK>>При чем тут DI?
V>Если агрегируемый объект абстрактен, то это смахивает на классическое DI после IoC.
Классический IoC никакой агрегации не предполагает и опысывает внешние связи разнородных сущностей.
V>ИМХО, всё-равно это от квалификации разработчика зависит.
Чем меньше возможности сделать бяку, тем лучше, вне зависимости от квалификации разработчика.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 7 6.1.7601.65536>>
Здравствуйте, AndrewVK, Вы писали:
V>>Если этот контракт виртуальный/абстрактный, то контроль за происходящим в конкретном месте уже не тот. AVK>Ничего не понял
А что тут непонятного? Например, подменить базовый класс в обычном наследовании никак, а в агрегировании можно подменить чем угодно. Хуже того — еще и в рантайм.
V>>Если агрегируемый объект абстрактен, то это смахивает на классическое DI после IoC. AVK>Классический IoC никакой агрегации не предполагает и опысывает внешние связи разнородных сущностей.
Агрегация по ссылке недалеко ушла от обычной связи разнородных сущеностей. А агрегация по значению имеет ровно те же эффекты, что обычное наследование (о чем и сказал тут: http://www.rsdn.ru/forum/philosophy/4877921.aspx
). Очевидно, что возражения на тот пост уместны только если иметь ввиду агрегацию по ссылке и твой пример насчет IObservable тому подтверждение.
V>>ИМХО, всё-равно это от квалификации разработчика зависит. AVK>Чем меньше возможности сделать бяку, тем лучше, вне зависимости от квалификации разработчика.
Дык, не спорю, главное, чтобы не в ущерб. ИМХО, если без особого фанатизма, то локальные иерархии объектов очень даже удобны. Просты, лаконичны, беспроблемны. Насчет "спрятать от остального кода" — пользуемся паттерном "Мост" и всех делов. Обычно за небольшой публичной иерархией моста может прятаться приличная непубличная, что решает обсуждаемые здесь вопросы. К тому же, полный контроль дотнета над переопределением методов интерейсов я бы сказал провоцирует на такой стиль.
Здравствуйте, vdimas, Вы писали:
V>Обычное делегирование, которое могло бы быть выполнено с помощью синтаксического сахара компилятора — уппсс, уже не работает, как только нам нужно от агрегированного объекта получать хоть какой-то callback, как в двойной диспетчеризации...
Двойная диспетчеризация делается двумерной табличкой функций (хэш-мэп, отображающий пару типов на операцию над ними).
Здравствуйте, Privalov, Вы писали:
Ф>>Наследование реализации — вещь полезная, т.к. позволяет экономить усилия. P>Наследование реализации — зло. В одном из проектов было примерно 8 уровней наследования, причем довольно часто оно применялось только за тем, что в наследуемом классе был подходящий метод. И делали такое не студенты, а вполне взрослые дядьки. В таком коде, если где-то обнаруживается бага, починить ее стоит совершенно адских усилий.
Здравствуйте, B0FEE664, Вы писали:
P>>В таком коде, если где-то обнаруживается бага, починить ее стоит совершенно адских усилий.
BFE>Почему?
Потому что при таком использовании наследования реализации, какое было в упомянутом проекте, если для какой-то иерархии классов где-то в середине починить проблемный для данной иерархии метод, то почти наверняка этот же метод свалит что-нибудь в другой иерархии. Тогда начинаются копипасты и все связанные с ними удовольствия. Особенно, когда сроки поджимают.
[offtopic]
Главное, все знают, "так нельзя". Но все равно делают. Говорят "потом напишем, как надо", хотя все знают, что никто и никогда не напишет.
Однажды один начальник произнес: "Главное — укладываться в сроки. Качество кода — в печку".
[/offtopic]
Здравствуйте, Privalov, Вы писали:
P>>>В таком коде, если где-то обнаруживается бага, починить ее стоит совершенно адских усилий. BFE>>Почему? P>Потому что при таком использовании наследования реализации, какое было в упомянутом проекте, если для какой-то иерархии классов где-то в середине починить проблемный для данной иерархии метод, то почти наверняка этот же метод свалит что-нибудь в другой иерархии. Тогда начинаются копипасты и все связанные с ними удовольствия. Особенно, когда сроки поджимают.
Звучит странно. Это звучит так, как будто реализация виртуальной функции "знает" о классах-наследниках. Скорее всего это значит, что код функции реализован не на своём уровне иерархии.
Если ты имел ввиду "двухтактность" происходящего, когда сначала выделяется память, а потом эта память инициализируется, то оно ничуть тебе не мешает и не может. Ты же в аргументе new указываешь конкретный тип, т.е. пофиг, сколько у него баз, вся механика скрыта от тебя.
Т.ё, всё что ты писал, к наследованию никаким боком, оно может быть просто поддержано языком.
Насчет перегрузки сигнатур конструкторов — аналогично. А что ты делаешь когда речь о пререгрузке сигнатур обычных ф-ий? Наверно, приводишь к нужной сигнатуре и компилятор находит подходящую сигнатуру, так?
=================
Есть, правда, небольшое рациональное зерно в том, чтобы иметь разные конструкторы с одинаковыми типами параметров. ))
Такая потребность возникает если эти одинаковые типы несут разную семантику. В этом случае как раз либо вместо публичных конструкторов делают статические методы, либо создают типы-алиасы-обертки, чтобы отделить внести разницу семантики в разницу типов. Классика — замена int на enum.
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, maxkar, Вы писали:
V>Если ты имел ввиду "двухтактность" происходящего, когда сначала выделяется память, а потом эта память инициализируется, то оно ничуть тебе не мешает и не может. Ты же в аргументе new указываешь конкретный тип, т.е. пофиг, сколько у него баз, вся механика скрыта от тебя.
Как раз таки может смущать и смущает. Мне не нравится, что на уровне пользователя класса различаются "конструктор" и "статический метод". Причем не только на уровне кода, а даже на уровне рантайма. Можно посмотреть на Newobj в msil, аналоги в java (там вообще два вызова — на выделение памяти и на вызов конструктора). Вроде бы и в C++ генерация кода различается для двух случаев. Еще можно посмотреть на reflection API, которые различают конструктор и остальные методы. Это все безобразие именно из-за той двухфазности с выделением памяти. Все "конструкторы" (в классическом смысле) — это на самом деле инициализаторы (устанавливают переменные и т.п.). А вот "создателя" из них делает конструкция "new ...".
Вполне естественно напрашивается вопрос: а зачем клиенту класса видеть такое разделение? Чем (с точки зрения клиента класса) отличается "конструктор" от "метода, который возвращает экземпляр класса"? Разница там небольшая: конструктор гарантирует новое identity объекта, обычный метод — нет. Учитывая, что для identity все равно нет нормальной поддержки (в контрактах методов/верификаторах/системе типов), особой роли это не играет.
Следующий вопрос. Если особой разницы для клиента нет, почему же эта разница все равно есть в языке? Правильно, в силу все той же двухфазности. Если мы хотим поддержать "method-like constructors", нужно вводить (на уровне генерации кода) еще и инициализаторы (которые память не выделяют). Фактически, вся компиляция будет генерировать "пару" методов. Один — с выделением памяти (и вызовом единственного инициализатора данного класса). Второй — чистый инициализатор (с вызовом родительских или других своих инициализаторов). При убирании наследования упрощается вторая часть. Там возможен только вызов своих инициализаторов. При этом даже ограничения по доступу (public/private) и т.п. проверять не нужно.
В общем, я хочу, чтобы в большей части языка не было лишней сущности под названием "конструктор". Где-то в деталях реализации отдельных методов все равно нужно иметь какое-то отличие от обычных методов, там будет выделяться память. Но это именно детали реализации, не присутствующие во внешнем контракте класса.
V>Т.ё, всё что ты писал, к наследованию никаким боком, оно может быть просто поддержано языком.
Без наследования оно проще в реализации внутренностей класса получается. А так — да, можно синтетику нагенериовать. V>Насчет перегрузки сигнатур конструкторов — аналогично. А что ты делаешь когда речь о пререгрузке сигнатур обычных ф-ий? Наверно, приводишь к нужной сигнатуре и компилятор находит подходящую сигнатуру, так?
А в "основном" языке у меня перегрузки методов вообще нет, зато есть аргументы по умолчанию и varargs. Поэтому нужно делать методы c разными именами. Зато очень легко работать с методами как с first class function — не нужно сигнатуры типов указывать, да и reflection заметно проще становится.
V>================= V>Есть, правда, небольшое рациональное зерно в том, чтобы иметь разные конструкторы с одинаковыми типами параметров. )) V>Такая потребность возникает если эти одинаковые типы несут разную семантику. В этом случае как раз либо вместо публичных конструкторов делают статические методы, либо создают типы-алиасы-обертки, чтобы отделить внести разницу семантики в разницу типов. Классика — замена int на enum.
И то, и другое — костыли. Ну вот почему какой-то из вариантов сделан конструктором, а какой-то — статическим методом? Который сделан методом чем-то хуже что-ли? И на уровне языка я один создаю через "new" а второй через вызов методов.
Что касается вторых костылей (алиасы). Мне такой подход категорически не нравится. Он, конечно, работает в mainstream, но там просто нет альтернатив. Это все — попытка натянуть все возможные ограничения на одну систему типов. В простейшем случае нужно отличать "типы" (те самые "int" и т.п.) и "роли". Роли — те самые обертки, чтобы значение ушло не туда. Соответственно, на роли накладываются свои ограничения вроде "сумма двух длин — длина". Более правильный пример для ролей — различные "кэши" приложения. "Кэш" — общий интерфейс. Система ролей же отличает "кэш товаров" от "кэша продавцов", например (это важно при dependency injection). При этом API двух кэшей полностью одинаковое. Аналогично можно разделять "роли" функций (я о first-class function, при этом функции могут быть с одной сигнатурой и разными ролями). В более сложном случае нужно в одной и той же программе иметь несколько различных систем типов. Одна — ограничения "языка", среды выполнения. Вторая — ограничения бизнес-логики. Третья — более продвинутая система ограничений, которая не имеет поддержки в runtime (например, какое-то подмножество зависимых типов). Все эти "языки" отличаются только понятием типа (т.е. в грамматике для type разные правила и правила комбинации типов разные), все остальное AST — одинаковое. У меня, кстати, есть сценарий для "первой и третьей" системы типов, когда я хотел бы в большей части проекта использовать более строгую типизацию и только в некоторых местах использовать более слабую типизацию — не все выражается в "более строгой" (+ хуки для более сильной типизации, чтобы переход между двумя системами работал нормально). Если интересно — расскажу, но это лучше в отдельную ветку, так как к наследованию и консрукторам это уже отношения не имеет.
Здравствуйте, maxkar, Вы писали:
M>В общем, я хочу, чтобы в большей части языка не было лишней сущности под названием "конструктор".
Ок, ответь тогда на несколько вопросов. Для примера возьмем CLR — там есть возможность создания объекта без вызова конструктора и нет никаких заморочек с выделением памяти. Итак:
1) Как обеспечить отсутствие возможности легко получить ссылку на неинициализированный объект?
2) Как гарантировать вызов конструкторов базовых классов, причем в нужном порядке?
3) Как обеспечить нормальную работу модификатора readonly?
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 7 6.1.7601.65536>>
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, maxkar, Вы писали:
M>>В общем, я хочу, чтобы в большей части языка не было лишней сущности под названием "конструктор".
AVK>Ок, ответь тогда на несколько вопросов. Для примера возьмем CLR — там есть возможность создания объекта без вызова конструктора и нет никаких заморочек с выделением памяти. Итак:
Хорошо. Только я не понял, в каком контексте вопросы. Так что если ответ получится совсем не в тему, значит нужно уточнить вопросы. Мне пока не ясно, что мы вообще делаем. Мы пишем компилятор нашего языка в CLR? Или мы пишем верификатор CLR-кода для нашего языка? Или мы делаем еще что-то? На всякий случай, я не утверждал, что будет хоть какая-нибудь совместимость с уже существующими библиотеками (в том числе и системной) и что вообще язык будет хорошо ложиться на какую-нибудь существующую систему (не утверждал и обратного).
Примерно как это будет выглядеть в языке я показывал здесь
, второй пример. В зависимости от желания контролировать байткод и типа наследования придется добавить от нуля до двух атрибутов. Атрибуты "ctor" и "initializer". Первый через reflection не виден. Второй, наверное, тоже не виден.
AVK>1) Как обеспечить отсутствие возможности легко получить ссылку на неинициализированный объект?
На каком уровне? На уровне языка в constructor так и будут все те ограничения, что есть в существующих классах. Клиент класса в языке объект "сам по себе" вообще никак создать не может (С# вроде бы тоже не позволит руками указать только ту инструкцию). На уровне байткода инструкцию создания объекта разрешить только внутри статических методов класса и ограничить аргументы только этим классом (и вообще, убрать у нее аргументы). В зависимости от уровня паранои можно позакручивать гайки — разрешить метод только у методов с атрибутом "ctor" или вообще автоматически на стек класть для методов с атрибутом "ctor" вновь созданный объект и инструкцию убрать (да, тогда точно понадобится атрибут). В байткоде верифицировать все то же самое, что и сейчас в конструкторах. Тоже зависит от уровня паранойи. Можно использование this запретить везде, кроме левых частей присваивания, запретить вызово методов и т.п. В общему, внутри "constructor'а" остаются все проверки существующего конструктора. А вот для вызывающего кода это обычный вызов метода (ему разницы нет, что там внутри себя метод делает).
Полностью я пока отказаться от понятия конструктора я не предлагал — не совсем понятно, как правильно выделять память (да и все остальные гарантии тоже, которые привязаны к процессу "создания" объекта).
AVK>2) Как гарантировать вызов конструкторов базовых классов, причем в нужном порядке?
А вот здесь как раз играет роль возможность/невозможность наследования реализации. Без наследования реализации проблемы просто нет. С наследованием придется на каждый "конструктор" плодить пару методов. Один — конструктор. Второй — инициализатор. Соответственно, конструктор создает объект и вызывает инициализатор. Инициализатор в свою очередь вызывает родительские инициализаторы. Соответственно, в язык придется добавлять конструкцию вызова родительского инициализатора. Вроде parent.initialize "someMethodName". Не очень хорошо получается, все таки приходится "светить" наследникам различие между "конструкторами" и "обычными методами" (у одного есть соответствующий инициализатор, у другого — нет).
За вопрос спасибо, все таки не зря я подозревал, что с наследованием не все так просто. Но меня вполне устроит отсутствие наследования реализации, я им не пользуюсь.
AVK>3) Как обеспечить нормальную работу модификатора readonly?
Точно так же, как и раньше это все обеспечивает сейчас обычный конструктор. Внутри метода "constructor" ведет себя так же, как и "обычный" конструктор — со всеми гараниями по полям и т.п. А вот с точки зрения клиента это обычный метод. Не важно, создал ли метод объект внутри себя (и инициализировал переменные) или вызывал другой метод для создания объекта. "Какой-то" constructor уже завершился и все гарантии работают.
Итого. На уровне "среды исполнения" модификатор "constructor" пока остается. Он нужен для выделения памяти и доп. гарантий. Но никто из клиентов этого класса не может осмысленно воспользоваться подобным знанием. Более того, "constructor" может стать затем обычным статическим методом с соответствующим типом возвращаемого значения, на работе клиентов это вообще не скажется. Возможно и обратное преобразование — из статического метода сделать "constructor".
P.S. Я .NET не знаю, знаю только Java. Так что если где-то пишу чушь, меня нужно поправить и описать что именно нужно чуть подробнее.
Здравствуйте, maxkar, Вы писали:
M>Хорошо. Только я не понял, в каком контексте вопросы.
Ты утверждаешь, что семантическая разница между конструктором заключается только в выделении памяти. Попробуй с этой позиции ответить на заданные вопросы. Они вроде как вполне понятные.
AVK>>1) Как обеспечить отсутствие возможности легко получить ссылку на неинициализированный объект? M>На каком уровне? На уровне языка
На нем.
M> в constructor так и будут все те ограничения, что есть в существующих классах
При чем тут ограничения? Сейчас проблемы неинициализированных объектов почти нет, потому что конструктор это очень специальный метод, и доступ ему дается только к самому себе. Это все равно приводит к проблемам, но не таким сильным как если бы неявный доступ к неинициализированному объекту был возможен из любого статического метода.
M>Клиент класса в языке объект "сам по себе" вообще никак создать не может
Это пока эту роль выполняют конструкторы.
M> (С# вроде бы тоже не позволит руками указать только ту инструкцию)
Все шарп позволяет. Только это не инструкция, а специальный метод специального объекта. Но сейчас такое сделать можно только специально. При отказе же от конструкторов нужно будет выставлять эту фичу в частое использование — кто то ведь, в итоге, экземпляр класса должен создать.
Но если тебе непременно приспичило — бери да пользуйся. Только что то я пока такого радикального неприятия конструкторов не встречал.
M>. На уровне байткода инструкцию создания объекта разрешить только внутри статических методов класса
Ну то есть проблема в твоем варианте никак не решается. Ответ понятен.
M>В зависимости от уровня паранои можно позакручивать гайки — разрешить метод только у методов с атрибутом "ctor" M>В байткоде верифицировать все то же самое, что и сейчас в конструкторах. M>Полностью я пока отказаться от понятия конструктора я не предлагал
О да, я именно такого ответа и ждал. Выкидываем из языка конструктор и добавляем способ разметки, чтобы сделать из некоторых статических методов ... да, конструктор. Ну и нафига все это? Чтобы можно было method group из конструктора сделать, и не писать примитивнейшей утилитки, нивелирующей разницу в рефлекшене между GetMethods и GetConstructors? Сильный замах, да.
AVK>>3) Как обеспечить нормальную работу модификатора readonly? M>Точно так же, как и раньше это все обеспечивает сейчас обычный конструктор.
Ну то есть мы все таки конструктор оставляем в неизменном практически виде, только теперь static надо будет дополнительно указать и имя придумать.
M>Итого. На уровне "среды исполнения" модификатор "constructor" пока остается.
Он у тебя пока остается на любом уровне.
M>P.S. Я .NET не знаю, знаю только Java.
В джаве вроде бы можно сделать конструктор приватным, не так ли? В таком случае все твои заявленные потребности эта фича покрывает с лихвой и без отказа от наследования реализации.
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 7 6.1.7601.65536>>
Здравствуйте, AndrewVK, Вы писали:
AVK>Ты утверждаешь, что семантическая разница между конструктором заключается только в выделении памяти. Попробуй с этой позиции ответить на заданные вопросы. Они вроде как вполне понятные.
Нужно посмотреть, как подобные вопросы решаются в Objective-C. «Обратите внимание, что метод(ы) init является обычным методом, ничем не выделяющимся среди остальных (в отличие от C++, где конструктор — это особый метод, у которого, например, нельзя взять адрес).» (Wikipedia)
Здравствуйте, AndrewVK, Вы писали:
AVK>Здравствуйте, maxkar, Вы писали:
M>> в constructor так и будут все те ограничения, что есть в существующих классах
AVK>При чем тут ограничения? Сейчас проблемы неинициализированных объектов почти нет, потому что конструктор это очень специальный метод, и доступ ему дается только к самому себе. Это все равно приводит к проблемам, но не таким сильным как если бы неявный доступ к неинициализированному объекту был возможен из любого статического метода.
В каком смысле "доступ дается только к самому себе"? В обычном конструкторе можно делать почти все то же, что и в других методах. И this из обычного конструктора элементарно можно можно куда угодно передать. В том числе и в статический метод. Тот "конструктор", который я предлагал, внутри инициализатора тоже работает как "обычный конструктор", а снаружи выглядит как "static method".
M>>Клиент класса в языке объект "сам по себе" вообще никак создать не может AVK>Это пока эту роль выполняют конструкторы.
И что? И дальше эту роль будут выполнять "конструкторы". Только вот клиент больше не знает, какой из используемых методов является "конструктором", а какой — обычным методом.
M>> (С# вроде бы тоже не позволит руками указать только ту инструкцию)
AVK>Все шарп позволяет. Только это не инструкция, а специальный метод специального объекта. Но сейчас такое сделать можно только специально. При отказе же от конструкторов нужно будет выставлять эту фичу в частое использование — кто то ведь, в итоге, экземпляр класса должен создать.
И в чем проблема? Как раз статический (чистый статический!) метод и создает экземпляр. И условия на то, откуда можно использовать такую инструкци, очень жесткие, вплоть до позиции инструкции в байткоде метода можно задать. У меня даже специально не получится создать объект, не вызвав предусмотренные классом инициализаторы. Совсем никак, любые попытки верификатор не пропустит. Вот если инициализаторы куда-то не туда уводят объект, то можно получить неинициализированный объект. Но и на обычных конструкторах я изображу все те же проблеимы. Во что именно дальше будет разворачиваться инициализатор — в вызов "instance method" инициализатора или будет продолжать внутри себя поля ставить — это уже проблемы кодогенерации и модели выполнения (те же гарантии на final поля и т.п.). AVK>Но если тебе непременно приспичило — бери да пользуйся. Только что то я пока такого радикального неприятия конструкторов не встречал.
M>>. На уровне байткода инструкцию создания объекта разрешить только внутри статических методов класса AVK>Ну то есть проблема в твоем варианте никак не решается. Ответ понятен.
Здесь все точно так же, как и с обычными "конструкторами". Обычный конструктор может передать неинициализированную ссылку. Мой тоже может передать (специально!). А вот "случайно" ссылки на неинициализированный объект у меня вообще не могут возникнуть. Они появляются при входе в "конструктор" в качестве объекта this (да, да, у моего "конструктора" внутри instance есть). Ну а дальше уже сам программист виноват...
M>>В зависимости от уровня паранои можно позакручивать гайки — разрешить метод только у методов с атрибутом "ctor" M>>В байткоде верифицировать все то же самое, что и сейчас в конструкторах. M>>Полностью я пока отказаться от понятия конструктора я не предлагал
AVK>О да, я именно такого ответа и ждал. Выкидываем из языка конструктор и добавляем способ разметки, чтобы сделать из некоторых статических методов ... да, конструктор. Ну и нафига все это? Чтобы можно было method group из конструктора сделать, и не писать примитивнейшей утилитки, нивелирующей разницу в рефлекшене между GetMethods и GetConstructors? Сильный замах, да.
Ну примерно для этого и делалось. Но только акцент в другую сторону. Чтобы из конструкторов сделать обычные (с точки зрения той же системы типов, например) методы. Method group меня не волнует. Меня волнуют лишние сущности. Вот зачем "вне" класса понятие его конструктора (при отсутствии наследования реализации)? Да не зачем, значит можно и выпилить его. Внутри класса нужен? Нужен. Значит там его и оставить. Инкапсуляция деталей реализации обычная.
AVK>>>3) Как обеспечить нормальную работу модификатора readonly? M>>Точно так же, как и раньше это все обеспечивает сейчас обычный конструктор.
AVK>Ну то есть мы все таки конструктор оставляем в неизменном практически виде, только теперь static надо будет дополнительно указать и имя придумать.
"Внутри" класса — да, оставляю. Вне класса (для всех его пользователей) конструкторов больше нет. Больше нельзя гарантированно получить новую identity (раньше это делал new ...). static писать не обязательно, он у всех "constructor" включен автоматически (так же, как public у методов интерфейсов). Даже имя можно оставить new в большинстве случаев для начала.
M>>Итого. На уровне "среды исполнения" модификатор "constructor" пока остается. AVK>Он у тебя пока остается на любом уровне.
Не на любом. При остуствтии наследовании реализации и компиляции во что-то низкоуровневное понятия "конструктор" на уровне среды нет. Есть метод, который выделяет память, инициализирует ее чем-то и возвращает результат. Все зависит от набора целевых команд и требуемой валидации. Если в тот же CLI генерировать целевой код, вместо всех моих "конструкторов" будут методы, которые зовут "обычные" приватные конструкторы для этого CLI. Все, все остальные не видят ни одного "конструктора".
M>>P.S. Я .NET не знаю, знаю только Java. AVK>В джаве вроде бы можно сделать конструктор приватным, не так ли? В таком случае все твои заявленные потребности эта фича покрывает с лихвой и без отказа от наследования реализации.
Да, можно. Но потребности фичи она не покрывает. Задалбывает ручками писать "прокси" для конструкторов, да и first-class methods в Java нет. Генерировать байт-код (аннотациями/постпроцессорами и т.п.) — костыли как раз. Мое выпиливание конструкторов ничего существенного (ага, кроме наследования реализации) не теряет, но костыли становятся не нужны. Да и вообще, если по всем языкам брать, все не так хорошо. У меня основной язык вообще ActionScript 3 (Java — второй). А там конструкторы всегда public, не first-class, да и через динамику их не вызывать. Метод я могу вызвать, передав аргументы массивом (func.apply(null, args)), а конструктор — нет (только через позиционные new Cls(...,..., ...)). Ну и обертки для конструкторов сильно надоедает писать (они единичные, но работа то лишняя).
Здравствуйте, maxkar, Вы писали:
M>В каком смысле "доступ дается только к самому себе"? В обычном конструкторе можно делать почти все то же, что и в других методах.
Но доступен так же неинициализированный this. И это вызывает целую кучу спецэффектов, кстати, которые только усугубятся, если можно будет этим this еще и явно манипулировать.
M> И this из обычного конструктора элементарно можно можно куда угодно передать.
Но это очень плохой тон, приводящий к куче проблем.
M> В том числе и в статический метод. Тот "конструктор", который я предлагал, внутри инициализатора тоже работает как "обычный конструктор", а снаружи выглядит как "static method".
А в чем принципиальная разница между конструктором и статическим методом снаружи? В синтаксисе вызова? Это все?
AVK>>Ну то есть проблема в твоем варианте никак не решается. Ответ понятен. M>Здесь все точно так же, как и с обычными "конструкторами".
Да у тебя везде все как с обычными конструкторами. Все принципиальные моменты, отличающие конструкторы от обычных методов, остаются в прежнем виде. В сухом остатке — мелкие синтаксические различия.
M>Ну примерно для этого и делалось. Но только акцент в другую сторону. Чтобы из конструкторов сделать обычные (с точки зрения той же системы типов, например) методы.
Они и так более менее обычные методы, по крайней мере в CLR и JVM. Хуже того, приватный конструктор позволяет тебе сделать именно то, что ты хочешь. Может ценой чуть чуть большего количества кода.
M> Method group меня не волнует.
Ты в первом сообщении переживал как раз по этому поводу.
M>Вот зачем "вне" класса понятие его конструктора (при отсутствии наследования реализации)?
Если не нужно — сделай конструктор приватным.
M>Да, можно. Но потребности фичи она не покрывает. Задалбывает ручками писать "прокси" для конструкторов
А не надо писать прокси, если внутри этой прокси логики нет. Конструктор ничем не хуже.
M>, да и first-class methods в Java нет
Там может в этом проблема, а не в конструкторах?
... << RSDN@Home 1.2.0 alpha 5 rev. 65 on Windows 7 6.1.7601.65536>>
Здравствуйте, AndrewVK, Вы писали:
AVK>А вообще можно, к примеру, было передать единственному классу в конструктор 1-2 функции. Если абсолютно одинаковые функции применялись не в одном месте (хотя при сотне вариантов такое, видимо, было редко) — конструктор вынести в статический метод-фабрику.
Очень интересно. Однако, таким образом мы можем расширить этот единственный класс функциями (методами). А как быть, если нужно добавить в базовый класс поля/свойства/эвенты ?
Здравствуйте, AndrewVK, Вы писали:
M>> В том числе и в статический метод. Тот "конструктор", который я предлагал, внутри инициализатора тоже работает как "обычный конструктор", а снаружи выглядит как "static method". AVK>А в чем принципиальная разница между конструктором и статическим методом снаружи? В синтаксисе вызова? Это все?
Ну в моем варианте разницы не будет. Сейчас:
1. Да, есть синтаксическая разница между вызовом конструктора и метода
2. Конкретно у меня в AS3 first-class Constructor и first-class function — разные и несовместимые друг с другом типы. Это неудобно. Нельзя скормить конструктор во все high-order function вроде map и пр. Причем в силу особенностей рантайма, я даже простым способом из конструктора сделать фунцкию не могу, нужно писать различные оберкти на различное число аргументов.
3. При изменении кода у конструкторов чуть меньше гибкости (только с точки зрения автоматизации действий). Интересует ситуация, когда внутренняя структура класса сильно меняется и конструктор становится "deprectated" (рекомендуется использовать другие конструкторы, например, или статический метод вместо конструктора). В AS3 проблема актуальна — там нет перегрузки конструкторов (и методов тоже). Придется искать все вхождения конструктора и править их. С методами можно оставить старый метод. Можно сделать бывший "конструктор" обычным методом (пользователи их не различают), вызывать из него правильный "конструктор" и сделать "inline method", например.
AVK>Да у тебя везде все как с обычными конструкторами. Все принципиальные моменты, отличающие конструкторы от обычных методов, остаются в прежнем виде. В сухом остатке — мелкие синтаксические различия.
А это уже от задачи и стиля зависит. Мне как раз эти "мелкие синтаксические различия" позволят писать меньше кода. Причем не в одном месте. И даже если это будет ценой отказа от наследования реализации, я не расстроюсь. Как раз наследованием реализации я и не пользуюсь, а своей фичей пользуюсь.
M>> Method group меня не волнует. AVK>Ты в первом сообщении переживал как раз по этому поводу.
Хм. Я их точно не имел в виду (у меня нет overload!). Мне нужны first-class constructors (которые при этом являются обычными first-class функциями) и возможность именованных конструкторов (в том числе из-за невозможности overload'а). Необходимость перегрузки конструктора "по имени" для меня нормальна.
M>>Вот зачем "вне" класса понятие его конструктора (при отсутствии наследования реализации)? AVK>Если не нужно — сделай конструктор приватным. M>>Да, можно. Но потребности фичи она не покрывает. Задалбывает ручками писать "прокси" для конструкторов AVK>А не надо писать прокси, если внутри этой прокси логики нет. Конструктор ничем не хуже.
Так не хочу я давать "публичный" конструктор. Может, он через месяц поменяется. Что мне дальше делать? Рефакторинги с методами технически проще реализуются (см. выше). В некоторых из случаев у меня еще и подозрения бывают, что возвращается интерфейс, а не класс. В том смысле, что API определено, а вот "точный" тип — нет. В статическом методе через какое-то время вместо объекта может создаваться его "наследник" (появятся новые методы, будет декомпозиция/параметризация поведением и т.п., все внутри реализации). С публичным (общедоступным) конструктором это все проделывать более сложно.
M>>, да и first-class methods в Java нет AVK>Там может в этом проблема, а не в конструкторах?
Это как посмотреть. В принципе, проблема очень похожа на лишнюю писанину с конструкторами, которой я хочу избежать. first-class методы прекрасно эмулируются интерфейсами и анонимными классами. Только это еще писана на каждый случай использования. Так что анонимные методы просто уменьшили бы объем кода. Моя фича тоже уменьшила бы у меня объем кода (в меньшей степени, чем first-class методы, но все же) в некоторых сценариях.