Eu já vi a discussão nesta pergunta sobre como uma classe que implementa a partir de uma interface seria instanciada. No meu caso, estou escrevendo um programa muito pequeno em Java que usa uma instância de TreeMap
e, de acordo com a opinião de todos, deve ser instanciado como:
Map<X> map = new TreeMap<X>();
No meu programa, estou chamando a função map.pollFirstEntry()
, que não está declarada na Map
interface (e algumas outras que também estão presentes na Map
interface). Eu consegui fazer isso transmitindo para TreeMap<X>
todos os lugares que eu chamo de método como:
someEntry = ((TreeMap<X>) map).pollFirstEntry();
Entendo as vantagens das diretrizes de inicialização descritas acima para programas grandes, no entanto, para um programa muito pequeno em que esse objeto não seria passado para outros métodos, consideraria desnecessário. Ainda assim, estou escrevendo esse código de exemplo como parte de um pedido de emprego e não quero que meu código fique mal ou desorganizado. Qual seria a solução mais elegante?
EDIT: Gostaria de salientar que estou mais interessado nas boas práticas gerais de codificação, em vez da aplicação da função específica TreeMap
. Como algumas das respostas já apontaram (e marquei como respondida a primeira), o nível de abstração mais alto possível deve ser usado, sem perder a funcionalidade.
fonte
Respostas:
"Programar em uma interface" não significa "usar a versão mais abstrata possível". Nesse caso, todo mundo usaria
Object
.O que isso significa é que você deve definir seu programa com a menor abstração possível sem perder a funcionalidade . Se você precisar de um
TreeMap
, precisará definir um contrato usando aTreeMap
.fonte
TreeMap
como exemplo. O "programa para uma interface" não deve ser considerado literalmente como uma interface ou uma classe abstrata - uma implementação também pode ser considerada uma interface.public interface
e não para 'os métodos públicos de uma implementação' .TreeMap
teria sobreSortedMap
ouNavigableMap
Se você ainda deseja usar uma interface, pode usar
não é necessário sempre usar uma interface, mas geralmente há um ponto em que você deseja ter uma visão mais geral que permita substituir a implementação (talvez para teste) e isso é fácil se todas as referências ao objeto forem abstraídas como o tipo de interface.
fonte
O ponto por trás da codificação para uma interface em vez de uma implementação é evitar o vazamento de detalhes da implementação que, de outra forma, restringiriam o seu programa.
Considere a situação em que a versão original do seu código usou
HashMap
ae a expôs.Isso significa que qualquer alteração
getFoo()
é uma alteração na API que interrompe e deixaria as pessoas infeliz. Se tudo o que você está garantindo é quefoo
é um mapa, você deve devolvê-lo.Isso oferece a flexibilidade de alterar como as coisas funcionam dentro do seu código. Você percebe que
foo
realmente precisa ser um mapa que retorne as coisas em uma ordem específica.E isso não quebra nada pelo resto do código.
Mais tarde, você pode fortalecer o contrato que a classe dá sem quebrar nada.
Isso mergulha no princípio de substituição de Liskov
Como NavigableMap é um subtipo de Mapa, essa substituição pode ser feita sem alterar o programa.
A exposição dos tipos de implementação dificulta a alteração de como o programa funciona internamente quando é necessário fazer uma alteração. Esse é um processo doloroso e muitas vezes cria soluções feias que servem apenas para infligir mais dor ao codificador posteriormente (estou olhando para o codificador anterior que continuava baralhar dados entre um LinkedHashMap e um TreeMap por algum motivo - confie em mim sempre que eu veja seu nome em svn culpa, eu me preocupo).
Você ainda deseja evitar o vazamento de tipos de implementação. Por exemplo, você pode implementar o ConcurrentSkipListMap em vez de algumas características de desempenho ou simplesmente gostar das instruções de importação,
java.util.concurrent.ConcurrentSkipListMap
e nãojava.util.TreeMap
nas instruções de importação ou qualquer outra coisa.fonte
Concordando com as outras respostas de que você deve usar a classe (ou interface) mais genérica da qual realmente precisa de algo, neste caso o TreeMap (ou como alguém sugeriu o NavigableMap). Mas eu gostaria de acrescentar que isso certamente é melhor do que transmitir para todo o lado, o que seria um cheiro muito maior em qualquer caso. Consulte /programming/4167304/why-should-casting-be-avoided por alguns motivos.
fonte
Trata-se de comunicar sua intenção de como o objeto deve ser usado. Por exemplo, se seu método espera um
Map
objeto com uma ordem de iteração previsível :Então, se você absolutamente precisar informar aos chamadores o método acima, ele também retorna um
Map
objeto com uma ordem de iteração previsível , porque existe essa expectativa por algum motivo:Obviamente, os chamadores ainda podem tratar o objeto de retorno
Map
como tal, mas isso está além do escopo do seu método:Dando um passo atrás
O conselho geral de codificação para uma interface é (geralmente) aplicável, porque geralmente é a interface que fornece a garantia de que o objeto deve ser capaz de executar, também conhecido como contrato . Muitos iniciantes começam com
HashMap<K, V> map = new HashMap<>()
e são aconselhados a declarar isso como aMap
, porque aHashMap
não oferece nada mais do que aquilo queMap
se deve fazer. A partir disso, eles serão capazes de entender (espero) por que seus métodos devem incluir um emMap
vez de umHashMap
, e isso permite que eles percebam o recurso de herança no OOP.Para citar apenas uma linha da entrada da Wikipedia do princípio favorito de todos, relacionado a este tópico:
Em outras palavras, o uso de uma
Map
declaração não é porque faz sentido "sintaticamente", mas as invocações no objeto devem apenas se importar com o que é um tipo deMap
.Código de limpeza
Acho que isso também me permite escrever um código mais limpo, principalmente quando se trata de testes de unidade. Criar um
HashMap
com uma única entrada de teste somente leitura leva mais de uma linha (excluindo o uso da inicialização entre chaves), quando eu posso substituí-lo facilmente porCollections.singletonMap()
.fonte