Мне очень интересно, какие преимущества дает подход «глобального корневого класса» для фреймворка. Проще говоря, по каким причинам .NET Framework был разработан, чтобы иметь один класс корневых объектов с общей функциональностью, подходящей для всех классов.
В настоящее время мы разрабатываем новую платформу для внутреннего использования (платформу на платформе SAP), и все мы разделились на два лагеря - первый, кто считает, что у платформы должен быть глобальный корень, и второй - кто думает об обратном.
Я нахожусь в "глобальном корневом" лагере. И мои причины того, что такой подход дает хорошую гибкость и снижение затрат на разработку, приводят к тому, что мы больше не будем развивать общую функциональность.
Итак, мне очень интересно узнать, по каким причинам действительно .NET-архитекторы толкают к разработке фреймворка таким образом.
источник
object
Отчасти в фреймворке .NET есть корень, потому что он предоставляет некоторые базовые возможности для всех объектов, таких какToString()
иGetHashCode()
wait()
/notify()
/notifyAll()
иclone()
.Ответы:
Наиболее неотложной причиной для этого
Object
являются контейнеры (до генериков), которые могут содержать что угодно, вместо того, чтобы идти в стиле C: «Напишите это снова для всего, что вам нужно». Конечно, можно утверждать, что идея о том, что все должно наследоваться от определенного класса, а затем злоупотреблять этим фактом, чтобы полностью потерять каждую частичку безопасности типов, настолько ужасна, что это должен был быть гигантский предупреждающий сигнал об отправке языка без обобщений, а также означает, что этоObject
полностью избыточно для нового кода.источник
Object
который не может быть написанvoid *
в C ++?void*
лучше? Это не так. Если что-нибудь. это даже хуже .void*
хуже в C со всеми неявными приведениями указателей, но C ++ более строг в этом отношении. По крайней мере, вы должны бросить явно.Я помню, как Эрик Липперт однажды сказал, что наследование от
System.Object
класса обеспечило «лучшую ценность для клиента». [править: да, он сказал это здесь ]:Наличие всего, что происходит от
System.Object
класса, обеспечивает надежность и полезность, которую я часто уважаю. Я знаю, что все объекты будут иметь тип (GetType
), что они будут соответствовать методам финализации CLR (Finalize
), и что я могу использоватьGetHashCode
их при работе с коллекциями.источник
GetType
, вы можете просто расширить,typeof
чтобы заменить его функциональность. Что касаетсяFinalize
, то на самом деле, многие типы совсем не являются финализируемыми и не имеют значимого метода Finalize. Кроме того, многие типы не могут быть хэшируемыми или конвертируемыми в строковые, особенно внутренние типы, которые никогда не предназначены для таких вещей и никогда не передаются для публичного использования. Все эти типы имеют свои интерфейсы без необходимости загрязнены.GetType
,Finalize
илиGetHashCode
для каждогоSystem.Object
. Я просто заявил, что они были там, если вы хотели их. Что касается «ненужного загрязнения их интерфейсов», я ссылаюсь на мою оригинальную цитату из elippert: «лучшая ценность для клиента». Очевидно, что команда .NET была готова иметь дело с компромиссами.Я думаю, что разработчики Java и C # добавили корневой объект, потому что он был для них по сути бесплатным, как в бесплатном обеде .
Затраты на добавление корневого объекта во многом равны затратам на добавление вашей первой виртуальной функции. Поскольку CLR и JVM являются средами для сборки мусора с финализаторами объектов, вам необходимо иметь хотя бы одну виртуальную среду для вашего
java.lang.Object.finalize
или для вашегоSystem.Object.Finalize
метода. Следовательно, затраты на добавление корневого объекта уже оплачены, вы можете получить все преимущества, не оплачивая его. Это лучшее из обоих миров: пользователи, которым нужен общий корневой класс, получают то, что хотят, а пользователи, которым все равно, могут программировать так, как если бы их не было.источник
Мне кажется, у вас есть три вопроса: один - почему общий корень был введен в .NET, второй - каковы его преимущества и недостатки, а третий - хорошая ли идея, чтобы элемент инфраструктуры имел глобальный корень.
Почему .NET имеет общий корень?
В самом техническом смысле наличие общего корня необходимо для рефлексии и для контейнеров, предшествующих дженерику.
Кроме того, насколько мне известно, наличие общего корня с базовыми методами, такими как
equals()
и,hashCode()
было воспринято очень положительно в Java, и на C # оказала влияние (среди прочего) Java, поэтому они также хотели иметь эту функцию.Каковы преимущества и недостатки наличия общего корня в языке?
Преимущество наличия общего корня:
equals()
иhashCode()
. Это означает, например, что каждый объект в Java или C # может использоваться в хэш-карте - сравните эту ситуацию с C ++.printf
-подобного метода.object
. Возможно, не очень часто, но без общего корня это было бы невозможно.Недостаток наличия общего корня:
Должны ли вы идти с общим рутом в вашем фреймворке?
Конечно, очень субъективно, но когда я посмотрю на приведенный выше список проконов, я определенно отвечу да . В частности, последний пункт в списке профессионалов - гибкость последующего изменения поведения всех объектов путем изменения корня - становится очень полезным в платформе, на которой изменения в корне, скорее всего, будут приняты клиентами, чем изменениями в целом язык.
Кроме того - хотя это еще более субъективно - я нахожу концепцию общего корня очень элегантной и привлекательной. Это также облегчает использование некоторых инструментов, например, теперь легко попросить инструмент показать всех потомков этого общего корня и получить быстрый, хороший обзор типов платформы.
Конечно, типы должны быть хотя бы немного связаны для этого, и, в частности, я бы никогда не пожертвовал правилом «есть» только ради общего корня.
источник
С математической точки зрения немного элегантнее иметь систему типов, которая содержит top и позволяет вам определить язык более полно.
Если вы создаете фреймворк, то вещи обязательно будут использованы существующей кодовой базой. У вас не может быть универсального супертипа, так как существуют все другие типы в том, что потребляет каркас.
На этом этапе решение иметь общий базовый класс зависит от того, что вы делаете. Очень редко многие вещи имеют общее поведение, и что полезно ссылаться на эти вещи только через общее поведение.
Но так бывает. Если это так, то продолжайте абстрагироваться от этого общего поведения.
источник
Это подтолкнуло к корневому объекту, потому что они хотели, чтобы все классы в структуре поддерживали определенные вещи (получение хеш-кода, преобразование в строку, проверки на равенство и т. Д.). И C #, и Java сочли полезным поместить все эти общие свойства объекта в некоторый корневой класс.
Имейте в виду, что они не нарушают никаких принципов ООП или чего-либо еще. Все в классе Object имеет смысл в любом подклассе (читай: любой класс) в системе. Если вы решили принять этот дизайн, убедитесь, что вы следуете этому шаблону. То есть не включайте в корень вещи, которые не принадлежат каждому отдельному классу в вашей системе. Если вы будете тщательно следовать этому правилу, я не вижу причин, по которым у вас не должно быть корневого класса, содержащего полезный общий код для всей системы.
источник
Пара причин, общая функциональность, которой могут поделиться все дочерние классы. И это также дало авторам фреймворка возможность записывать множество других функций в фреймворк, который мог использовать каждый. Примером может служить кэширование и сессии ASP.NET. В них можно хранить практически все, потому что они написали методы add для принятия объектов.
Корневой класс может быть очень заманчивым, но его очень, очень легко использовать. Можно ли вместо этого иметь корневой интерфейс? И просто есть один или два маленьких метода? Или это добавит намного больше кода, чем требуется для вашей платформы?
Я спрашиваю, мне любопытно, какую функциональность вы должны предоставить всем возможным классам в фреймворке, который вы пишете. Будет ли это действительно использоваться всеми объектами? Или большинством из них? И как только вы создадите корневой класс, как вы будете препятствовать добавлению в него случайных функций? Или случайные величины они хотят быть "глобальными"?
Несколько лет назад было очень большое приложение, где я создал корневой класс. После нескольких месяцев разработки он был заполнен кодом, в котором не было бизнеса. Мы конвертировали старое приложение ASP, и корневой класс был заменой старого файла global.inc, который мы использовали в прошлом. Пришлось усвоить этот урок нелегким путем.
источник
Все автономные объекты кучи наследуются от
Object
; это имеет смысл, потому что все автономные объекты кучи должны иметь определенные общие аспекты, такие как средства идентификации их типа. В противном случае, если сборщик мусора имеет ссылку на объект кучи неизвестного типа, он не сможет узнать, какие биты в блоке памяти, связанном с этим объектом, следует рассматривать как ссылки на другие объекты кучи.Кроме того, в системе типов удобно использовать один и тот же механизм для определения членов структур и членов классов. Поведение мест хранения типов значений (переменных, параметров, полей, слотов массивов и т. Д.) Очень отличается от поведения мест хранения классов, но такие поведенческие различия достигаются в компиляторах исходного кода и механизме выполнения (включая компилятор JIT), а не выражается в системе типов.
Одним из следствий этого является то, что определение типа значения эффективно определяет два типа - тип места хранения и тип объекта кучи. Первый может быть неявно преобразован в последний, а последний может быть преобразован в первый посредством typecast. Оба типа преобразования работают путем копирования всех открытых и закрытых полей из одного экземпляра рассматриваемого типа в другой. Кроме того, возможно использование общих ограничений для непосредственного вызова элементов интерфейса в хранилище с типом значения, без предварительного его копирования.
Все это важно, потому что ссылки на объекты кучи типа значения ведут себя как ссылки на классы, а не как типы значений. Рассмотрим, например, следующий код:
Если
testEnumerator()
метод передается в хранилище типа значения,it
он получит экземпляр, чьи открытые и закрытые поля будут скопированы из переданного значения. Локальная переменнаяit2
будет содержать другой экземпляр, все поля которого скопированыit
. ВызовMoveNext
наit2
не повлияетit
.Если приведенному выше коду передается место хранения типа класса, то переданное значение
it
иit2
все будут ссылаться на один и тот же объект, и, таким образом, вызовMoveNext()
любого из них будет эффективно вызывать его для всех них.Обратите внимание, что приведение
List<String>.Enumerator
кIEnumerator<String>
эффективно превращает его из типа значения в тип класса. Тип объекта кучи есть,List<String>.Enumerator
но его поведение будет сильно отличаться от типа значения с тем же именем.источник
List<T>.Enumerator
которое хранится как aList<T>.Enumerator
, существенно отличается от поведения того, которое хранится в aIEnumerator<T>
, в том, что сохранение значения предыдущего типа в месте хранения любого типа сделает копию состояния перечислителя, при сохранении значения последнего типа в хранилище, которое также относится к последнему типу, копирование не производится.Object
имеет ничего общего с этим фактом.System.Int32
также является экземпляромSystem.Object
, но место хранения типа неSystem.Int32
содержит ниSystem.Object
экземпляра, ни ссылки на него.Этот дизайн действительно восходит к Smalltalk, который я бы рассматривал в основном как попытку добиться ориентации объекта за счет почти любых других проблем. Как таковой, он имеет тенденцию (на мой взгляд) использовать объектную ориентацию, даже когда другие методы, вероятно (или даже наверняка) превосходят.
Наличие единственной иерархии с
Object
(или чем-то похожим) в корне позволяет довольно легко (для одного примера) создавать классы коллекций в виде коллекцийObject
, поэтому тривиально для коллекции содержать любой тип объекта.В обмен на это довольно незначительное преимущество вы получаете целый ряд недостатков. Во-первых, с точки зрения дизайна вы получите действительно безумные идеи. По крайней мере, согласно взгляду Вселенной на Java, что общего между Атеизмом и Лесом? Что у них обоих есть хэш-коды! Является ли карта коллекцией? По словам Java, нет, это не так!
В 70-х, когда разрабатывался Smalltalk, такая чепуха была принята, прежде всего потому, что никто не придумал разумной альтернативы. Smalltalk был завершен в 1980 году, а к 1983 году была разработана Ada (которая включает в себя дженерики). Хотя Ада так и не достигла той популярности, которую некоторые предсказывали, ее обобщений было достаточно для поддержки коллекций объектов произвольных типов - без безумия, присущего монолитным иерархиям.
При разработке Java (и, в меньшей степени, .NET) монолитная иерархия классов, вероятно, рассматривалась как «безопасный» выбор - с проблемами, но в основном с известными проблемами. Родовое программирование, напротив, было тем, что почти все (даже тогда) поняли, было, по крайней мере, теоретически гораздо лучшим подходом к проблеме, но тот, который многие коммерчески ориентированные разработчики считали довольно плохо изученным и / или рискованным (то есть в коммерческом мире). Ада была в основном отклонена как провал).
Позвольте мне быть предельно ясным: монолитная иерархия была ошибкой. Причины этой ошибки были, по крайней мере, понятны, но в любом случае это была ошибка. Это плохой дизайн, и его проблемы с дизайном пронизывают почти весь код, использующий его.
Однако для нового дизайна сегодня нет разумного вопроса: использование монолитной иерархии - явная ошибка и плохая идея.
источник
То, что вы хотите сделать, на самом деле интересно, трудно доказать, правильно это или нет, пока вы не закончили.
Вот некоторые вещи для размышления:
Это единственные две вещи, которые могут извлечь пользу из общего корня. В языке это используется для определения нескольких очень распространенных методов и позволяет вам передавать объекты, которые еще не были определены, но они не должны применяться к вам.
Также из личного опыта:
Я использовал инструментарий, когда-то написанный разработчиком, который имел опыт Smalltalk. Он сделал так, чтобы все его классы «Данные» расширяли один класс, и все методы взяли этот класс - пока все хорошо. Проблема заключается в том, что разные классы данных не всегда были взаимозаменяемыми, поэтому теперь мой редактор и компилятор не могли дать мне ЛЮБОЙ помощи в отношении того, что переходить в данную ситуацию, и мне пришлось обращаться к его документам, которые не всегда помогали. ,
Это была самая трудная в использовании библиотека, с которой мне когда-либо приходилось иметь дело.
источник
Потому что это путь ООП. Важно иметь тип (объект), который может ссылаться на что угодно. Аргумент типа object может принимать все, что угодно. Также есть наследование, вы можете быть уверены, что ToString (), GetHashCode () и т. Д. Доступны для всего и вся.
Даже Oracle осознал важность наличия такого базового типа и планирует удалить примитивы в Java около 2017 года.
источник
ToString()
иGetType()
будет на каждом объекте. Вероятно, стоит указать, что вы можете использовать переменную типаobject
для хранения любого объекта .NET.