A implementação de uma interface definida em um subpacote é um antipadrão?


9

Digamos que tenho o seguinte:

package me.my.pkg;
public interface Something {
    /* ... couple of methods go here ... */
}

e:

package me.my;
import me.my.pkg.Something;
public class SomeClass implements Something {
    /* ... implementation of Something goes here ... */
    /* ... some more method implementations go here too ... */
}

Ou seja, a classe que implementa uma interface fica mais próxima da raiz da hierarquia de pacotes do que a interface que implementa, mas ambas pertencem à mesma hierarquia de pacotes.

A razão para isso, no caso em particular que tenho em mente, é que existe um pacote existente que agrupa a funcionalidade à qual a Somethinginterface pertence logicamente e a lógica (como em "aquele que você esperaria" e "aquele para onde precisa, dada a arquitetura atual "), a classe de implementação existe anteriormente e vive um nível" acima "do posicionamento lógico da interface. A classe de implementação não pertence logicamente a nenhum lugar em me.my.pkg.

No meu caso particular, a classe em questão implementa várias interfaces, mas parece que isso não faz nenhuma (ou pelo menos nenhuma diferença significativa) aqui.

Não consigo decidir se esse é um padrão aceitável ou não. É ou não é, e por quê?

Respostas:


6

Sim, é aceitável, desde que a classe e a interface estejam definitivamente nos pacotes corretos.

Organizar classes em um conjunto hierárquico de pacotes é um pouco como organizar classes em uma hierarquia de herança. Funciona muito bem na maioria das vezes, mas há muitos casos legítimos que simplesmente não se encaixam.

Observe que o Java nem sequer possui uma hierarquia de pacotes - a natureza hierárquica não se estende além da estrutura de pastas. Não posso falar pelos designers da linguagem Java, mas duvido que tenham tomado essa decisão por acidente!

Às vezes, acho que ajuda pensar na 'hierarquia de pacotes' como um auxiliar para ajudar os programadores a encontrar o pacote que estão procurando.


3

Embora formalmente legítimo, isso pode indicar um cheiro de código . Para descobrir se isso é verdade no seu caso, faça três perguntas a si mesmo:

  1. Você vai precisar disso?
  2. Por que você empacotou dessa maneira?
  3. Como você sabe se a API do seu pacote é conveniente de usar?

Você vai precisar disso?

Se você não vê o motivo de investir um esforço extra na manutenção do código , considere deixá-lo como está. Esta abordagem é baseada no princípio YAGNI

o programador não deve adicionar funcionalidades até que seja considerado necessário ... "Sempre implemente as coisas quando você realmente precisar delas, nunca quando você apenas prever que precisa delas."

As considerações fornecidas abaixo pressupõem que o esforço de investimento em uma análise mais aprofundada seja justificado, digamos que você espere que o código seja mantido a longo prazo ou que esteja interessado em praticar e aperfeiçoar habilidades para escrever código sustentável. Se isso não se aplicar, fique à vontade para pular o resto - porque você não precisará disso .

Por que você empacotou dessa maneira?

A primeira coisa que vale a pena notar é que nenhuma das convenções e tutoriais oficiais de codificação fornece qualquer orientação sobre isso, enviando um forte sinal de que espera-se que decisões desse tipo sejam feitas a critério de um programador.

Mas se você observar o design implementado pelos desenvolvedores do JDK , notará que "vazar" a API de subpacotes para os de nível superior não é praticada lá. Por exemplo, veja o pacote util simultâneo e seus subpacotes - bloqueios e atômica .

  • Vale notar que usando subpackages API dentro é considerado OK, por exemplo ConcurrentHashMap implementação importações fechaduras e usa ReentrantLock. Apenas não expõe a API de bloqueios como pública.

Tudo bem, os principais desenvolvedores de API parecem evitar isso e descobrir por que isso acontece, pergunte a si mesmo, por que você empacotou dessa maneira? A razão natural disso seria o desejo de comunicar aos usuários do pacote algum tipo de hierarquia, tipo de camadas , para que o subpacote corresponda a conceitos de uso de nível mais baixo / mais especializados / mais estreitos.

Isso geralmente é feito para facilitar o uso e a compreensão da API, para ajudar os usuários da API a operar dentro de uma determinada camada, evitando incomodá-los com preocupações pertencentes a outras camadas. Se for esse o caso, expor a API de nível inferior a partir de subpacotes certamente vai contra sua intenção.

  • Nota lateral, se você não pode dizer por que o empacota dessa maneira, considere voltar ao seu design e analisá-lo completamente até entender isso. Pense nisso, se é difícil explicar até para você, mesmo agora, quão difícil seria para aqueles que manterão e usarão seu pacote no futuro?

Como você sabe se a API do seu pacote é conveniente de usar?

Para isso, eu recomendo fortemente escrever testes que exercitem a API fornecida pelo seu pacote. Pedir a alguém para revisar também não prejudicaria, mas o revisor experiente da API provavelmente solicitará que você forneça esses testes de qualquer maneira. O problema é que é difícil superar assistir a um exemplo de uso real ao revisar a API.

  • Pode-se olhar para a classe com método único com parâmetro único e uau como é simples , mas se o teste para invocá-lo exige a criação de 10 outros objetos, invocando como 20 métodos com 50 parâmetros no total, isso quebra uma ilusão perigosa e revela a verdadeira complexidade de o design.

Outra vantagem de ter testes é que eles podem simplificar bastante a avaliação de várias idéias que você considera para melhorar seu design.

Às vezes me ocorreu que alterações relativamente pequenas na implementação desencadearam grandes simplificações nos testes, reduzindo a quantidade de importações, objetos, invocações de métodos e parâmetros. Mesmo antes da execução dos testes, isso serve como uma boa indicação do caminho que vale a pena seguir adiante.

O contrário também aconteceu comigo - ou seja, quando mudanças grandes e ambiciosas em minhas implementações destinadas a simplificar a API foram comprovadas erradas pelo respectivo impacto insignificante e menor nos testes, ajudando-me a abandonar as idéias infrutíferas e deixar o código como está (com talvez apenas um comentário) adicionado para ajudar a entender os antecedentes do design e ajudar outras pessoas a aprender sobre o caminho errado).


Para o seu caso concreto, a primeira coisa que vem à mente é considerar a alteração da herança na composição - ou seja, em vez de SomeClassimplementar diretamente Something, inclua um objeto implementando essa interface dentro da classe,

package me.my;
import me.my.pkg.Something;
public class SomeClass {
    private Something something = new Something() {
        /* ... implementation of Something goes here ... */
    }
    /* ... some more method implementations go here too ... */
}

Essa abordagem pode compensar no futuro, impedindo o risco de uma subclasse de SomeClasssubstituir acidentalmente um método Something, fazendo com que ele se comporte de uma maneira que você não planejou.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.