Здравствуйте, VladD2, Вы писали:
AS>> Если нужна такая фича, проще перегрузить конструтор, а в нем написать super(). И все, и никаких проблем
VD>Помтоу что это море никому не нужного кода. Вот представь... Создал я реализацию некой коллекции. Причем в рассчете на то, что напрямую ее создавать никто не будет, а на против, будут порождать он нее наследников. Так вот в этой коллекции есть, предположим, 20 конструкторов. Их все прейдется описать в наследнике. Да еще не ошибиться.
Создавать 20 конструкторов в классе, предназначенном исключительно для наследования от него, с твоей стороны, -- это плыть против течения. Тем более, что ты прекрасно знаешь, что кому-то (может быть и тебе) придется в наследниках их все определять заново.
Одно из предназначений конструктора -- инициализировать поля объекта. Поэтому часто бывает достаточно одного "главного" конструктора (возможно с большим количеством аргументов), который инициализирует все поля. Однако конструктор с большим количеством аргументов использовать неудобно, поэтому и создаются дополнительные конструкторы (с меньшим количеством аргументов), которые некоторые поля инициализируют некими умолчательными значениями. Полагаю, что твой класс для наследования с 20 конструкторами ничего не потеряет если будет иметь всего один "главный" конструктор. А на остальных конструкторах данного класса можно было бы и сэкономить.
Что касается наследников, то на их конструкторах сэкономить, в общем случае, уже не получится, поскольку им в любом случае надо инициализировать свои поля. "Наследование конструкторов" могло бы быть безопасным лишь в том случае, если бы наследник не добавлял своих полей; либо явно указывал, что свои поля он инициализировать значениями, отличными от умолчательных, не собирается; либо явно переопределял все "унаследованные" конструкторы.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, dshe, Вы писали:
D>>Необходимость возникает когда в наследнике добавляется поле, которое должно быть инициализировано (в конструкторе) ненулевым значением. Так называемое "наследование конструкторов" скорее всего приведет к тому, что у класса-наследника будет много конструкторов, унаследованных от предков самой разной близости, которые не инициализируют объект полностью. А это чревато ошибками. На мой взгляд, гораздо проще (и безопаснее) явно объявить конструктор, в котором просто вызвать base, чем скрывать за private или protected лишние конструкторы.
VD>Ну, и почему не поступать так же как с конструкторами без параметров? Ну, наследовать конструкторы если в наследнике они не определены явно.
В смысле, наследовать конструкторы, если в наследнике ни один конструктор не определен явно? Наверняка существуют условия при которых "наследование конструкторов" безопасно. Но в любом случае, "лишние" конструкторы в наследнике не должны быть сюрпризом для программиста.
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, Oyster, Вы писали:
O>> при добавлении нового конструктора в базовом классе он чудесным образом появится во всех наследниках, как в C++.
J>Ничего подобного в С++ нет.
Здравствуйте, dshe, Вы писали:
D>Создавать 20 конструкторов в классе, предназначенном исключительно для наследования от него, с твоей стороны, -- это плыть против течения. Тем более, что ты прекрасно знаешь, что кому-то (может быть и тебе) придется в наследниках их все определять заново.
Это жизнь. Ну, нужны эти 20 конструкторов. Мало ил как объект будет инициализироваться?
D>Одно из предназначений конструктора -- инициализировать поля объекта. Поэтому часто бывает достаточно одного "главного" конструктора (возможно с большим количеством аргументов), который инициализирует все поля.
"Часто", "бывает"... это не те категории. Бывает по разному. Бывает, что нужно унаследовать конструкторы. Ну, не ввел я ни одного нового поля. Так зачем же мне переоеределяь все 20 конструкторов? А ведь с теми самыми базовыми классами коллекций так и бывает.
D> Однако конструктор с большим количеством аргументов использовать неудобно, поэтому и создаются дополнительные конструкторы (с меньшим количеством аргументов), которые некоторые поля инициализируют некими умолчательными значениями. Полагаю, что твой класс для наследования с 20 конструкторами ничего не потеряет если будет иметь всего один "главный" конструктор. А на остальных конструкторах данного класса можно было бы и сэкономить.
Это экономия на пользователях твоего класса. В общем, плохое решение.
D>Что касается наследников, то на их конструкторах сэкономить, в общем случае, уже не получится, поскольку им в любом случае надо инициализировать свои поля.
Предположим, нет полей. Хотя я уже повторяюсь.
D> "Наследование конструкторов" могло бы быть безопасным лишь в том случае, если бы наследник не добавлял своих полей; либо явно указывал, что свои поля он инициализировать значениями, отличными от умолчательных, не собирается; либо явно переопределял все "унаследованные" конструкторы.
Ну, и в чем проблема? Если полей нет или все они инициализируются по месту, то что может помешать наследованию конструктора?
ЗЫ
В общем, это все попытки скрыть явный прощет в дизайне языков.
... << RSDN@Home 1.2.0 alpha rev. 591>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Какими средствами? Т.е., если инвариантом Derived является то, что Base ПК>проинициализирован someFlag = true, то как это обеспечить при открытом ПК>конструкторе Base(bool)?
ПК>Варианты явным образом закрывать конструкторы подходят не вполне, т.к. ПК>при добавлении базовых классов необходимо будет просматривать всех ПК>наследников, что в общем случае сделать нереально.
Добавлении базовых классов? Это как?
И зачем просматривать всех наследников? Ничего не пойму
Дарней,
ПК>> Варианты явным образом закрывать конструкторы подходят не вполне, т.к. ПК>> при добавлении базовых классов необходимо будет просматривать всех ПК>> наследников, что в общем случае сделать нереально.
Д> Добавлении базовых классов? Это как?
Оговорился. При добавлении конструкторов в базовых классах.
Д> И зачем просматривать всех наследников? Ничего не пойму
Чтобы убедиться, что их инварианты не нарушаются при добавлении нового конструктора в базе.
Posted via RSDN NNTP Server 2.0 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, AndrewVK, Вы писали:
SC>>>В чём глубокий смысл сабжа в C#?
AVK>>Одна из причин — необходимость убирать базовые конструкторы.
VD>Лучше бы решили проблему скрытия методов в наследнике.
А зачем может понадобиться скрывать методы в наследнике? Если наследник не реализует весь тот интерфейс, который он унаследовал, то он может и не должен быть наследником? поскольку в этом случае явно нарушается LSP.
Кстати, в С++ по-моему можно в наследнике делать методы приватными, которые в базовом были public.
struct A {
virtual void f() {}
};
class B: public A {
virtual void f() {}
};
Что приводит к следующему парадоксу, на мой взгляд.
B *b = new B();
b->f(); // так нельзя -- ошибка компиляции
((A *) b)->f(); // а так уже можно
Получается, что вполне легальными средствами можно доступиться к скрытому методу.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК> Дарней,
ПК>>> Варианты явным образом закрывать конструкторы подходят не вполне, т.к. ПК>>> при добавлении базовых классов необходимо будет просматривать всех ПК>>> наследников, что в общем случае сделать нереально.
Д>> Добавлении базовых классов? Это как?
ПК>Оговорился. При добавлении конструкторов в базовых классах.
Д>> И зачем просматривать всех наследников? Ничего не пойму
ПК>Чтобы убедиться, что их инварианты не нарушаются при добавлении нового конструктора в базе.
Не просто нового конструктора в базе. А такого нового конструктора, который требует соблюдения неких инвариантов его вызова во всех наследниках. Это — во первых.
А во вторых — в этом случае достаточно сделать наследника от базового класса, который перекрывает этот конструктор и делает его protected, а для наследников оставляет открытый конструктор с соблюдением инварианта. Все порожденные классы наследуешь уже от него. И всё!
Здравствуйте, dshe, Вы писали:
D>А зачем может понадобиться скрывать методы в наследнике? Если наследник не реализует весь тот интерфейс, который он унаследовал, то он может и не должен быть наследником? поскольку в этом случае явно нарушается LSP.
Чтобы не приходилось писать в документации вот так:
Здравствуйте, Дарней, Вы писали:
ПК>>Чтобы убедиться, что их инварианты не нарушаются при добавлении нового конструктора в базе.
Д>Не просто нового конструктора в базе. А такого нового конструктора, который требует соблюдения неких инвариантов его вызова во всех наследниках. Это — во первых.
Как новый конструктор в базовом классе обеспечит инициализацию всех полей в тех наследниках, которые их, все-таки, несмотря ни на что, добавляют?
Д>А во вторых — в этом случае достаточно сделать наследника от базового класса, который перекрывает этот конструктор и делает его protected, а для наследников оставляет открытый конструктор с соблюдением инварианта. Все порожденные классы наследуешь уже от него. И всё!
Так может новый конструтор в базовый класс добавлялся не просто так, а для того, чтобы он был унаследован и экземпляры классов-наследников могли создаваться с его помощью...
Кстати, эти манипуляции с промежуточным базовым классом -- разве это не похоже на написание массы ненужного кода?
Здравствуйте, dshe, Вы писали:
D>ок. Укажи все те условия, при которых, как ты считаешь, наследовать конструкторы безопасно.
Это безопасно всегда, когда наследники не накладывают ограничений на параметры вызова открытых конструкторов базы. Этого достаточно?
Впрочем, в данном случае это тоже безопасно Просто требует дополнительных действий в виде создания дополнительного класса в иерархии.
Здравствуйте, dshe, Вы писали:
D>Как новый конструктор в базовом классе обеспечит инициализацию всех полей в тех наследниках, которые их, все-таки, несмотря ни на что, добавляют?
Программисты, которые не ищут себе лишних проблем на задницу, делают это вот так:
class MyClass : BaseClass
{
SomeClass someVar = new SomeClass();
}
Какие проблемы ты здесь видишь?
D>Так может новый конструтор в базовый класс добавлялся не просто так, а для того, чтобы он был унаследован и экземпляры классов-наследников могли создаваться с его помощью...
Мне кажется, ты просто не понял, о чем идет речь.
D>Кстати, эти манипуляции с промежуточным базовым классом -- разве это не похоже на написание массы ненужного кода?
Немного похоже. Разница только в том, что в одном случае мы делаем один вспомогательный класс и наследуемся от него, а вдругом — копипейстим код вызова конструктора в десятки классов-наследников.
Для тебя это не имеет значения?
Здравствуйте, Дарней, Вы писали:
Д>Здравствуйте, dshe, Вы писали:
D>>А зачем может понадобиться скрывать методы в наследнике? Если наследник не реализует весь тот интерфейс, который он унаследовал, то он может и не должен быть наследником? поскольку в этом случае явно нарушается LSP.
Д>Чтобы не приходилось писать в документации вот так: Д>
D>>Что приводит к следующему парадоксу, на мой взгляд. D>>
D>>B *b = new B();
b->>>f(); // так нельзя -- ошибка компиляции
D>>((A *) b)->f(); // а так уже можно
D>>
D>>Получается, что вполне легальными средствами можно доступиться к скрытому методу.
Д>А что здесь парадоксального? Программист просто дает понять, что хотя метод и можно вызвать, но лучше этого не делать.
А чем тогда NotSupportedException не подошел? Тоже способ дать понять, что хотя метод и можно вызвать, но лучше этого не делать.
Насколько я понял, возможность скрывать методы в наследнике подразумевает проверки на этапе компиляции, что скрытые методы у класса-наследника не используются и в принципе использоваться не могут. Пример на C++ -- это повод показать, что полноценные проверки сделать невозможно; в C++, по крайней мере, это не получилось. Предложите свою модель.
По поводу Array.IList.Add Method: то, что метод всегда выбрасывает исключение -- не образец для подражания.
Здравствуйте, dshe, Вы писали:
D>А чем тогда NotSupportedException не подошел? Тоже способ дать понять, что хотя метод и можно вызвать, но лучше этого не делать.
Тем, что он не срабатывает во время компиляции.
D>в принципе использоваться не могут.
С помощью отражения, unsafe и/или иных трюков можно использовать любые скрытые методы и поля, если у программиста возникнет такое желание.
Поэтому отложим вакуумных сфероконей в сторону и поговорим о более приземленных вещах. Например, защите от нечаянной ощибки. Сокрытие методов эту задачу решает — этого вполне достаточно.
D>По поводу Array.IList.Add Method: то, что метод всегда выбрасывает исключение -- не образец для подражания.
В данном случае это скорее кривой дизайн. Тем не менее, такая необходимость бывает, и нередко. Сейчас ее можно решить только инкапсуляцией и копи-пейстом гор кода, что не есть хорошо — и к тому же, все равно не дает никаких гарантий.
Здравствуйте, Дарней, Вы писали:
Д>Здравствуйте, dshe, Вы писали:
D>>Как новый конструктор в базовом классе обеспечит инициализацию всех полей в тех наследниках, которые их, все-таки, несмотря ни на что, добавляют?
Д>Программисты, которые не ищут себе лишних проблем на задницу, делают это вот так:
calm down. различия во взглядах -- это не повод делать замечания, которые могут восприниматься как оскорбление.
Д>Программисты, которые не ищут себе лишних проблем на задницу, делают это вот так: Д>
Для поля someVar (в данном примере), возможно не существует умолчательного значения, типа new SomeClass(). И во всех конструкторах (своих) оно инициализируется через параметр. Предсказать, какие конструкторы будут добавлены в базовый класс, для того, чтобы их скрыть заранее, либо должным образом переопределить -- задача нереальная.
D>>Так может новый конструтор в базовый класс добавлялся не просто так, а для того, чтобы он был унаследован и экземпляры классов-наследников могли создаваться с его помощью...
Д>Мне кажется, ты просто не понял, о чем идет речь.
Возможно. Объясни тогда другими словами. Я пытаюсь принять вашу позицию и найти в ней уязвимые места.
Здравствуйте, dshe, Вы писали:
D>Для поля someVar (в данном примере), возможно не существует умолчательного значения, типа new SomeClass(). И во всех конструкторах (своих) оно инициализируется через параметр.
В таком случае, тебе все равно придется создавать конструкторы вручную — неважно, есть наследование конструкторов или нет
D>Предсказать, какие конструкторы будут добавлены в базовый класс, для того, чтобы их скрыть заранее, либо должным образом переопределить -- задача нереальная.
Ну и не нужно ничего предсказывать. Это задача того, кто добавит конструктор в базовый класс.
Вообще говоря, сделать промежуточный класс, который будет гарантировать соблюдение инварианта для своих наследников — это хорошая идея в любом случае.
D>Возможно. Объясни тогда другими словами. Я пытаюсь принять вашу позицию и найти в ней уязвимые места.
А чего тут непонятного? Промежуточный класс гарантирует соблюдение инвариантадля всей подветки своих наследников. Достигается это тем, что конструктор промежуточного класса соблюдает инвариант всегда, а наследники могут вызвать только этот конструктор, но не конструктор базового класса напрямую.
Здравствуйте, Дарней, Вы писали:
Д>Здравствуйте, dshe, Вы писали:
D>>Для поля someVar (в данном примере), возможно не существует умолчательного значения, типа new SomeClass(). И во всех конструкторах (своих) оно инициализируется через параметр.
Д>В таком случае, тебе все равно придется создавать конструкторы вручную — неважно, есть наследование конструкторов или нет
Верно. Только в случае если конструкторы не наследуются по умолчанию, я буду уверен, что никто не просочится без моего ведома.
Здравствуйте, dshe, Вы писали:
D>Верно. Только в случае если конструкторы не наследуются по умолчанию, я буду уверен, что никто не просочится без моего ведома.
А если будут, ты все равно получишь ворнинг, что поле не проинициализировано