É um exagero agrupar uma coleção em uma classe simples apenas por uma questão de melhor legibilidade?

15

Eu tenho o seguinte mapa:

Map<Double, List<SoundEvent>> soundEventCells = new HashMap<Double, List<SoundEvent>>();

Isso HashMapmapeia doublevalores (que são pontos no tempo) para a SoundEvent'célula' correspondente : cada 'célula' pode conter um número de SoundEvents. É por isso que é implementado como um List<SoundEvent>, porque é exatamente o que é.

Por uma questão de melhor legibilidade do código, pensei em implementar uma classe interna estática muito simples assim:

private static class SoundEventCell {
    private List<SoundEvent> soundEvents = new ArrayList<SoundEvent>();
    public void addEvent(SoundEvent event){
        soundEvents.add(event);
    }
    public int getSize(){
        return soundEvents.size();
    }
    public SoundEvent getEvent(int index){
        return soundEvents.get(index);
    }
    // .. remove() method unneeded
}

E que a declaração do mapa (e muitos outros códigos) ficaria melhor, por exemplo:

Map<Double, SoundEventCell> soundEventCells = new HashMap<Double, SoundEventCell>();

Isso é um exagero? Você faria isso em seus projetos?

Aviv Cohn
fonte
pode-se argumentar que, conceitualmente, isso foi abordado em Como você saberia se tivesse escrito um código legível e de fácil manutenção? Se os seus colegas continuam reclamando sobre sua maneira de fazer as coisas, seja de uma forma ou de outra, é melhor mudar para torná-los se sentir melhor
mosquito
1
O que torna a lista de eventos sonoros uma "célula" em vez de uma lista? Essa escolha de palavras significa que uma célula tem ou eventualmente terá um comportamento diferente de uma lista?
x-code
@DocBrown Por quê? A classe é private staticporque só será usada pela classe externa, mas não está relacionada a nenhuma instância específica da classe externa. Não é exatamente esse o uso adequado private static?
Aviv Cohn
2
@ Doc Brown, Aviv Cohn: Não há tags especificando nenhum idioma, portanto, tudo pode estar certo e errado ao mesmo tempo!
Emilio Garavaglia
@EmilioGaravaglia: Java (acho que é bem claro, já que, a julgar pela sintaxe, poderia ser Java ou C #, e as convenções usadas o restringem a Java;)).
Aviv Cohn

Respostas:

12

Não é um exagero. Comece com as operações necessárias, em vez de começar com "Eu posso usar um HashMap". Às vezes, um HashMap é exatamente o que você precisa.
No seu caso, suspeito que não. O que você provavelmente quer fazer é algo como isto:

public class EventsByTime {
    public EventsByTime addEvent(double time, SoundEvent e);
    public List<SoundEvent> getEvents(double time);
    // ... more methods specific to your use ...
}

Você definitivamente não quer ter um monte de código dizendo isso:

List<SoundEvent> events = eventMap.get(time);
if (events == null) {
   events = new ArrayList<SoundEvent>();
   eventMap.put(time, events);
}

Ou talvez você possa apenas usar uma das implementações do Guava Multimap .

Kevin Cline
fonte
Então você defende o uso de uma classe, que é essencialmente um mecanismo de ocultação de informações, como um ... mecanismo de ocultação de informações? O horror.
Robert Harvey
1
Na verdade, sim, eu tenho uma TimeLineaula exatamente para esse tipo de coisa :) É um invólucro fino em torno de uma HashMap<Double, SoundEventCell>(eventualmente eu fui com a ideia em SoundEventCellvez de List<SoundEvent>). Então, eu posso apenas fazer timeline.addEvent(4.5, new SoundEvent(..))e ter o material de nível mais baixo encapsulado :)
Aviv Cohn
14

Embora possa ajudar na legibilidade em algumas áreas, também pode complicar as coisas. Pessoalmente, evito envolver ou estender coleções por motivos de fluência, pois o novo invólucro, na leitura inicial, implica para mim que pode haver um comportamento do qual preciso estar ciente. Considere um tom de princípio da menor surpresa.

Aderir à implementação da interface significa que só preciso me preocupar com a interface. A implementação concreta pode, é claro, abrigar um comportamento adicional, mas não preciso me preocupar com isso. Então, quando estou tentando encontrar o caminho através do código de alguém, prefiro as interfaces simples para facilitar a leitura.

Se, por outro lado, você estiver encontrando um caso de uso que se beneficia de um comportamento adicionado, terá um argumento para melhorar o código criando uma classe de pleno direito.

Loop de feedback
fonte
11
Um wrapper também pode ser usado para remover (ou ocultar) comportamentos desnecessários.
Roman Reiner
4
@RomanReiner - eu recomendaria essas coisas. Hoje, o comportamento desnecessário é frequentemente o programador xingando seu nome amanhã. Todo mundo sabe o que um Listpode fazer, e faz todas essas coisas por um bom motivo.
Telastyn 17/09/14
Aprecio o desejo de manter a funcionalidade, apesar de achar que a solução é um equilíbrio cuidadoso entre funcionalidade e abstração. SoundEventCellpoderia implementar Iterablefor SoundEvents, que ofereceria o iterador do soundEventsmembro, para que você pudesse ler (mas não escrever) como qualquer lista. Hesito em mascarar a complexidade quase tanto quanto hesito em usar um Listquando precisar de algo mais dinâmico no futuro.
Neil
2

O agrupamento limita sua funcionalidade apenas aos métodos que você decide escrever, basicamente aumentando seu código sem nenhum benefício. No mínimo, tentaria o seguinte:

private static class SoundEventCell : List<SoundEvent>
{
}

Você ainda pode escrever o código do seu exemplo.

Map<Double, SoundEventCell> soundEventCells = new HashMap<Double, SoundEventCell>();

Dito isto, só fiz isso quando há alguma funcionalidade que a própria lista precisa. Mas acho que seu método seria um exagero para isso. A menos que você tenha um motivo para querer limitar o acesso à maioria dos métodos da Lista.

Lawtonfogle
fonte
-1

Outra solução pode ser definir sua classe de wrapper com um único método que expõe a lista:

private static class SoundEventCell
{
    private List<SoundEvent> events;

    public SoundEventCell(List<SoundEvent> events)
    {
        this.events = events;
    }

    public List<SoundEvent> getEvents()
    {
        return events;
    }
}

Isso fornece a sua classe bem nomeada com código mínimo, mas ainda fornece encapsulamento, permitindo, por exemplo, tornar a classe imutável (fazendo uma cópia defensiva no construtor e usando Collections.unmodifiableListno acessador).

(No entanto, se essas listas realmente estiverem sendo usadas apenas nesta classe, acho que seria melhor substituí-lo Map<Double, List<SoundEvent>>por um Multimap<Double, SoundEvent>( docs ), pois isso geralmente salva muita lógica e erros de verificação nula.)

MikeFHay
fonte