Qual é o nome do (anti) padrão a seguir? Quais são as vantagens e desvantagens?

28

Nos últimos meses, tropecei algumas vezes na seguinte técnica / padrão. No entanto, não consigo encontrar um nome específico, nem tenho 100% de certeza sobre todas as suas vantagens e desvantagens.

O padrão é o seguinte:

Dentro de uma interface Java, um conjunto de métodos comuns é definido como de costume. No entanto, usando uma classe interna, uma instância padrão vaza pela interface.

public interface Vehicle {
    public void accelerate();
    public void decelerate();

    public static class Default {
         public static Vehicle getInstance() {
             return new Car(); // or use Spring to retrieve an instance
         }
    }
 }

Para mim, parece que a maior vantagem está no fato de que um desenvolvedor precisa apenas conhecer a interface e não suas implementações, por exemplo, caso ele queira criar rapidamente uma instância.

 Vehicle someVehicle = Vehicle.Default.getInstance();
 someVehicle.accelerate();

Além disso, vi essa técnica sendo usada junto com o Spring para fornecer instâncias dinamicamente, dependendo da configuração. Nesse sentido, também parece que isso pode ajudar na modularização.

No entanto, não posso deixar de pensar que isso é um uso indevido da interface, pois ela acopla a interface a uma de suas implementações. (Princípio de inversão de dependência etc.) Alguém poderia me explicar como é chamada essa técnica, bem como suas vantagens e desvantagens?

Atualizar:

Após algum tempo para consideração, verifiquei novamente e notei que a seguinte versão singleton do padrão era usada com muito mais frequência. Nesta versão, uma instância estática pública é exposta através da interface que é inicializada apenas uma vez (devido ao campo ser final). Além disso, a instância é quase sempre recuperada usando o Spring ou uma fábrica genérica que desacopla a interface da implementação.

public interface Vehicle {
      public void accelerate();
      public void decelerate();

      public static class Default {
           public static final Vehicle INSTANCE = getInstance();

           private static Vehicle getInstance() {
                return new Car(); // or use Spring/factory here
           }
      }
 }

 // Which allows to retrieve a singleton instance using...
 Vehicle someVehicle = Vehicle.Default.INSTANCE;

Em poucas palavras: parece que esse é um padrão singleton / factory personalizado, que basicamente permite expor uma instância ou singleton por meio de sua interface. Com relação às desvantagens, algumas foram citadas nas respostas e comentários abaixo. Até agora, a vantagem parece estar na sua conveniência.

Jérôme
fonte
algum tipo de padrão singleton?
Bryan Chen
Eu acho que você está certo que a interface não deve "saber" sobre suas implementações. Provavelmente Vehicle.Defaultdeve ser movido para o namespace do pacote como uma classe de fábrica, por exemplo VehicleFactory.
Augurar
7
By the way,Vehicle.Default.getInstance() != Vehicle.Default.getInstance()
Konrad Morawski
20
O antipadrão aqui é usar o antipadrão de analogia de carro, intimamente relacionado ao antipadrão de analogia de animal. A maioria dos softwares não tem rodas ou pernas.
Pieter B
@PieterB: :-)))) Você fez o meu dia!
Doc Brown

Respostas:

24

Default.getInstanceIMHO é apenas uma forma muito específica de um método de fábrica , misturada com uma convenção de nomenclatura tirada do padrão singleton (mas sem ser uma implementação deste último). Na forma atual, isso é uma violação do " princípio de responsabilidade única ", porque a interface (que já serve para declarar uma API de classe) assume a responsabilidade adicional de fornecer uma instância padrão, que seria muito melhor colocada em uma unidade separada. VehicleFactoryclasse.

O principal problema causado por essa construção é que ela induz uma dependência cíclica entre Vehiclee Car. Por exemplo, no formulário atual, não será possível colocar Vehicleem uma biblioteca e Carem outra. Usar uma classe de fábrica separada e colocar o Default.getInstancemétodo lá resolveria esse problema. Também pode ser uma boa idéia dar um nome diferente a esse método, para evitar qualquer confusão relacionada aos singletons. Portanto, o resultado pode ser algo como

VehicleFactory.createDefaultInstance()

Doc Brown
fonte
Obrigado pela sua resposta! Concordo que este exemplo em particular é uma violação do SRP. No entanto, não tenho certeza se ainda é uma violação, caso a implementação seja recuperada dinamicamente. Por exemplo, usando Spring (ou uma estrutura semelhante) para recuperar uma instância específica definida por, por exemplo, um arquivo de configuração.
Jérôme
11
@ Jérôme: IMHO uma classe não aninhada chamada VehicleFactoryé mais convencional que a construção aninhada Vehicle.Default, especialmente com o método "getInstance" enganoso. Embora seja possível contornar a dependência cíclica usando Spring, eu preferiria pessoalmente a primeira variante apenas por causa do bom senso. E, ainda assim, você tem a opção de mover a fábrica para um componente diferente, alterando a implementação posteriormente para funcionar sem o Spring etc.
Doc Brown
Muitas vezes, a razão para usar uma interface em vez de uma classe é permitir que classes com outros objetivos sejam utilizáveis ​​por código que precisa de uma implementação de interface. Se uma classe de implementação padrão puder atender a todas as necessidades de código que simplesmente precisam de algo que implemente a interface "de maneira normal", especificar um meio de criar uma instância parece uma coisa útil para a interface.
Supercat 24/02
Se Car é uma implementação padrão muito genérica do veículo, isso não é uma violação do SRP. O veículo ainda tem apenas um único motivo para mudar, por exemplo, quando o Google adiciona um autodrive()método. Seu carro muito genérico deve ser alterado para implementar esse método, por exemplo, lançando uma NotImplementedException, mas isso não confere um motivo adicional para a alteração no veículo.
user949300
@ user949300: o SRP não é um conceito de "comprovação matemática". E as decisões de design de software nem sempre são "em preto e branco". O código original não é tão ruim que não poderia ser usado no código de produção, é claro, e pode haver casos em que a conveniência supere a dependência cíclica, como o OP observou por si mesmo em sua "atualização". Portanto, leia minha resposta como "considere se o uso de uma classe de fábrica separada não lhe serve melhor". Observe também que o autor mudou um pouco sua pergunta original depois que eu dei minha resposta.
Doc Brown
3

Parece -me o padrão Null Object .

Mas isso depende muito de como a Carclasse é implementada. Se for implementado de maneira "neutra", é definitivamente um Objeto Nulo. Isso significa que, se você puder remover todas as verificações nulas na interface e colocar a instância dessa classe em vez de cada ocorrência de null, o código ainda precisará funcionar corretamente.

Além disso, se for esse padrão, não haverá problema em criar um acoplamento rígido entre a própria interface e a implementação do objeto nulo. Porque o objeto nulo pode estar em qualquer lugar onde a interface é usada.

Eufórico
fonte
2
Olá e obrigado pela resposta. Embora eu tenha certeza de que, no meu caso, isso não foi usado para implementar o padrão "Objeto Nulo", é uma explicação interessante.
Jérôme