Por exemplo, eu tinha visto alguns códigos que criam um fragmento como este:
Fragment myFragment=new MyFragment();
que declara uma variável como fragmento em vez de MyFragment, que MyFragment é uma classe filho de fragmento. Não estou satisfeito com esta linha de códigos porque acho que esse código deve ser:
MyFragment myFragment=new MyFragment();
o que é mais específico, isso é verdade?
Ou, na generalização da questão, é uma má prática usar:
Parent x=new Child();
ao invés de
Child x=new Child();
se podemos mudar o primeiro para o último sem erro de compilação?
Parent x = new Child()
é uma boa idéia.Respostas:
Depende do contexto, mas eu diria que você deve declarar o tipo mais abstrato possível. Dessa forma, seu código será o mais genérico possível e não dependerá de detalhes irrelevantes.
Um exemplo seria ter um
LinkedList
e doArrayList
qual ambos descendemList
. Se o código funcionaria igualmente bem com qualquer tipo de lista, não há razão para restringi-lo arbitrariamente a uma das subclasses.fonte
List list = new ArrayList()
: O código que usa a lista não se importa com o tipo de lista, apenas com o contrato da interface da Lista. No futuro, poderíamos mudar facilmente para uma implementação de Lista diferente e o código subjacente não precisaria ser alterado ou atualizado.Object
!A resposta de JacquesB está correta, embora um pouco abstrata. Deixe-me enfatizar alguns aspectos mais claramente:
No seu snippet de código, você usa explicitamente a
new
palavra - chave e talvez queira continuar com outras etapas de inicialização que provavelmente são específicas para o tipo concreto: é necessário declarar a variável com esse tipo concreto específico.A situação é diferente quando você obtém esse item de outro lugar, por exemplo
IFragment fragment = SomeFactory.GiveMeFragments(...);
. Aqui, a fábrica normalmente deve retornar o tipo mais abstrato possível e o código descendente não deve depender dos detalhes da implementação.fonte
Existem diferenças de desempenho potencialmente.
Se a classe derivada for selada, o compilador poderá incorporar e otimizar ou eliminar o que de outra forma seriam chamadas virtuais. Portanto, pode haver uma diferença significativa de desempenho.
Exemplo:
ToString
é uma chamada virtual, masString
está selada. AtivadoString
,ToString
é um no-op; portanto, se você declararobject
que é uma chamada virtual, se declarar queString
o compilador sabe que nenhuma classe derivada substituiu o método porque a classe está selada, então isso é um no-op. Considerações semelhantes aplicam-se aArrayList
vsLinkedList
.Portanto, se você conhece o tipo concreto do objeto (e não há motivo para encapsulá-lo), você deve declarar esse tipo. Desde que você acabou de criar o objeto, você conhece o tipo concreto.
fonte
Parent p = new Child()
é sempre uma criança ..A principal diferença é o nível de acesso necessário. E toda boa resposta sobre abstração envolve animais, pelo que me disseram.
Suponhamos que você tenha alguns animais -
Cat
Bird
eDog
. Agora, estes animais têm alguns comportamentos comuns -move()
,eat()
espeak()
. Todos comem de maneira diferente e falam de maneira diferente, mas se eu preciso que meu animal coma ou fale, eu realmente não me importo com o que eles fazem.Mas às vezes eu faço. Eu nunca endureci um gato de guarda ou um pássaro de guarda, mas tenho um cão de guarda. Então, quando alguém invade minha casa, não posso confiar apenas em falar para espantar o intruso. Eu realmente preciso que meu cachorro latir - isso é diferente do que ele fala, que é apenas uma brincadeira.
Então, no código que requer um invasor, eu realmente preciso fazer
Dog fido = getDog(); fido.barkMenancingly();
Mas, na maioria das vezes, posso alegremente que qualquer animal faça truques onde mia, mia ou twita por guloseimas.
Animal pet = getAnimal(); pet.speak();
Para ser mais concreto, o ArrayList possui alguns métodos que
List
não. NotavelmentetrimToSize()
. Agora, em 99% dos casos, não nos importamos com isso. AList
quase nunca é suficientemente grande para nós para cuidar. Mas há momentos em que é. Portanto, ocasionalmente, precisamos solicitar especificamente que umArrayList
executetrimToSize()
e reduza sua matriz de apoio.Observe que isso não precisa ser feito na construção - isso pode ser feito através de um método de retorno ou mesmo de um elenco, se tivermos certeza absoluta.
fonte
De um modo geral, você deve usar
ou
para variáveis locais, a menos que haja um bom motivo para a variável ter um tipo diferente do que é inicializado.
(Aqui, estou ignorando o fato de que o código C ++ provavelmente deve estar usando um ponteiro inteligente. Há casos que não ocorrem com mais de uma linha e sem mais de uma linha.)
A idéia geral aqui é com linguagens que suportam a detecção automática de variáveis de tipo, usando-a para facilitar a leitura do código e faz a coisa certa (ou seja, o sistema de tipos do compilador pode otimizar o máximo possível e alterar a inicialização para ser uma nova tipo funciona bem se a maioria dos abstratos tiver sido usada).
fonte
Bom porque é fácil de entender e modificar este código:
Bom porque comunica intenção:
Ok, porque é comum e fácil de entender:
A única opção que é realmente ruim é usar
Parent
seChild
tiver apenas um métodoDoSomething()
. É ruim porque comunica intenção incorretamente:Agora, vamos elaborar um pouco mais sobre o caso em que a pergunta especificamente é feita:
Vamos formatar isso de maneira diferente, fazendo na segunda metade uma chamada de função:
Isso pode ser reduzido para:
Aqui, suponho que seja amplamente aceito que
WhichType
deve ser o tipo mais básico / abstrato que permite que a função funcione (a menos que haja problemas de desempenho). Nesse caso, o tipo apropriado seria ouParent
, ou algoParent
deriva.Essa linha de raciocínio explica por que usar o tipo
Parent
é uma boa escolha, mas não entra no clima, as outras opções são ruins (não são).fonte
tl; dr - O uso
Child
excessivoParent
é preferível no escopo local. Isso não apenas ajuda na legibilidade, mas também é necessário garantir que a resolução do método sobrecarregado funcione corretamente e ajude a permitir a compilação eficiente.No escopo local,
Conceitualmente, trata-se de manter o máximo de informações de tipo possível. Se fizermos o downgrade para
Parent
, estamos basicamente retirando informações de tipo que poderiam ter sido úteis.Manter as informações completas sobre o tipo tem quatro vantagens principais:
Vantagem 1: Mais informações para o compilador
O tipo aparente é usado na resolução do método sobrecarregado e na otimização.
Exemplo: Resolução de método sobrecarregado
No exemplo acima, ambas as
foo()
chamadas funcionam , mas em um caso, obtemos a melhor resolução do método sobrecarregado.Exemplo: Otimização do Compilador
No exemplo acima, as duas
.Foo()
chamadas finalmente chamam o mesmooverride
método que retorna2
. Apenas, no primeiro caso, há uma pesquisa de método virtual para encontrar o método correto; essa pesquisa de método virtual não é necessária no segundo caso, uma vez que é desse métodosealed
.Agradecemos a @Ben, que forneceu um exemplo semelhante em sua resposta .
Vantagem 2: Mais informações para o leitor
Conhecer o tipo exato, ou seja
Child
, fornece mais informações para quem está lendo o código, facilitando a visualização do que o programa está fazendo.Claro, talvez não importa para o código real uma vez que ambos
parent.Foo();
echild.Foo();
faz sentido, mas para alguém ver o código pela primeira vez, mais informações é simplesmente útil.Além disso, dependendo do seu ambiente de desenvolvimento, o IDE pode ser capaz de fornecer dicas de ferramentas mais úteis e metadados sobre
Child
aParent
.Vantagem 3: Código mais limpo e mais padronizado
A maioria dos exemplos de código C # que eu tenho visto ultimamente usa
var
, o que é basicamente uma abreviação deChild
.Ver uma
var
declaração de não declaração simplesmente desaparece; se houver uma razão situacional para isso, incrível, mas, caso contrário, parece antipadrão.Vantagem 4: Lógica de programa mais mutável para prototipagem
As três primeiras vantagens foram as grandes; esse é muito mais situacional.
Ainda assim, para pessoas que usam código como outros usam o Excel, estamos constantemente alterando nosso código. Talvez a gente não precisa chamar um método único para
Child
a presente versão do código, mas pode redirecionar ou refazer o código mais tarde.A vantagem de um sistema de tipo forte é que ele nos fornece certos metadados sobre a lógica do programa, tornando as possibilidades mais aparentes. Isso é incrivelmente útil na criação de protótipos, por isso é melhor mantê-lo sempre que possível.
Sumário
O uso de
Parent
bagunças na resolução do método sobrecarregado, inibe algumas otimizações do compilador, remove as informações do leitor e torna o código mais feio.Usar
var
é realmente o caminho a percorrer. É rápido, limpo, padronizado e ajuda o compilador e o IDE a fazer seu trabalho corretamente.Importante : Esta resposta é sobre
Parent
vs.Child
no escopo local de um método. A questão deParent
vs.Child
é muito diferente para tipos de retorno, argumentos e campos de classe.fonte
Parent list = new Child()
não faz muito sentido. O código que cria diretamente a instância concreta de um objeto (em vez de indireto através de fábricas, métodos de fábrica etc.) possui informações perfeitas sobre o tipo que está sendo criado; pode ser necessário interagir com a interface concreta para configurar o objeto recém-criado etc. O escopo local não é onde você obtém flexibilidade. Flexibilidade é alcançada quando você expõe o objeto recém-criado para um cliente de sua classe usando uma interface mais abstrato (fábricas e métodos de fábrica são um exemplo perfeito)Parent p = new Child(); p.doSomething();
eChild c = new Child; c.doSomething();
ter um comportamento diferente? Talvez isso seja uma peculiaridade em alguma linguagem, mas é suposto que o objeto controla o comportamento, não a referência. Essa é a beleza da herança! Contanto que você possa confiar nas implementações filhas para cumprir o contrato da implementação pai, você estará pronto.new
palavra - chave em C # , e quando há várias definições, por exemplo, com herança múltipla em C ++ .Parent
é uminterface
.Dado
parentType foo = new childType1();
, o código será limitado ao uso de métodos do tipo paifoo
, mas poderá armazenar emfoo
uma referência a qualquer objeto cujo tipo deriva deparent
- e não apenas de instâncias dechildType1
. Se a novachildType1
instância for a única coisa para a qualfoo
uma referência será mantida, será melhor declararfoo
o tipo comochildType1
. Por outro lado, se forfoo
necessário manter referências a outros tipos, tê-lo declarado comoparentType
tornará isso possível.fonte
Eu sugiro usar
ao invés de
O último perde informações enquanto o primeiro não.
Você pode fazer tudo com o primeiro que você pode fazer com o último, mas não vice-versa.
Para elaborar mais o ponteiro, vamos ter vários níveis de herança.
Base
->DerivedL1
->DerivedL2
E você deseja inicializar o resultado de
new DerivedL2()
para uma variável. Usar um tipo de base deixa a opção de usarBase
ouDerivedL1
.Você pode usar
ou
Não vejo nenhuma maneira lógica de preferir uma à outra.
Se você usar
não há o que discutir.
fonte
Base
(supondo que funcione). Você prefere o mais específico, AFAIK a maioria prefere o menos específico. Eu diria que, para variáveis locais, isso pouco importa.Base x = new DerivedL2();
, fica mais restrito e isso permite que você troque outro (neto) filho com o mínimo de esforço. Além disso, você vê imediatamente que é possível. +++ Concordamos quevoid f(Base)
é melhor do quevoid f(DerivedL2)
?Eu sugeriria sempre retornar ou armazenar variáveis como o tipo mais específico, mas aceitar parâmetros como os tipos mais amplos.
Por exemplo
Os parâmetros são
List<T>
, um tipo muito geral.ArrayList<T>
,LinkedList<T>
e outros poderiam ser aceitos por esse método.Importante, o tipo de retorno é
LinkedHashMap<K, V>
, nãoMap<K, V>
. Se alguém quiser atribuir o resultado desse método aMap<K, V>
, ele pode fazer isso. Ele comunica claramente que esse resultado é mais do que apenas um mapa, mas um mapa com uma ordem definida.Suponha que esse método retorne
Map<K, V>
. Se o chamador quisesse umLinkedHashMap<K, V>
, eles teriam que fazer uma verificação de tipo, conversão e manipulação de erros, o que é realmente complicado.fonte
Map
não aLinkedHashMap
- você só desejaria retornar aLinkedHashMap
para uma função comokeyValuePairsToFifoMap
algo assim. Mais amplo é melhor até que você tenha um motivo específico para não fazê-lo.LinkedHashMap
paraTreeMap
, por qualquer razão, agora eu tenho um monte de coisas para a mudança.LinkedHashMap
paraTreeMap
não é uma alteração trivial, como alterar deArrayList
paraLinkedList
. É uma mudança com importância semântica. Nesse caso, você deseja que o compilador gere um erro, para forçar os consumidores do valor de retorno a observar a alteração e tomar as medidas apropriadas.Thread#getAllStackTraces()
- retorna um emMap<Thread,StackTraceElement[]>
vez de umHashMap
especificamente tão adiante que eles podem alterá-lo para o tipoMap
que quiserem.