¿Cuál es el nombre del siguiente patrón (anti)? ¿Cuáles son sus ventajas y desventajas?

28

En los últimos meses, me topé algunas veces con la siguiente técnica / patrón. Sin embargo, parece que no puedo encontrar un nombre específico, ni estoy 100% seguro de todas sus ventajas y desventajas.

El patrón es el siguiente:

Dentro de una interfaz Java, un conjunto de métodos comunes se define como de costumbre. Sin embargo, al usar una clase interna, se filtra una instancia predeterminada a través de la interfaz.

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 mí, parece que la mayor ventaja radica en el hecho de que un desarrollador solo necesita saber acerca de la interfaz y no de sus implementaciones, por ejemplo, en caso de que quiera crear rápidamente una instancia.

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

Además, he visto que esta técnica se usa junto con Spring para proporcionar dinámicamente instancias dependiendo de la configuración. En este sentido, también parece que esto puede ayudar con la modularización.

Sin embargo, no puedo evitar la sensación de que se trata de un mal uso de la interfaz, ya que combina la interfaz con una de sus implementaciones. (Principio de inversión de dependencia, etc.) ¿Podría alguien explicarme cómo se llama esta técnica, así como sus ventajas y desventajas?

Actualizar:

Después de un tiempo de consideración, volví a comprobar y noté que la siguiente versión singleton del patrón se usaba con mucha más frecuencia. En esta versión, una instancia estática pública se expone a través de la interfaz que se inicializa solo una vez (debido a que el campo es final). Además, la instancia casi siempre se recupera utilizando Spring o una fábrica genérica que desacopla la interfaz de la implementación.

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;

En pocas palabras: parece que este es un patrón singleton / factory personalizado, que básicamente permite exponer una instancia o un singleton a través de su interfaz. Con respecto a las desventajas, se han mencionado algunas en las respuestas y comentarios a continuación. Hasta ahora, la ventaja parece radicar en su conveniencia.

Jérôme
fuente
algún tipo de patrón singleton?
Bryan Chen
Creo que tienes razón en que la interfaz no debe "saber" acerca de sus implementaciones. Probablemente Vehicle.Defaultdebería moverse hacia arriba en el espacio de nombres del paquete como una clase de fábrica, por ejemplo VehicleFactory.
augurar
77
Por cierto,Vehicle.Default.getInstance() != Vehicle.Default.getInstance()
Konrad Morawski
20
El antipatrón aquí es usar el antipatrón de analogía de automóviles, estrechamente relacionado con el antipatrón de analogía animal. La mayoría del software no tiene ruedas ni patas.
Pieter B
@PieterB: :-)))) ¡Me alegraste el día!
Doc Brown

Respuestas:

24

Default.getInstanceEn mi humilde opinión, es solo una forma muy específica de un método de fábrica , mezclado con una convención de nomenclatura tomada del patrón singleton (pero sin ser una implementación de este último). En la forma actual, esto es una violación del " principio de responsabilidad única ", porque la interfaz (que ya cumple el propósito de declarar una API de clase) asume la responsabilidad adicional de proporcionar una instancia predeterminada, que sería mucho mejor ubicar en un sitio separado VehicleFactoryclase.

El principal problema causado por esta construcción es que induce una dependencia cíclica entre Vehicley Car. Por ejemplo, en la forma actual no será posible colocarlo Vehicleen una biblioteca y Caren otra. Usar una clase de fábrica separada y colocar el Default.getInstancemétodo allí resolvería ese problema. También puede ser una buena idea darle a ese método un nombre diferente, para evitar cualquier confusión relacionada con los singletons. Entonces el resultado podría ser algo como

VehicleFactory.createDefaultInstance()

Doc Brown
fuente
¡Gracias por tu respuesta! Estoy de acuerdo en que este ejemplo en particular es una violación del SRP. Sin embargo, no estoy seguro de si sigue siendo una violación en caso de que la implementación se recupere dinámicamente. Por ejemplo, utilizando Spring (o un marco similar) para recuperar una instancia particular definida por, por ejemplo, un archivo de configuración.
Jérôme
1
@ Jérôme: en mi humilde opinión, una clase no anidada llamada VehicleFactoryes más convencional que la construcción anidada Vehicle.Default, especialmente con el método engañoso "getInstance". Aunque es posible eludir la dependencia cíclica utilizando Spring, personalmente preferiría la primera variante solo por el sentido común. Y, aún así, le deja la opción de trasladar la fábrica a un componente diferente, luego cambiar la implementación para que funcione sin Spring, etc.
Doc Brown
A menudo, la razón para usar una interfaz en lugar de una clase es permitir que las clases que tienen otros propósitos puedan ser utilizadas por código que necesita una implementación de interfaz. Si una clase de implementación predeterminada puede satisfacer todas las necesidades de código que simplemente necesita algo que implemente la interfaz de manera "normal", especificar un medio para crear una instancia parecería algo útil para la interfaz.
supercat
Si el automóvil es una implementación estándar muy genérica del vehículo, esto no es una violación de SRP. El vehículo todavía tiene una sola razón para cambiar, por ejemplo, cuando Google agrega un autodrive()método. Su automóvil muy genérico debe cambiar para implementar ese método, por ejemplo, lanzando una excepción NotImplementedException, pero eso no confiere una razón adicional para cambiar en el vehículo.
user949300
@ user949300: el SRP no es un concepto "matemáticamente demostrable". Y las decisiones de diseño de software no siempre son "en blanco y negro". El código original no es tan malo que no podría usarse en el código de producción, por supuesto, y puede haber casos en los que la conveniencia sea mayor que la dependencia cíclica, como lo ha señalado el OP en su "actualización". Por lo tanto, lea mi respuesta como "considere si usar una clase de fábrica separada no le sirve mejor". Tenga en cuenta también que el autor cambió su pregunta original un poco después de que di mi respuesta.
Doc Brown
3

Esto me parece un patrón de objeto nulo .

Pero eso depende en gran medida de cómo Carse implemente la clase. Si se implementa de forma "neutral", definitivamente es un objeto nulo. Eso significa que si puede eliminar todas las comprobaciones nulas en la interfaz y colocar la instancia de esta clase en lugar de cada aparición de null, entonces el código aún debe funcionar correctamente.

Además, si se trata de este patrón, no hay problema de crear un acoplamiento estrecho entre la interfaz en sí y la implementación del objeto nulo. Porque el objeto nulo puede estar en todas partes donde se usa dicha interfaz.

Eufórico
fuente
2
Hola y gracias por la respuesta. Aunque estoy bastante seguro de que en mi caso esto no se usó para implementar el patrón "Objeto nulo", es una explicación interesante.
Jérôme