![]() |
Модераторы: Partizan, gambit |
![]() ![]() ![]() |
|
diadiavova |
|
|||
![]() Доктор Зло(диагност, настоящий, с лицензией и полномочиями) ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 5821 Регистрация: 14.8.2008 Где: В Коньфпольте Репутация: 31 Всего: 142 |
Дело в том, что переопределённая виртуальная функция - это та же самая функция, для которой было изменено поведение. Не знаю как всё устроено в жабе(знаком обзорно), но в дотнете это может привести к неприятностям. Если функция помещена в класс просто для того, чтобы её могли использовать будущие поколения - то не страшно. Но если эта функция используется самим базовым классом, и влияет на его поведение, то её переопределение может привести к непредсказуемым последствиям. Поскольку программист не всегда знает детали реализации базового класса, то учесть все последствия переопрделния он не может. И понять источник проблем тоже. Когда метод затеняется он продолжает работать в классе и передаётся потомкам, просто к нему нет прямого доступа, но если он используется в базовом классе - он не будет лежать мёртвым грузом, а будет работать. В то же время метод с такой же сигнатурой может быть предоставлен для использования. Затенённый и затеняющий методы - два разных метода, имеющих одинаковую сигнатуру и находящихся в одной области видимости(в следствии чего один из них не виден). Переопределённый метод - это тот же самый метод, для которого в производном классе определен другой тип поведения.
-------------------- Хочешь получить мудрый совет - читай подписи участников форумов. Злой доктор Щасзаболит ![]() |
|||
|
||||
Dims |
|
||||||||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1016 Регистрация: 21.11.2006 Репутация: 0 Всего: 11 |
В Джаве функции переопределяются не для будущих поколений, а для полиморфизма, то есть, чтобы можно было работать с объектом не зная точно его типа. Для любого объекта, независимо от того, что нам в данный момент известно о его типе будет вызываться именно его метод. Это же логично.
Для этого есть слово final, которое запрещает переопределение функции. Если у нас такая функция, которая используется самим классом и вредно, чтобы она "знала правду" о классе, тогда мы делаем её final. Всё, теперь такого не случится. А в обратном случае это может даже помочь. Мы можем нарочно определить функцию, которая используется в базовом классе но в расчёте на возможное переопределение в наследниках. Тогда наследник сможет влиять на поведение вызывающей функцией базового класса. А к каким последствиям, по-Вашему, может привести первый сценарий? Можете придумать пример?
Мне кажется, это нелогично. Если человек не знает свойств базового класса, то зачем он взялся его переопределять? Пусть тогда напишет обёртку, а не наследника. Кроме того, если наследование чревато, то, значит, разработчик базового класса не позаботился о том, чтобы его класс можно было легко расширять.
Я это понимаю, всё-таки с Си++ знаком. Вопрос в том, нафига это нужно. Зачем нужен такой же метод, но делающий совершенно другое? По логике, метод должен быть спроектирован и назван так, чтобы было понятно его предназначение. Если у метода другое предназначение, то нужно его назвать по-другому. По Си++, повторяю, мне кажется, что причина была в экономии памяти. Всё-таки Си -- это макроассемблер, а Си++ это Си с классами. То есть, Си++ это ассемблер с классами. При программировании на Си++ можно (и желательно) представлять, что происходит с байтами, сколько занимает класс, как он расположен в памяти и так далее. А виртуальные функции вносят сумятицу в "ассемблерность". Поэтому для Си++ они лишь опция. А почему они лишь опция для C#? |
||||||||
|
|||||||||
diadiavova |
|
||||||
![]() Доктор Зло(диагност, настоящий, с лицензией и полномочиями) ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 5821 Регистрация: 14.8.2008 Где: В Коньфпольте Репутация: 31 Всего: 142 |
В дотнете это всё тоже есть и такой подход даже является главенствующим, однако дополнительные возможности , насколько я понимаю "жрать не просят". Когда я писал обудущих поколениях я имел в виду функции, которые не вызываются базовым классом, а предоставлены для использования производными. Относительно final, в до-диезе есть слово sealed которое(как я понимаю) деделает то же самое. Но если мне надо создать метод, переопределение которого будет иметь неблагоприяятные последствия из-за того, что в базовом классе этот метод вызывается в расчёте на определённое поведение и вдруг в производном классе это поведение изменяется. Чтобы этого избежать я просто не даю такой возможности и не делаю метод виртуальным. Что в этом плохого? Если в жабе тоже есть такая возможность, то это говорит только о том, что различия здесь в том, какими функции считаются по умолчанию: в жабе - виртуальными, в дотнете - нет. И там и тут это можно изменить. Только в дотнете ещё можно и создать такую же функцию. Насчёт упаковывания класса - хорошая задумка, особенно если в нём пара сотен членов и все надо упаковать, а в дотнете вместо этого надо упаковать всего-лишь один метод, к тому же затенённый метод можно вызвать из затеняющего(это не проблема). Ну для этого и есть виртуальные методы, в дотнете они тоже так работают(учитывая происхождение дотнета вряд ли стоит этому удивляться).
Ну например такая ситуация: Внутренний метод, вызывающий переопределённую функцию расчитывает на то, что значение ею возвращённое будет лежать в пределах определённого диапазона значений. Другое не предусмотренно из соображений экономии ресурсов. Переопределённый вариант функции(в отличии от первоначального) может возвращать значение, к которому вызывающий метод просто не готов. Естественно это вызовет сбой. Конечно пример не очень показательный, но по крайней мере ясно, что я имею в виду.
А от куда он может знать свойства? Если функция помечена как виртуальная - он знает, что её можно переопределить. Если нет - знает, что нельзя. Тут по-моему как раз всё просто.
Да нет. Как раз таки он позаботился, определив какие члены класса можно переопределять, а какие нет. Я не сказал, что он будет делать совершенно другое. Чуть-чуть другое, или просто по-другому, или например делая тоже самое(скажем посредством вызова затеняемого метода), при этом ещё инициировать какое-нибудь событие, да и вообще мало ли что ещё может понадобиться. Иногда бывает надо закрыть метод просто из-за того, что его вызов может не вписаться в логику класса и просто надо его перекрыть, например методом с пустой реализацией. И потом, в таком случае, почему не вызывает вопросов возможность перегрузки методов? Зачем нужно два метода с одинаковыми именами? Вариантов может быть много, хотя затенением я не пользуюсь, возможно именно по этому не могу привести какой-то яркий пример, но если так рассуждать "зачем нужно то, да зачем нужно это", то так можно очень далеко зайти. К примеру: зачем нужны модификаторы области видимости, когда можно просто не использовать те которые трогат нельзя, а зачем их прятать? Вопрос звучит странно, но он из того же набора, если мне не нужно, чтобы метод переопределяли - я не даю такой возможности Что значит "лишь опция"? Почему по-умолчанию они должны быть виртуальными? Потомучто в жабе так? Учитывая тот факт, что дотнет (мягко говоря) взял много из жабы, то наверно там это доставляло какие-то неудобства, раз уж это изменено. -------------------- Хочешь получить мудрый совет - читай подписи участников форумов. Злой доктор Щасзаболит ![]() |
||||||
|
|||||||
Dims |
|
||||||||||||||||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1016 Регистрация: 21.11.2006 Репутация: 0 Всего: 11 |
Я не знаю. Просто мне пока непонятно, зачем это может быть нужно и оно кажется нелогичным. Если брать двоих: потомка и предка, то, по логике, потомок всегда знает больше предка. Поэтому, бояться, что знающий больше что-то напортачит, нелогично. Кроме того, затенение само по себе чревато ошибками: если мы приведём наследника к типу предка, что совершенно допустимо и не должно приводить ни к каким плохим последствиям, и вызовем метод, то вызовется затенённый метод. То есть, метод предка отработает на материале наследника, что чревато гораздо более серьёзными глюками, так как предок в принципе не может ничего знать о наследнике. Но даже если возможности просто не нужны, то они "просят жрать" в том смысле, что требуют ресурсов, а взамен ничего не дают. Например, требуют от меня набирать слово "virtual". В том-то и дело, что в Джаве это нельзя изменить. Все функции всегда виртуальные.
Я так понимаю, что современные генераторы делают это автоматически. Мы же говорим о ситуации, когда разработчик не знает поведения базового класса и боится, что переопределение какой-то функции приведёт к нежелательным последствиям. Это значит, скорее всего, что ему и не нужны все 100 членов (раз он неосведомлён о том, что они делают). Всё же это абстрактная ситуация. Где конкретно такое может произойти? Как правило, наследник более специфичен, а это значит, что диапазон возвращаемых значений должен только сужаться. Например, функция "Рост" для "Животного" может возвращать значение от миллиметров до десятков метров. А та же функция для "Человека" примерно от метра до двух. Как может получиться так, что частный случай возвращает больший спектр значений, чем общий? Если это происхожит, то это, наверное, не частный случай. В этом случае функцию просто, наверное, нужно назвать по-другому.
Ну это правильно, но если функция не помечена, как виртуальная, то её можно затенить. И зачем нужна эта возможность -- непонятно. Ну а как же тогда? По логике ООП наследник -- это некто более осведомлённый. Частный случай предка. Его методы -- они либо повторяют методы предка, либо делают что-то более частное, детальное, что на уровне предка сделать было нельзя. Такая эволюция методов логична.
Ну а причём тут затенение? Виртуальная функция точно так же может вызвать перекрытый метод, разве нет? Для этого затенение не нужно.
С одинаковыми именами, но с разными сигнатурами. Как правило, такие методы должны делать одно и тоже с разными исходными данными. А как же? Объявляете все функции виртуальными?
Ну я не знаю, куда можно зайти ![]() Соответственно, когда какие-то вещи, которые были убраны и по которым ты совсем не скучал, возвращаются, неизбежно возникает вопрос, нафига. Это чё такое? public/private?
Минуточку, но это ведь как раз Вы говорите, что что плохого в затенении, если его можно просто не использовать. Повторюсь, что слово override мне нравится. Оно добавляет проверку (кстати, если они любители проверок, то нафига убрали throws? в джаве есть два типа Exception, одни из них обязательно описывать в сигнатуре метода при помощи слова throws; то есть, ты смотришь на метод и видишь, чего он выбрасывает)
Как Вы можете не дать затенять? Ну если это единственная полезная их форма, то разве это не основание?
Ну так давайте выясним, какие. Разве это не интересно? |
||||||||||||||||
|
|||||||||||||||||
diadiavova |
|
||||||||||||||||||||||||||||
![]() Доктор Зло(диагност, настоящий, с лицензией и полномочиями) ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 5821 Регистрация: 14.8.2008 Где: В Коньфпольте Репутация: 31 Всего: 142 |
Потомок может и знает, только код этого потомка пишет программист, который знает не всё. Наследник должен создаваться с учётом поведения предка. Предок же не может заранее знать, что прийдёт в голову программисту который будет реализовывать наследование, поэтому логично некоторые действия просто запретить. Но запрещать создавать методы с определённой сигнатурой не стали, дабы расширить возможности. Ну к затеняющему методу можно относиться как к перегруженному. Перегруженные методы - это тоже разные методы, а одно имя им даётся намеренно, именно потому, что они делают одно дело но по-разному. Ну если не набирать это слово, тогда компилятору будет не понятно почему программист создал метод с существующим именем: по-ошибке или намеренно. Полиморфизм - это хорошо, а если я просто создаю метод для поддержки какой-то функциональности и мне не надо, чтобы его переопределяли. Ядолжен каждый раз об этом уведомлять словом final? По моему гораздо проще реализовать метод без учёта того, что он может быть переопределён. Я например виртуальные методы создаю как раз в тех случаях, когда именно на то и расчитываю, что потом можно будет переопределить их, но там продумывается логика и все возможные нюансы возможной(!) реализации в производном классе. Гораздо проще написать метод под конкретные задачи, да и ресурсов он меньше жрёт, потомучто не проверяет тех возможностей, которых не должно быть в случае, если всё пойдёт как я задумал. В виртуальном методе всё это надо учитывать.
Тогда я не понял, что такое final. Ему не члены нужны а сам класс. Возможно он вообще должен быть определённого типа там где используется. Базовый класс вообще может быть абстрактным, а реализуется только потому, что какой-то метод затребовал в качестве параметра экземпляр класса производного от данного, да вариантов море. Надо мне конрол создать, не знаю всех нюансов поведения, сделаю упаковку , и куда я с ней? В коллекцию Controls её не добавишь, что мне эту упаковку сбоку пристраивать чтоли, а если некоторые методы надо переопределить, как это реализовать с упаковкой? Да и потом мне не ясна такая логика, когда написать virtual трудно, а создавать упаковку, даже если генерить код автоматом - легко.
Если оба класса пишет один человек - такого быть не должно. Но это не всегда так. Насчёт специфичности класса я не согласен. Потомок расширяет базовый класс(кстати в jscript.net для указания базового класса используется слово exteds). Если считать, что производный класс сужает возможности(как в примере с ростом) тогда самым богатым классом должен быть Object, а все остальные более специфичными, ну это же не так.
Это точно не частный случай. А функцию можно назвать и по-другому, но можно и так же(если надо).
Возможность эта нужна за тем же зачем и переопределить. Если нужно, чтобы в производном классе была такая функция, но выполняла другие действия, то эту функцию создают. Если функция виртуальная, то эту новую функцию можно оформить как переопределяющую(не обязательно виртуальные функции тоже можно затенять), а в остальных случаях она будет затенять соответствующую функцию базового класса. Я подчёркиваю: в обоих случаях делается одно и то же. Разница состоит в том, что при переопределении все вызовы, определённые в базовом классе, будут обращаться к новой версии функции, а при затенении - к старой. Но новая реализация функции имеет место в обоих случаях. То есть: возможность затенения нужна для того, чтобы при создании такого же метода в производном классе, поведение базового, завязанное на ней не менялось. Я бы сказал не что-то более частное, а скорее, что-то сходное по логике, но иногда совсем другое. Что-то, что может быть тем же, что и в базовом классе, только применительно к другому контексту.
Может, только как я уже сказал, базовый класс тоже будет её вызывать. А пример я привёл для того, чтобы показать, что метод не только не делает что-то другое, аможет вести себя так же как виртуальный, даже вызывать затеняемый метод, другой вопрос, что его не будет вызывать базовый класс.
Как правило - это тоже ни кчему не обязывает, а потом я не говорил, что затеняющий метод должен делать что-то другое, по-другому - да. Нет, только те, которые собираюсь переопределять. Вся фишка в том, что как правило в затенении нет необходимости, но если она возникает, то у меня есть возможность создать в производном классе функцию с нужной сигнатурой, не затрагивая болевых точек базового класса. И хорошо, что она есть. Я понимаю, что без этого, наверно можно обойтись, но это удобно. Если переопределение функции опасно - я лишён такой возможности. Разве это плохо? Сколько косяков можно напороть просто из-за незнания всех возможных опасностей. В современных языках в основном приходится работать с огромным количеством библиотечных классов, знать все нюансы поведения которых, просто невозможно. И если там есть ограничения, то это приводит просто к тому, что я не могу сделать некоторых вредных штук. Взять например зоопарк. Звери сидят в клетках, чтоб никого не растерзали, но на расстоянии от клетки стоит ограда для людей, чтобы они не подходили близко. Ограничение свободы? Да. Хорошо это или плохо в данном случае? Ну может от этого она не очищена, просто там до этогоне додумались? ![]() А если учесть, что у дотнета ноги из жабы растут... Угу =========== Насчёт trows я не понял, но в дотнетовских языках это тоже есть, не знаю, может в жабе есть варианты... А зачем? Не давать переопределять нужно для того, чтобы вызовы базового класса не получали подмену, а зачем не давать затенять? Затенение - другой метод. Пусть создают сколько влезет методов(или других членов) с такими же сигнатурами, на поведение, определённое в базовом классе это не повлияет. Запрещать затенение - это всё равно, что запрещать создавать члены с определёнными именами. Это ничего не даст. А вот запрет на изменение логики на какю-то неожиданную, может оказаться полезным. Эта форма не единственная полезная, и кроме того - потенциально опасная. Да в общем-то этим и занимаемся уже несколько дней. ![]() Добавлено через 10 минут и 18 секунд
Потомок может и знает, только код этого потомка пишет программист, который знает не всё. Наследник должен создаваться с учётом поведения предка. Предок же не может заранее знать, что прийдёт в голову программисту который будет реализовывать наследование, поэтому логично некоторые действия просто запретить. Но запрещать создавать методы с определённой сигнатурой не стали, дабы расширить возможности. Ну к затеняющему методу можно относиться как к перегруженному. Перегруженные методы - это тоже разные методы, а одно имя им даётся намеренно, именно потому, что они делают одно дело но по-разному. Ну если не набирать это слово, тогда компилятору будет не понятно почему программист создал метод с существующим именем: по-ошибке или намеренно. Полиморфизм - это хорошо, а если я просто создаю метод для поддержки какой-то функциональности и мне не надо, чтобы его переопределяли. Ядолжен каждый раз об этом уведомлять словом final? По моему гораздо проще реализовать метод без учёта того, что он может быть переопределён. Я например виртуальные методы создаю как раз в тех случаях, когда именно на то и расчитываю, что потом можно будет переопределить их, но там продумывается логика и все возможные нюансы возможной(!) реализации в производном классе. Гораздо проще написать метод под конкретные задачи, да и ресурсов он меньше жрёт, потомучто не проверяет тех возможностей, которых не должно быть в случае, если всё пойдёт как я задумал. В виртуальном методе всё это надо учитывать.
Тогда я не понял, что такое final. Ему не члены нужны а сам класс. Возможно он вообще должен быть определённого типа там где используется. Базовый класс вообще может быть абстрактным, а реализуется только потому, что какой-то метод затребовал в качестве параметра экземпляр класса производного от данного, да вариантов море. Надо мне конрол создать, не знаю всех нюансов поведения, сделаю упаковку , и куда я с ней? В коллекцию Controls её не добавишь, что мне эту упаковку сбоку пристраивать чтоли, а если некоторые методы надо переопределить, как это реализовать с упаковкой? Да и потом мне не ясна такая логика, когда написать virtual трудно, а создавать упаковку, даже если генерить код автоматом - легко.
Если оба класса пишет один человек - такого быть не должно. Но это не всегда так. Насчёт специфичности класса я не согласен. Потомок расширяет базовый класс(кстати в jscript.net для указания базового класса используется слово exteds). Если считать, что производный класс сужает возможности(как в примере с ростом) тогда самым богатым классом должен быть Object, а все остальные более специфичными, ну это же не так.
Это точно не частный случай. А функцию можно назвать и по-другому, но можно и так же(если надо).
Возможность эта нужна за тем же зачем и переопределить. Если нужно, чтобы в производном классе была такая функция, но выполняла другие действия, то эту функцию создают. Если функция виртуальная, то эту новую функцию можно оформить как переопределяющую(не обязательно виртуальные функции тоже можно затенять), а в остальных случаях она будет затенять соответствующую функцию базового класса. Я подчёркиваю: в обоих случаях делается одно и то же. Разница состоит в том, что при переопределении все вызовы, определённые в базовом классе, будут обращаться к новой версии функции, а при затенении - к старой. Но новая реализация функции имеет место в обоих случаях. То есть: возможность затенения нужна для того, чтобы при создании такого же метода в производном классе, поведение базового, завязанное на ней не менялось. Я бы сказал не что-то более частное, а скорее, что-то сходное по логике, но иногда совсем другое. Что-то, что может быть тем же, что и в базовом классе, только применительно к другому контексту.
Может, только как я уже сказал, базовый класс тоже будет её вызывать. А пример я привёл для того, чтобы показать, что метод не только не делает что-то другое, аможет вести себя так же как виртуальный, даже вызывать затеняемый метод, другой вопрос, что его не будет вызывать базовый класс.
Как правило - это тоже ни кчему не обязывает, а потом я не говорил, что затеняющий метод должен делать что-то другое, по-другому - да. Нет, только те, которые собираюсь переопределять. Вся фишка в том, что как правило в затенении нет необходимости, но если она возникает, то у меня есть возможность создать в производном классе функцию с нужной сигнатурой, не затрагивая болевых точек базового класса. И хорошо, что она есть. Я понимаю, что без этого, наверно можно обойтись, но это удобно. Если переопределение функции опасно - я лишён такой возможности. Разве это плохо? Сколько косяков можно напороть просто из-за незнания всех возможных опасностей. В современных языках в основном приходится работать с огромным количеством библиотечных классов, знать все нюансы поведения которых, просто невозможно. И если там есть ограничения, то это приводит просто к тому, что я не могу сделать некоторых вредных штук. Взять например зоопарк. Звери сидят в клетках, чтоб никого не растерзали, но на расстоянии от клетки стоит ограда для людей, чтобы они не подходили близко. Ограничение свободы? Да. Хорошо это или плохо в данном случае? Ну может от этого она не очищена, просто там до этогоне додумались? ![]() А если учесть, что у дотнета ноги из жабы растут... Угу =========== Насчёт trows я не понял, но в дотнетовских языках это тоже есть, не знаю, может в жабе есть варианты... А зачем? Не давать переопределять нужно для того, чтобы вызовы базового класса не получали подмену, а зачем не давать затенять? Затенение - другой метод. Пусть создают сколько влезет методов(или других членов) с такими же сигнатурами, на поведение, определённое в базовом классе это не повлияет. Запрещать затенение - это всё равно, что запрещать создавать члены с определёнными именами. Это ничего не даст. А вот запрет на изменение логики на какю-то неожиданную, может оказаться полезным. Эта форма не единственная полезная, и кроме того - потенциально опасная. Да в общем-то этим и занимаемся уже несколько дней. ![]() -------------------- Хочешь получить мудрый совет - читай подписи участников форумов. Злой доктор Щасзаболит ![]() |
||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
PashaPash |
|
|||
Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1233 Регистрация: 3.1.2008 Репутация: 13 Всего: 49 |
Dims,
По логике потомок не знает больше предка, скорее к ним применимо "потомок есть предок". Человек, пишущий класс-предок, знает его public/protected интерфейс и внутреннюю реализацию. Человек, пишущий класс-потомок, знает о предке только public/protected интерфейс и ничего не знает о реализации. И, соответственно, не должен никак на эту реализацию влиять, если это явно не разрешил автор предка. Значения по умолчанию направлены на уменьшение интерфейса класса/сборки - private/internal вместо public, невиртуальные вместо virtual. Вообще, с точки зрения ООД наследование настолько увеличивает связность и тянет за собой столько ошибок, что надо классы по умолчанию sealed объявлять. Вот именно поэтому и выводится warning, и требуется необязательное слово new. Просто надо воспринимать warnings as errors ![]() Набирая слово virtual, ты явно увеличиваешь площадь интерфейса. Во время набора нужно подумать что будет, если потомок реализует этот метод по-своему. Учесть что у автора потомка не будет знаний о внутреннем устройстве твоего класса. Учесть что через год какой-нибудь нуб придет и переопределит этот метод через 4 класса в цепочке наследования, при этом все 4 будут написаны тобой и будут завязаны на внутренее устройство родителя. Возможные проблемы гораздо круче чем проблемы при игнорировании варнинга "нужен new". Поэтому и слово длиннее на 4 буквы, чтобы больше времени было ![]() Затем, что классы-потомки могут бросать исключения, не описанные в синатуре предков. И еще затем, чтобы уменьшить связность и проблемы с версиями. В .net можно подменить, например, сборку с базовым классом даже если сборка потомка явно привязана к ее версии. И в момент выполнения список throws оказался бы совсем не таким, как в момент компиляции. В java фишек с версиями нет, там throws проверяется только в compile time. Это сообщение отредактировал(а) PashaPash - 28.10.2008, 10:46 |
|||
|
||||
Dims |
|
||||||||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1016 Регистрация: 21.11.2006 Репутация: 0 Всего: 11 |
Это было в Си++, потом это убрали в Джаве, а потом обратно вернули в C#. Джава произошла от Си++, а C# от них обоих.
Я не знаю. Я не знаю ни зачем не давать затенять, ни зачем давать. В Си++ я понимаю, зачем сделано затенение, а зачем в C# не понимаю. Добавлено через 8 минут и 30 секунд
Правильно. Но если ему в инструкции сказано, что переопределив такой-то метод он добьётся такого-то результата, то он кое-что знает и о реализации.
Ну давайте тогда уберём спецификацию возвращаемого типа, чтобы потомки могли и возвращать объекты другого типа. Ну ладно, это отклонение. В этой теме давайте разберёмся, зачем нужно затенение. |
||||||||
|
|||||||||
PashaPash |
|
||||||
Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1233 Регистрация: 3.1.2008 Репутация: 13 Всего: 49 |
Основной принцип ооп - о классе известен только его внешний интерфейс. Если в интрукции сказано что переопределение метода как-то повлияет на результат, то это значит все то же - что метод - часть public интерфейса класса. И никак не значит, что наследник что-то узнает о реализации. Если для наследования нужно нарушить инкапсуляцию, то это какое-то не ооп-ное наследование. На самом деле вопрос примерно на уровне: А почему методы не public по умолчанию? в языке xxx все методы pubic, очень удобно. Ну и что, что программер может не знать зачем они нужны. Никто ж не заставляет его их вызвать, да и автору базового класса легче - не надо писать дурацкие public. Да, в .net/java есть возможность сделать метод public, но зачем нужны private методы, это ж неудобно.
в jave бросаемые исключения - часть интерфейса, т.е. подразумевается что они могут возникать при обычном ходе выполнения программы. В .net исключения - это именно исключительные ситуации, и они не являются частью интерфейса. На самом деле код очень редко может нормально обработать исключение, и принудительное перечисление типов приведет к желанию программеров тупо исключения глушить. И код очень редко знает полный список исключений, которые может бросить вызываемый им кусок. Кроме кода на границе слоев, ес-но
С таким же успехом можно утверждать что C# произошел от Eiffel или от Modula. C#/.NET - не наследник java, это самостоятельная платформа. Ничего в нем не "убирали" и не "добавляли". В нем все так и было с самого начала. И вопросы с позиции "почему тут не так, как в java" имеют общий ответ: потому что это не java, не java от MS, и не наследник Java. И потому что в java тоже есть просчеты с точки зрения дизайна платформы - хотя бы дурацкое выделение элементарных типов, обязательное перечисление исключений, проблемы с версионностью, вирутальные по умолчанию... C# не обязан копировать из java ее проблемы. |
||||||
|
|||||||
diadiavova |
|
|||
![]() Доктор Зло(диагност, настоящий, с лицензией и полномочиями) ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 5821 Регистрация: 14.8.2008 Где: В Коньфпольте Репутация: 31 Всего: 142 |
А зачем такие сложности, когда можно просто запретить и всё(как в зоопарке) Проблема именно в том, что в этой теме мы смешали в кучу несколько близких тем. Это вносит некоторый сумбур. 1.Почему не все методы виртуальные? Насколько я понял в жабе либо все методы виртуальны, либо класс вообще не наследуется. В дотнете, в противовес этому, механизм наследования можно настроить тонко. То есть при написании класса автор сам определяет что можно переопределить, а что надо оставить без изменений. На мой взгляд - дотнетовский подход более гибкий, чем "всё или ничего". 2. Зачем нужно затенение? В общем то это следует из первого вопроса. Если возникает ситуация, в которой необходима собственная реализация метода, а он не виртуальный, то существует возможность это реализовать не затрагивая функций базового класса, завязанных на реализации данного метода в базовом классе. 3. Почему методы не виртуальны по-умолчанию? Реализация функциональности завязанная на виртуальных методах требует учёта того, что они могут быть переопределены. Реализовать функциональность под конкретные задачи - проще и экономичнее, чем проверять все возможные(и невозможные) последствия переопределения. ЗЫ PashaPash, нам скоро с гастролями можно будет выступать. Как Терренс и Филипп. ![]() -------------------- Хочешь получить мудрый совет - читай подписи участников форумов. Злой доктор Щасзаболит ![]() |
|||
|
||||
Dims |
|
||||||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1016 Регистрация: 21.11.2006 Репутация: 0 Всего: 11 |
А что значит "известен" внешний интерфейс? Ведь это же не означает просто, что известны имена методов и типы их параметров. Это означает, что известно, и что эти методы ДЕЛАЮТ. Например, метод "длина" класса "вектор" вычисляет длину вектора. Нет, вопрос совсем на другом уровне. То есть, Вы утверждаете, что разработчики C# не продумывали свойства языка, а просто сделали не так как в Java?
Однако, эти вещи были специально созданы разработчиками, имея в виду какие-то мысли. Может быть, мысли и ошибочные, но они были. А в Вашем описании C# это вещь в себе, которая не произошла ни от кого и функции которой просто есть, нипочему. Я совершенно с этим не согласен. Я думаю, что и тут были какие-то мысли, просто мы их не знаем. Почему просто в этом не признаться? Добавлено через 51 секунду
Мы разбираем не вопрос как запретить, а вопрос как разрешить. И зачем. Добавлено через 4 минуты и 49 секунд На самом деле, я понял, что такое затенение функций -- это отколовшаяся часть системы преобразования типов. Возможны как минимум два преобразования типов от типа наследника к типу предка. При первом преобразовании отражается ситуация, когда программисту просто неважен конкретный тип наследника. В этом случае он хочет работать с наследником, думая о предке, но при этом чтобы ничего не нарушалось. В этом случае у каждого объекта должна работать своя функция, то есть, все функции должны быть виртуальными. Во втором случае программист может хотеть принудительно превратить наследника в предка, обрубить ему все особенности. В этом случае методы должны браться от предка. |
||||||
|
|||||||
diadiavova |
|
|||
![]() Доктор Зло(диагност, настоящий, с лицензией и полномочиями) ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 5821 Регистрация: 14.8.2008 Где: В Коньфпольте Репутация: 31 Всего: 142 |
Честно говоря: не понимаю, как одно можно рассматривать вне контекста другого. Я говорил всего лишь о том, что ппроще запретить переопределение конкретных членов, чем описывать в документации какие члены переопределять не следует. Конкретный тип не важен тогда, когда в базовом классе либо вообще отсутствуют виртуальные члены, либо те, что есть не переопределены в производном. В этом случае при преобразовании к базовому типу объект ведёт себя так как будто это экземпляр базового типа без каких-либо изменений. Здесь же мы имеем дело с ситуацией, когда переопределёнными могут быть некоторые члены, а остальные - нет. Поведение такого объекта будет отличаться от экземпляров других классов, так же произведённых от данного, в силу того, что в нём таки будут присутствовать виртуальные члены. -------------------- Хочешь получить мудрый совет - читай подписи участников форумов. Злой доктор Щасзаболит ![]() |
|||
|
||||
Dims |
|
|||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1016 Регистрация: 21.11.2006 Репутация: 0 Всего: 11 |
Ну Вы же сами предложили рассмотреть случай -- а вдруг человек переопределит метод и это приведёт к ужасным последствиям. И я полностью с Вами согласен: проще запретить. Это можно сделать и в Джаве при помощи final. А вот когда это не запрещено, как я понимаю, по Вашей теории, и могут быть проблемы. Вопрос: какие? Хочу подчеркнуть, что тут я рассматриваю ситуацию гипотетического языка. Я слышал, что в новом Си++ ввели несколько разновидностей преобразования типов, но ещё не разбирался с ними и не уверен, что это то. Так вот, в этом гипотетическом языке все функции виртуальные, как и в Джаве. А вопрос, какую из них использовать, решается в зависимости от формы преобразования типа. По моим представлениям, после того, как над объектом было проведено "преобразовние к предку первого рода", он не перестал быть потомком. Поэтому, все функции по отношению к нему будут браться от потомка, если они там переопределены. Если к объекту применено преобразование типа "второго рода", то он полностью превращён в предка, а лишняя информация, включая переопределённые функции, стирается. |
|||
|
||||
diadiavova |
|
|||
![]() Доктор Зло(диагност, настоящий, с лицензией и полномочиями) ![]() ![]() ![]() ![]() Профиль Группа: Модератор Сообщений: 5821 Регистрация: 14.8.2008 Где: В Коньфпольте Репутация: 31 Всего: 142 |
Просто тут есть выбор: запрещать всё или только часть. В дотнетовских языках тоже есть возможность запретить дальнейшее наследование класса. Кроме того, здесь есть возможность указать, что в дальнейшем виртуальный член перестаёт быть виртуальным(то есть больше не переопределяется в производных классах). Здесь есть возможность затенения виртуальных членов(выбор между переопределением и затенением). То есть здесь можно всё то же, что и в яве, плюс дополнительные возможности.
Проблемы могут возникнуть именно когда я хочу запретить переопределение пары функций, а мне для этого прийдётся запрещать наследование всего класса. Трудно рассуждать о гипотетическом языке, но в такую модель изначально заложены грабли. Вместо тонкой настройки поведения конкретного класса (в том числе в случае приведения типов), использовать общие правила для всех типов, которые в конкретном случае нельзя изменить... Не думаю, что это наилучшее решение. При желании поведение класса при приведении типов и так можно задать. Не вижу смысла в ограничении этой возможности. Добавлено через 8 минут и 22 секунды И потом: что значит стирается информация о переопределённых членах? Члены просто перестают быть виртуальными? -------------------- Хочешь получить мудрый совет - читай подписи участников форумов. Злой доктор Щасзаболит ![]() |
|||
|
||||
PashaPash |
|
||||
Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1233 Регистрация: 3.1.2008 Репутация: 13 Всего: 49 |
ЧТО делает не означает КАК делает. Если метод длина использует внутри метод "нарисовать" и "подсчитать клетки в нарисованном векторе" (для примера), то эти методы не должны быть видны извне. Подход "явно указать что видно извне" просто безопаснее с точки зрения архитектуры чем, "все сделать видимым извне, а потом закрывать доступ". Потому что урезать интерфейс после публикации довольно сложно - всегда есть шанс что на него кто-то уже привязался. Скажем так - в java не было выбора virtual/not virtual. В C# есть выбор, и по умолчанию он сделан в сторону решения, несущего меньше потенциальных последствий. Никто тебя ни к чему не заставляет и не принуждает ![]()
Не так - не всмысле "специально сделали через ж, чтобы от java отличаться". Не так - значит продумали, рассмотрели решения в 3-4х OO-языках и выбрали лучшее. Поэтому не стоит удивляться что что-то "не так". Просто C#/.NET вышел позже чем Java, и его создатели попытались решить некоторые проблемы OOP, о которых до появления Java никто не задумывался. Но это не значит что C# - это Java от MS. C# намного ближе к C++.
Проблема в том, что ты все еще считаешь что все функции должны быть виртуальными. Подходи с классической позиции с учетом минимализма интерфейса. Полиморфизм позднего связывания - только один из 2-х видов полиморфизма в OOP, но почем-то единственный в Java. Но на java свет клином не сошелся. ![]() |
||||
|
|||||
Dims |
|
||||||||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1016 Регистрация: 21.11.2006 Репутация: 0 Всего: 11 |
Если они не видны извне, то их и нельзя переопределить. Мы же рассматриваем случай, когда метод А виден извне, используется одним из других методов Б и был переопределён в наследнике. Утверждается, что это плохая ситуация, так как программист наследника не должен знать, что метод Б использует А. Я с этим не согласен. Если разработчик предка использовал метод А в методе Б и оставил метод А открытым, то он должен был написать в инструкции, что роль метода А сводится не только к тому, что он делает нечто, но она состоит ещё и в том, что он используется методом Б. Тогда программист наследника, прочтя инструкцию, узнает об этом и будет переопределять А с умом. Если же это нежелательно, то метод А можно сделать оконечным и тогда никто его не сможет переопределить. Зато он был в Си++, опыт которого использовали при создании Джава. То есть, в Джаве специально убрали этот выбор. Намеренно.
Ну так это я и имел в виду, под "убрали" или "добавили". Учёт опыта. Не переводите вопрос в плоскость религиозных войн. Вы с одной стороны признаёте, что при создании Си-диез учитывался опыт Джавы, а с другой стороны пытаетесь принизить роль этого факта, говоря, что Си-диез это не Джава от МС. Главные фишки Джавы -- это виртуальная машина, ссылочные типы и сборщик мусора. Всё это есть и в .НЕТ. Причём исторически тоже, сперва Микрософт лицензировало Джаву, сделала свой JIT, затем отказалась от неё, сделала J++ и потом придумало свою платформу NET и C#. Влияние, на мой взгляд, самое, что ни на есть прямое. Ну примерно как в СССР при создании ЕС использовали IBM 360. Учитывая производственные мощности Микрософта и их наработки, просто неизбежно должно было произойти какое-то столкновение видений и была должна получиться своя масштабная и полноценная платформа.
Это не проблема, а моё мнение. ;)
На мой взгляд, связывание имеет отношение не к ООП, а к реализации. В моём гипотетическом языке компиллятор осуществлял бы раннее связываение при одном преобразовании типов и позднее -- при другом. |
||||||||
|
|||||||||
![]() ![]() ![]() |
Прежде чем создать тему, посмотрите сюда: | |
|
Используйте теги [code=csharp][/code] для подсветки кода. Используйтe чекбокс "транслит" если у Вас нет русских шрифтов. Что делать если Вам помогли, но отблагодарить помощника плюсом в репутацию Вы не можете(не хватает сообщений)? Пишите сюда, или отправляйте репорт. Поставим :) Так же не забывайте отмечать свой вопрос решенным, если он таковым является :) Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, mr.DUDA, THandle. |
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | Общие вопросы по .NET и C# | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |