É muito interessante para mim quais vantagens oferecem a abordagem "classe raiz global" para o framework. Em palavras simples, quais motivos resultaram na estrutura do .NET que foi projetada para ter uma classe de objeto raiz com funcionalidade geral adequada para todas as classes.
Atualmente, estamos projetando uma nova estrutura para uso interno (a estrutura da plataforma SAP) e todos nós dividimos em dois campos - primeiro quem pensa que essa estrutura deve ter raiz global, e o segundo - quem pensa oposto.
Estou no campo "raiz global". E minhas razões pelas quais essa abordagem renderia boa flexibilidade e redução de custos de desenvolvimento, porque não desenvolveremos mais a funcionalidade geral.
Portanto, estou muito interessado em saber por que motivos realmente levam os arquitetos .NET a projetar a estrutura dessa maneira.
fonte
object
na estrutura do .NET em parte porque ela fornece alguns recursos básicos em todos os objetos, comoToString()
eGetHashCode()
wait()
/notify()
/notifyAll()
eclone()
.Respostas:
A causa mais urgente
Object
são os contêineres (antes dos genéricos) que podem conter qualquer coisa em vez de ter que seguir o estilo C "Escreva novamente para tudo o que você precisa". É claro que, sem dúvida, a idéia de que tudo deveria herdar de uma classe específica e, em seguida, abusar desse fato para perder totalmente todos os pontos de segurança de tipo é tão terrível que deveria ter sido uma luz de advertência gigante sobre o envio do idioma sem genéricos e também significa issoObject
é completamente redundante para o novo código.fonte
Object
que não possa ser escritovoid *
em C ++?void*
era melhor? Não é. Se alguma coisa. é ainda pior .void*
é pior em C com todos os lançamentos implícitos do ponteiro, mas o C ++ é mais rigoroso nesse sentido. Pelo menos você deve transmitir explicitamente.Lembro-me de Eric Lippert dizendo uma vez que herdar da
System.Object
classe fornecia "o melhor valor para o cliente". [edit: sim, ele disse aqui ]:Ter tudo derivado da
System.Object
classe fornece uma confiabilidade e utilidade que eu respeito muito. Eu sei que todos os objetos terão um type (GetType
), que eles estarão alinhados com os métodos de finalização do CLR (Finalize
) e que eu posso usáGetHashCode
-los ao lidar com coleções.fonte
GetType
, você pode simplesmente estendertypeof
para substituir sua funcionalidade. Quanto aFinalize
, na verdade, muitos tipos não são completamente finalizáveis e não possuem um método Finalize significativo. Além disso, muitos tipos não são laváveis nem conversíveis em strings - especialmente tipos internos que nunca são destinados a tal coisa e nunca são distribuídos para uso público. Todos esses tipos têm suas interfaces desnecessariamente poluídas.GetType
,Finalize
ouGetHashCode
para cadaSystem.Object
. Apenas afirmei que eles estavam lá, se você os quisesse. Quanto a "suas interfaces desnecessariamente poluídas", vou me referir à minha citação original de elippert: "melhor valor para o cliente". Obviamente, a equipe do .NET estava disposta a lidar com as compensações.Eu acho que os designers de Java e C # adicionaram um objeto raiz porque era essencialmente gratuito para eles, como no almoço grátis .
Os custos de adicionar um objeto raiz são muito iguais aos custos de adicionar sua primeira função virtual. Como o CLR e a JVM são ambientes de coleta de lixo com finalizadores de objetos, você precisa ter pelo menos um virtual para o seu
java.lang.Object.finalize
ou para o seuSystem.Object.Finalize
método. Portanto, os custos de adicionar seu objeto raiz já são pré-pagos, você pode obter todos os benefícios sem precisar pagar por isso. Esse é o melhor dos dois mundos: os usuários que precisam de uma classe raiz comum obtêm o que desejam e os usuários que não se importam menos poderiam programar como se não estivesse lá.fonte
Parece-me que você tem três perguntas aqui: uma é por que uma raiz comum foi introduzida no .NET, a segunda é quais são as vantagens e desvantagens disso e a terceira é se é uma boa idéia para um elemento da estrutura ter uma global raiz.
Por que o .NET tem uma raiz comum?
No sentido mais técnico, ter uma raiz comum é essencial para a reflexão e para os contêineres pré-genéricos.
Além disso, que eu saiba, ter uma raiz comum com métodos básicos como
equals()
ehashCode()
foi recebido muito positivamente em Java, e o C # foi influenciado por (entre outros) Java, então eles queriam ter esse recurso também.Quais são as vantagens e desvantagens de ter uma raiz comum em um idioma?
A vantagem de ter uma raiz comum:
equals()
ehashCode()
. Isso significa, por exemplo, que todos os objetos em Java ou C # podem ser usados em um mapa de hash - compare essa situação com C ++.printf
método semelhante.object
. Talvez não seja muito comum, mas sem uma raiz comum não seria possível.A desvantagem de ter uma raiz comum:
Você deve ir com uma raiz comum em seu projeto de estrutura?
Muito subjetivo, é claro, mas quando eu olho para a lista pró-contra acima, eu definitivamente respondo que sim . Especificamente, o último marcador na lista profissional - flexibilidade de alterar posteriormente o comportamento de todos os objetos alterando a raiz - se torna muito útil em uma plataforma, na qual as alterações na raiz têm mais probabilidade de serem aceitas pelos clientes do que as alterações em um todo língua.
Além disso - embora isso seja ainda mais subjetivo - acho o conceito de uma raiz comum muito elegante e atraente. Isso também facilita o uso de algumas ferramentas, por exemplo, agora é fácil solicitar que uma ferramenta mostre todos os descendentes dessa raiz comum e receba uma rápida e boa visão geral dos tipos da estrutura.
É claro que os tipos precisam estar pelo menos um pouco relacionados a isso e, em particular, eu nunca sacrificaria a regra "é-a" apenas para ter uma raiz comum.
fonte
Matematicamente, é um pouco mais elegante ter um sistema de tipos que contém top , e permite definir um idioma um pouco mais completamente.
Se você estiver criando uma estrutura, as coisas necessariamente serão consumidas por uma base de código existente. Você não pode ter um supertipo universal, já que existem todos os outros tipos em qualquer que seja o consumo da estrutura.
No que ponto, a decisão de ter uma classe base comum depende do que você está fazendo. É muito raro que muitas coisas tenham um comportamento comum e que seja útil fazer referência a essas coisas apenas pelo comportamento comum.
Mas acontece. Se isso acontecer, siga em frente abstraindo esse comportamento comum.
fonte
Eles pressionaram por um objeto raiz porque queriam que todas as classes na estrutura suportassem certas coisas (obter um código hash, converter em uma string, verificações de igualdade etc.). Tanto o C # quanto o Java consideraram útil colocar todos esses recursos comuns de um Objeto em alguma classe raiz.
Lembre-se de que eles não estão violando nenhum princípio de POO. Qualquer coisa na classe Object faz sentido em qualquer subclasse (leia-se: qualquer classe) no sistema. Se você optar por adotar esse design, siga este padrão. Ou seja, não inclua itens na raiz que não pertençam a todas as classes do sistema. Se você seguir esta regra com cuidado, não vejo motivo para não ter uma classe raiz que contenha código comum e útil para todo o sistema.
fonte
Por alguns motivos, funcionalidade comum que todas as classes filho podem compartilhar. E também deu aos escritores da estrutura a capacidade de escrever muitas outras funcionalidades na estrutura que tudo poderia usar. Um exemplo seria o cache e as sessões do ASP.NET. Quase tudo pode ser armazenado neles, porque eles escreveram os métodos add para aceitar objetos.
Uma classe raiz pode ser muito atraente, mas é muito, muito fácil de usar indevidamente. Seria possível ter uma interface raiz? E apenas tem um ou dois métodos pequenos? Ou isso adicionaria muito mais código do que o necessário para sua estrutura?
Pergunto, estou curioso sobre qual funcionalidade você precisa expor a todas as classes possíveis na estrutura que está escrevendo. Isso realmente será usado por todos os objetos? Ou pela maioria deles? E uma vez criada a classe raiz, como você impedirá que as pessoas adicionem funcionalidade aleatória a ela? Ou variáveis aleatórias que eles querem ser "globais"?
Vários anos atrás, havia uma aplicação muito grande onde eu criei uma classe raiz. Após alguns meses de desenvolvimento, foi preenchido por código que não tinha nenhum negócio lá. Estávamos convertendo um aplicativo ASP antigo e a classe raiz era a substituição do antigo arquivo global.inc que tínhamos usado no passado. Tive que aprender essa lição da maneira mais difícil.
fonte
Todos os objetos de heap autônomos herdam
Object
; isso faz sentido porque todos os objetos de heap independentes devem ter certos aspectos em comum, como um meio de identificar seu tipo. Caso contrário, se o coletor de lixo tivesse uma referência a um objeto de heap de tipo desconhecido, não haveria como saber quais bits do blob de memória associado a esse objeto deveriam ser considerados como referências a outros objetos de heap.Além disso, dentro do sistema de tipos, é conveniente usar o mesmo mecanismo para definir os membros das estruturas e os membros das classes. O comportamento dos locais de armazenamento do tipo valor (variáveis, parâmetros, campos, slots de matriz etc.) é muito diferente do dos locais de armazenamento do tipo classe, mas essas diferenças de comportamento são alcançadas nos compiladores de código-fonte e no mecanismo de execução (incluindo o compilador JIT) em vez de ser expresso no sistema de tipos.
Uma conseqüência disso é que a definição de um tipo de valor define efetivamente dois tipos - um tipo de local de armazenamento e um tipo de objeto de heap. O primeiro pode ser implicitamente convertido no último, e o último pode ser convertido no primeiro via typecast. Os dois tipos de conversão funcionam copiando todos os campos públicos e privados de uma instância do tipo em questão para outra. Além disso, é possível usar restrições genéricas para chamar membros da interface diretamente em um local de armazenamento de valor, sem fazer uma cópia primeiro.
Tudo isso é importante porque as referências a objetos de heap do tipo valor se comportam como referências de classe e não como tipos de valor. Considere, por exemplo, o seguinte código:
Se o
testEnumerator()
métodoit
receber um local de armazenamento do tipo valor, receberá uma instância cujos campos públicos e privados serão copiados do valor passado. A variável localit2
manterá outra instância cujos campos são todos copiadosit
. ChamandoMoveNext
emit2
não afetaráit
.Se o código acima for passado para um local de armazenamento do tipo de classe, o valor passado
it
, eit2
, se referirá ao mesmo objeto e, portanto, a chamadaMoveNext()
de qualquer um deles o chamará efetivamente em todos eles.Note-se que lançando
List<String>.Enumerator
paraIEnumerator<String>
efetivamente transforma-lo a partir de um tipo de valor para um tipo de classe. O tipo do objeto de heap é,List<String>.Enumerator
mas seu comportamento será muito diferente do tipo de valor com o mesmo nome.fonte
List<T>.Enumerator
que é armazenado como aList<T>.Enumerator
é significativamente diferente do comportamento de um que é armazenado em umIEnumerator<T>
, pois o armazenamento de um valor do tipo anterior em um local de armazenamento de qualquer tipo fará uma cópia do estado do enumerador, enquanto armazenar um valor desse último tipo em um local de armazenamento que também seja do último tipo, não fará uma cópia.Object
tem nada a ver com esse fato.System.Int32
também é uma instância doSystem.Object
, mas um local de armazenamento do tipoSystem.Int32
não contém nem umaSystem.Object
instância nem uma referência a uma.Esse design realmente remonta ao Smalltalk, que eu consideraria em grande parte como uma tentativa de buscar a orientação a objetos à custa de quase toda e qualquer outra preocupação. Como tal, tende (na minha opinião) a usar a orientação a objetos, mesmo quando outras técnicas são provavelmente (ou até certamente) superiores.
Ter uma única hierarquia com
Object
(ou algo semelhante) na raiz facilita bastante (por exemplo) criar suas classes de coleção como coleções deObject
, portanto, é trivial que uma coleção contenha qualquer tipo de objeto.Em troca dessa pequena vantagem, você obtém uma série de desvantagens. Primeiro, do ponto de vista do design, você acaba tendo algumas idéias realmente insanas. Pelo menos de acordo com a visão Java do universo, o que o ateísmo e a floresta têm em comum? Que ambos têm códigos de hash! Um mapa é uma coleção? De acordo com Java, não, não é!
Nos anos 70, quando o Smalltalk estava sendo projetado, esse tipo de absurdo foi aceito, principalmente porque ninguém havia projetado uma alternativa razoável. O Smalltalk foi finalizado em 1980 e, em 1983, Ada (que inclui genéricos) foi projetado. Embora Ada nunca tenha alcançado o tipo de popularidade que alguns previram, seus genéricos foram suficientes para suportar coleções de objetos de tipos arbitrários - sem a loucura inerente às hierarquias monolíticas.
Quando o Java (e, em menor grau, o .NET) foram projetados, a hierarquia monolítica de classes provavelmente foi vista como uma opção "segura" - com problemas, mas principalmente problemas conhecidos . A programação genérica, por outro lado, foi uma que quase todo mundo (até então) percebeu que era pelo menos teoricamente uma abordagem muito melhor para o problema, mas que muitos desenvolvedores com orientação comercial consideravam pouco explorada e / ou arriscada (isto é, no mundo comercial). Ada foi amplamente descartado como um fracasso).
Deixe-me ser claro: a hierarquia monolítica foi um erro. As razões para esse erro eram pelo menos compreensíveis, mas era um erro de qualquer maneira. É um design ruim e seus problemas de design permeiam quase todo o código que o utiliza.
Para um novo design hoje, no entanto, não há uma pergunta razoável: usar uma hierarquia monolítica é um erro claro e uma má idéia.
fonte
O que você quer fazer é realmente interessante, é difícil provar se está certo ou errado até você terminar.
Aqui estão algumas coisas para pensar:
Essas são as únicas duas coisas que podem se beneficiar de uma raiz compartilhada. Em um idioma, é usado para definir alguns métodos muito comuns e permitir que você passe objetos que ainda não foram definidos, mas eles não devem se aplicar a você.
Além disso, por experiência pessoal:
Eu usei um kit de ferramentas uma vez escrito por um desenvolvedor que teve como background o Smalltalk. Ele fez isso para que todas as suas classes "Data" estendessem uma única classe e todos os métodos a classificassem - até agora tudo bem. O problema é que as diferentes classes de dados nem sempre eram intercambiáveis, então agora meu editor e compilador não podiam me dar QUALQUER ajuda sobre o que passar para uma determinada situação e eu tinha que me referir aos documentos dele, que nem sempre ajudavam. .
Essa foi a biblioteca mais difícil de usar que eu já tive que lidar.
fonte
Porque esse é o caminho do POO. É importante ter um tipo (objeto) que possa se referir a qualquer coisa. Um argumento do tipo objeto pode aceitar qualquer coisa. Também há herança, você pode ter certeza de que ToString (), GetHashCode () etc estão disponíveis em tudo e qualquer coisa.
Até a Oracle percebeu a importância de ter esse tipo de base e planeja remover as primitivas em Java perto de 2017
fonte
ToString()
eGetType()
estará em todos os objetos. Provavelmente vale ressaltar que você pode usar uma variável do tipoobject
para armazenar qualquer objeto .NET.