Java Generics - como encontrar um equilíbrio entre expressividade e simplicidade

8

Estou desenvolvendo algum código que utiliza genéricos, e um dos meus princípios orientadores era torná-lo utilizável para cenários futuros, e não apenas para os de hoje. No entanto, vários colegas de trabalho expressaram que eu posso ter trocado a legibilidade em prol da extensibilidade. Eu queria reunir alguns comentários sobre possíveis maneiras de resolver isso.

Para ser específico, aqui está uma interface que define uma transformação - você começa com uma coleção de itens de origem e aplica a transformação a cada elemento, armazenando os resultados em uma coleção de destino. Além disso, quero poder devolver a coleção de destino ao chamador e, em vez de forçá-los a usar uma referência de coleção, quero que eles possam usar qualquer tipo de coleção que eles realmente forneceram para a coleção de destino.

Por fim, possibilito que o tipo de itens na coleção de destino seja diferente do tipo de itens na coleção de origem, porque talvez seja isso que a transformação faça. No meu código, por exemplo, vários itens de origem compõem um item de destino após a transformação.

Isso produz a seguinte interface:

interface Transform<Src, Dst> {
    <DstColl extends Collection<? super Dst>> DstColl transform(
                Collection<? extends Src> sourceCollection,
                DstColl                   destinationCollection);
}

Tentei ser legal e apliquei o princípio PECS de Josh Bloch (produtor estende, consumidor super) para garantir que a interface seja utilizável com super e subtipos, quando apropriado. O resultado final é uma monstruosidade.

Agora, seria bom se eu pudesse estender essa interface e especializá-la de alguma forma. Por exemplo, se eu realmente não me importo em jogar bem com os subtipos dos itens de origem e os supertipos dos itens de destino, eu poderia ter:

interface SimpleTransform<Src, Dst> {
    <DstColl extends Collection<Dst>> DstColl transform(
                  Collection<Src> sourceCollection,
                  DstColl         destinationCollection);
}

Mas não há como fazer isso em Java. Quero tornar as implementações dessa interface algo que outros realmente considerariam fazer, em vez de correr com medo. Eu considerei várias opções:

  • Não retorne a coleção de destino. Parece estranho, dado que você faz uma transformação, mas não recebe nada de volta.
  • Tenha uma classe abstrata que implemente essa interface, mas depois converta os parâmetros para algo mais fácil de usar e chame outro método "translateImpl ()", que possui a assinatura mais simples e, portanto, apresenta menos carga cognitiva para os implementadores. Mas é estranho ter que escrever uma classe abstrata apenas para tornar a interface mais amigável.
  • Abandone a extensibilidade e tenha apenas a interface mais simples. Possivelmente junte isso sem retornar a coleção de destino. Mas isso limita minhas opções no futuro.

O que você acha? Estou perdendo uma abordagem que eu poderia usar?

RuslanD
fonte
2
Tentar prever um cenário futuro em vez de conhecer o caso de uso não é uma boa ideia. É aqui que o princípio do KISS está em jogo.
Lorus
1
Além do princípio KISS, há também o princípio YAGNI sugerindo que você não deve fazer isso.
13763 Frank
@lorus - minha idéia era ter uma interface de "transformação" que eu pudesse usar para uma variedade de transformações, em oposição a um caso específico que é adaptado a uma combinação específica de tipos de itens de origem e de destino. Uma maneira de resumir minha postagem é "como eu a mantenho simples de uma maneira que não exija retrabalho significativo no futuro". Estamos em um ambiente corporativo típico, onde os prazos são importantes e não posso simplesmente refatorar as coisas, a menos que haja uma boa razão comercial. A mesma preocupação se aplica a YAGNI no comentário de Frank.
RuslanD
Em vez de Collection<? extends Src> srcCollection, você deve usarIterable<? extends Src>
kevin Cline

Respostas:

3

O Guava Collections já fornece essa funcionalidade e, se você puder esperar um pouco mais, o Java 8 também fornecerá isso :-). FWIW: Acho que seu uso ? extends Té o idioma correto, não há muito o que fazer, já que o Java usa Type Erasure

Martijn Verburg
fonte
Infelizmente, é improvável que eu consiga convencer minha equipe e gerenciamento a pular diretamente do Java 6 para o Java 8 :) Enquanto isso, você poderia esclarecer o que quer dizer com "Guava Collections já fornece essa funcionalidade"? Você está falando sobre transformar coleções? Um ponteiro para um recurso relevante seria muito útil. Obrigado!
rusland
Estou confuso sobre por que o apagamento é significativo aqui. Você está pensando no que o C # x.0 chama dentro e fora de parâmetros genéricos? / Usar Collection<? super Dst>seria ideal aqui, se também não estivesse retornando o mesmo tipo (presumivelmente a mesma referência, o que não é realmente uma ótima idéia (acho que o Java SE 8 fará isso para alguns métodos ( into))). É interessante notar que os gostos Collections.fillusam curingas desnecessariamente para serem mais expressivos. / IIRC, o Java SE 6 finalmente não é suportado este mês.
Tom Hawtin - defina
1
A goiaba é code.google.com/p/guava-libraries
MatrixFrog
2
+1 para Goiaba - é excelente e deve ser parte do kit de ferramentas de todo desenvolvedor Java (também Joda Time and Joda Money)
Gary Rowe
0

Pessoalmente, não vejo muitos problemas com a maneira como você definiu sua interface, no entanto, estou acostumado a brincar com os Genéricos e posso entender que seus colegas de trabalho considerariam seu código um pouco exagerado, se esse não for o caso.

Se eu fosse você, eu usaria a terceira solução que você declara: simplifique as coisas às custas de ter que voltar mais tarde. Porque afinal, talvez você não precise fazer isso no final; ou você acabaria sendo capaz de usá-lo totalmente em algum momento, mas não o suficiente para compensar o esforço que você fez para tornar essa interface tão genérica, bem como o esforço que seus colegas de trabalho podem ter feito para entender isto.

Outra coisa a considerar é a frequência com que você usa essa interface e em qual nível de complexidade. Além disso, qual a utilidade dessa interface nos seus projetos. Se permitir alta reutilização de alguns componentes (e não apenas potencial, mas real), é uma coisa boa. Se uma interface muito mais simples também funciona 90% das vezes, você pode se perguntar se, pelos 10% restantes, uma interface complexa seria útil ou não.

Eu não acho que seria uma idéia muito boa não usar supere extend, embora se você puder ficar sem eles por enquanto, tenho certeza de que seus colegas não se importarão de vê-los desaparecer. No entanto, você realmente tem tantas situações em que também precisa alterar o tipo Collection?

Alguns conselhos já dados são realmente bons, e eu concordo com Frank sobre seguir o princípio YAGNI, a menos que você tenha boas razões para não fazê-lo. Ainda é possível alterar o código quando surgir a necessidade de mais complexidade, mas não é muito útil desenvolver coisas que você não tem certeza de que serão usadas em breve. Além disso, o conselho de Martijn de usar o Goiaba é algo a considerar seriamente. Ainda não tive a oportunidade de usá-lo, mas ouvi muito bem sobre ele, inclusive em uma apresentação em que o padrão Transformer foi discutido (você pode assisti-lo on-line no InfoQ, se estiver interessado).

Como um aparte, não seria melhor na sua interface atual destinationCollectionser do tipo Class<DstColl>?

KevinLH
fonte
0

Eu gosto de agrupar coleções Java. O encapsulamento adequado pode realmente ajudar. Esse padrão precisa ser relacionado com base no tipo, mas deixa muitas linhas de código inconscientes do tipo de coleção.

Por exemplo, suponha que exista uma lista de produtos. A classe do produto seria imutável e terá alguns métodos de utilidade. A classe da lista de produtos pode adicionar um produto ou outra lista a ele. Se você quisesse executar um método em toda a lista, ele estaria na classe da lista de produtos. Existe um método que pegaria uma classe de lista de filtros e forneceria uma nova lista de produtos com os filtrados. O filtrado seria excluído do original.

Existe uma interface de filtro que decide se UM produto passa no filtro. Haveria uma classe de lista de filtros que possui uma lista de filtros e implementa a interface exigindo que TODOS os filtros passem um produto para que o produto passe.

O engraçado é que eu posso empilhar e alterá-lo para uma lista vinculada sem alterações visíveis como essa. Pode fazer um loop e fazer condicionais muito perceptíveis e simples. Por isso, cria códigos imperativos e adiciona flexibilidade. E é bem organizado.

Akash Patel
fonte
Você percebe que pode fazer tudo isso em algumas linhas de código com coleções, fluxos e lambdas Java padrão? Ter uma "classe de lista de produtos" especial seria um desperdício de tempo muito, muito horrível, que cria nada além de dores de cabeça de manutenção.
Michael Borgwardt