Ultimamente, tenho o hábito de "mascarar" coleções Java com nomes de classes amigáveis ao ser humano. Alguns exemplos simples:
// Facade class that makes code more readable and understandable.
public class WidgetCache extends Map<String, Widget> {
}
Ou:
// If you saw a ArrayList<ArrayList<?>> being passed around in the code, would you
// run away screaming, or would you actually understand what it is and what
// it represents?
public class Changelist extends ArrayList<ArrayList<SomePOJO>> {
}
Um colega apontou para mim que isso é uma prática ruim e apresenta atraso / latência, além de ser um anti-padrão OO. Eu posso entender introduzindo um grau muito pequeno de sobrecarga de desempenho, mas não consigo imaginar que seja de todo significativo. Então eu pergunto: isso é bom ou ruim, e por quê?
java
anti-patterns
generics
collections
herpylderp
fonte
fonte
ChangeList
a compilação será interrompidaextends
, porque List é uma interface, requerimplements
. @randomA o que você está imaginando errou o ponto por causa desse erro #implementation
ieHashMap
ouTreeMap
e o que ele teve lá foi um erro de digitação.Respostas:
Atraso / Latência? Eu chamo BS por isso. Deve haver exatamente zero sobrecarga nessa prática. ( Editar: foi apontado nos comentários que isso pode, de fato, inibir as otimizações realizadas pela VM do HotSpot. Não sei o suficiente sobre a implementação da VM para confirmar ou negar isso. Eu estava baseando meu comentário no C ++ implementação de funções virtuais.)
Há algum código adicional. Você precisa criar todos os construtores da classe base que desejar, encaminhando seus parâmetros.
Eu também não vejo isso como um antipadrão, por si só. No entanto, eu vejo isso como uma oportunidade perdida. Em vez de criar uma classe que deriva a classe base apenas para renomear, que tal criar uma classe que contenha a coleção e ofereça uma interface aprimorada, específica para cada caso? O cache do widget deve realmente oferecer a interface completa de um mapa? Ou deveria oferecer uma interface especializada?
Além disso, no caso de coleções, o padrão simplesmente não funciona em conjunto com a regra geral de usar interfaces, não implementações - ou seja, no código de coleção simples, você cria um
HashMap<String, Widget>
e atribui-o a uma variável do tipoMap<String, Widget>
. VocêWidgetCache
não pode estenderMap<String, Widget>
, porque essa é uma interface. Não pode ser uma interface que estenda a interface base, porqueHashMap<String, Widget>
não implementa essa interface e nenhuma outra coleção padrão. E embora você possa torná-la uma classe que se estendeHashMap<String, Widget>
, você deve declarar as variáveis comoWidgetCache
orMap<String, Widget>
e a primeira perde a flexibilidade de substituir uma coleção diferente (talvez uma coleção de carregamento lento do ORM), enquanto a segunda derrota o ponto de ter a aula.Alguns desses contrapontos também se aplicam à minha classe especializada proposta.
Estes são todos os pontos a considerar. Pode ou não ser a escolha certa. Nos dois casos, os argumentos oferecidos pelo seu colega não são válidos. Se ele acha que é um antipadrão, deve nomear.
fonte
public class Properties extends Hashtable<Object,Object>
injava.util
diz "Como as propriedades herdam do Hashtable, os métodos put e putAll podem ser aplicados a um objeto Properties. Seu uso é fortemente desencorajado, pois permite que o chamador insira entradas cujas chaves ou valores não são Strings. ". A composição teria sido muito mais limpa.De acordo com a IBM, esse é realmente um antipadrão. Essas classes do tipo 'typedef' são chamadas de tipos psuedo.
O artigo explica muito melhor do que eu, mas tentarei resumir se o link for desativado:
WidgetCache
não pode manipular umMap<String, Widget>
No artigo, eles propõem o seguinte truque para facilitar a vida sem usar pseudo tipos:
O que funciona devido à inferência automática de tipo.
(Cheguei a esta resposta por meio dessa pergunta de estouro de pilha relacionada)
fonte
newHashMap
agora não é resolvida pelo operador de diamantes?WidgetCache
possa ser atribuído a uma variável do tipoWidgetCache
ouMap<String,Widget>
sem uma conversão, mas existe era um meio pelo qual um método estáticoWidgetCache
poderia fazer referênciaMap<String,Widget>
e retorná-lo como tipoWidgetCache
após executar qualquer validação desejada. Esse recurso pode ser especialmente útil para genéricos, que não precisariam ser apagados (desde ... #O impacto no desempenho seria limitado, no máximo, a uma pesquisa de tabela, que você provavelmente já está incorrendo. Essa não é uma razão válida para se opor.
A situação é bastante comum, pois a maioria das linguagens de programação com estaticamente tem sintaxe especial para os tipos de alias, geralmente chamados a
typedef
. O Java provavelmente não os copiou porque originalmente não tinha tipos parametrizados. Estender uma classe não é ideal, devido aos motivos que Sebastian cobriu tão bem em sua resposta, mas pode ser uma solução razoável para a sintaxe limitada do Java.Os Typedefs têm várias vantagens. Eles expressam a intenção do programador mais claramente, com um nome melhor, em um nível de abstração mais apropriado. Eles são mais fáceis de pesquisar para fins de depuração ou refatoração. Considere encontrar em qualquer lugar que a
WidgetCache
é usado versus encontrar os usos específicos de aMap
. Eles são mais fáceis de alterar, por exemplo, se você achar que precisa mais tardeLinkedHashMap
ou mesmo seu próprio contêiner personalizado.fonte
Sugiro que você, como já mencionado, use composição sobre herança, para que você possa expor apenas métodos realmente necessários, com nomes que correspondam ao caso de uso pretendido. Os usuários de sua classe realmente precisam saber que
WidgetCache
é um mapa? E ser capaz de fazer o que quiserem? Ou eles só precisam saber que é um cache para widgets?Exemplo de classe da minha base de código, com solução para um problema semelhante que você descreveu:
Você pode ver que internamente é "apenas um mapa", mas a interface pública não está mostrando isso. E possui métodos "amigáveis ao programador", como
appendMessage(Locale locale, String code, String message)
para uma maneira mais fácil e significativa de inserir novas entradas. E os usuários da classe não podem, por exemplotranslations.clear()
, porqueTranslations
não estão se estendendoMap
.Opcionalmente, você sempre pode delegar alguns dos métodos necessários para o mapa usado internamente.
fonte
Eu vejo isso como um exemplo de uma abstração significativa. Uma boa abstração tem algumas características:
Ele oculta detalhes de implementação que são irrelevantes para o código que o consome.
É tão complexo quanto precisa ser.
Ao estender, você está expondo toda a interface do pai, mas em muitos casos muito disso pode ser melhor ocultado; portanto, você deve fazer o que Sebastian Redl sugere e favorecer a composição sobre a herança e adicionar uma instância do pai como um membro privado da sua classe personalizada. Qualquer um dos métodos de interface que faça sentido para sua abstração pode ser facilmente delegado (no seu caso) à coleção interna.
Quanto ao impacto no desempenho, sempre é uma boa idéia otimizar o código para facilitar a leitura e, se houver suspeita de um impacto no desempenho, crie um perfil do código para comparar as duas implementações.
fonte
+1 nas outras respostas aqui. Também acrescentarei que é realmente considerado uma prática muito boa pela comunidade DDD (Domain Driven Design). Eles defendem que seu domínio e as interações com ele devem ter significado de domínio semântico, em oposição à estrutura de dados subjacente. A
Map<String, Widget>
poderia ser um cache, mas também poderia ser outra coisa, o que você fez corretamente em Minha opinião não tão humilde (IMNSHO) é modelar o que a coleção representa, nesse caso, um cache.Vou adicionar uma edição importante, pois o wrapper da classe de domínio em torno da estrutura de dados subjacente provavelmente também deve ter outras variáveis ou funções de membro que realmente a tornam uma classe de domínio com interações, em vez de apenas uma estrutura de dados (se apenas Java tivesse tipos de valor , vamos obtê-los em Java 10 - prometa!)
Será interessante ver o impacto que os fluxos do Java 8 terão sobre tudo isso; posso imaginar que talvez algumas interfaces públicas prefiram lidar com um fluxo de (insira primitivo comum Java ou String) em oposição a um objeto Java.
fonte