2010-10-04 13 views
18

Recientemente descubrí una forma interesante de crear una nueva instancia de un objeto en Google Guava y Project Lombok: Ocultar un constructor detrás de un método de creador estático. Esto significa que en lugar de hacer new HashBiMap(), usted hace HashBiMap.create().¿Ocultando un constructor detrás de un método de creador estático?

Mi pregunta es ¿por qué? ¿Qué ventaja tienes de ocultar el constructor? Para mí, no veo absolutamente ninguna ventaja de hacer esto, y parece romper los principios básicos de creación de objetos. Desde el principio, usted crea un objeto con new Object(), no con algún método Object.createMe(). Esto casi parece crear un método por el bien de crear un método.

¿Qué ganas al hacer esto?

+0

Tenga en cuenta que obtendrá advertencias utilizando el 'nuevo HashBiMap()' sin formato, donde no utilizará 'HashBiMap.create()'. Esta es la razón principal por la que Guava usa este patrón, como mencioné en mi respuesta. – ColinD

Respuesta

26

Existen varias razones por las que puede preferir un método de fábrica estático en lugar de un constructor público. Puede leer el Artículo 1 en Effective Java, Second Edition para una discusión más larga.

  1. Permite que el tipo de objeto devuelto por el método sea diferente del tipo de la clase que contiene el método. De hecho, el tipo devuelto puede depender de los parámetros. Por ejemplo, EnumSet.of(E) devolverá un tipo diferente si el tipo emum tiene muy pocos elementos vs si el tipo enum tiene muchos elementos (Edit: en este caso particular, mejorando el rendimiento para el caso común donde la enumeración no tiene muchos elementos)
  2. Permite el almacenamiento en caché. Por ejemplo, Integer.valueOf(x) devolverá la misma instancia de objeto si se llama varias veces con el mismo valor x, si x está entre -128 y 127.
  3. Le permite tener constructores con nombre (que pueden ser útiles si la clase necesita muchos constructores). Ver, por ejemplo, los métodos en java.util.concurrent.Executors.
  4. Le permite crear una API que es conceptualmente simple pero realmente muy poderosa. Por ejemplo, los métodos estáticos en Collections esconden muchos tipos. En lugar de tener una clase Collections con muchos métodos estáticos, podrían haber creado muchas clases públicas, pero eso hubiera sido más difícil de entender o recordar para alguien nuevo en el lenguaje.
  5. Para los tipos genéricos, puede limitar la cantidad de tipeo que necesita hacer. Por ejemplo, en lugar de escribir List<String> strings = new ArrayList<String>() en Guava, puede hacer List<String> strings = Lists.newArrayList() (el método newArrayList es un método genérico y se deduce el tipo del tipo genérico).

Para HashBiMap, la última razón es la más probable.

+1

¡Gran explicación! Gracias por la ayuda – TheLQ

+0

Hmm, parece que estas son todas ventajas menores y de borde. Debido a que los creadores/fábricas estáticos se oponen a OOP, el uso desenfrenado de este patrón no parece justificado. –

+0

@ScottBiggs No estoy de acuerdo en que los métodos de creación estáticos "vayan en contra de OOP"."Tenga en cuenta que todas estas razones provienen de _ Effective Java, Second Edition_, que es la guía definitiva de" mejores prácticas "para Java, y todos los ejemplos que di son del JDK. – NamshubWriter

3

Esto se conoce como un patrón de método de fábrica. Donde la fábrica se encuentra dentro de la clase misma. Wikipedia describes it pretty well pero aquí hay algunos fragmentos.

Los métodos de fábrica son comunes en los kits de herramientas y marcos donde el código de la biblioteca necesita crear objetos de tipos que pueden ser subclasificados por aplicaciones que usan el marco.

Las jerarquías de clases paralelas a menudo requieren objetos de una jerarquía para poder crear objetos apropiados de otra.

+0

El patrón de fábrica funciona bien en cosas como 'BorderFactory', pero no entiendo por qué harías el patrón de fábrica y tu clase en una sola clase. ¿Qué estás ganando al hacer esto? – TheLQ

+0

Ver: http://code.google.com/p/guava-libraries/source/browse/trunk/src/com/google/common/collect/HashBiMap.java#57 Tengo curiosidad por saber por qué usar este patrón cuando estás logrando lo mismo pero con más código. – TheLQ

+0

@TheLQ He actualizado la respuesta. –

0

Bien, sería posible que SomeClass.create() extrajera una instancia de un caché. new SomeClass() no hará eso sin algunos chanchullos.

También sería posible que create() devuelva cualquier cantidad de implementaciones de SomeClass. Básicamente, un tipo de trato de fábrica.

7

Esto generalmente se hace porque la clase realmente instanciada por el método create() puede ser diferente del tipo sobre el que está invocando el método. es decir, un patrón de fábrica donde el método create() devuelve una subclase específica que es apropiada dado el contexto actual. (Por ejemplo, devolver una instancia cuando el entorno actual es Windows y otra cuando es Linux).

+0

¿No debería ser solo para casos especiales? Debido a que HashBiMap en Guava no hace ningún almacenamiento en caché especial, simplemente devuelve una nueva instancia. Y no puedes anularlo (o simularlo con TMK) porque es estático. http://code.google.com/p/guava-libraries/source/browse/trunk/src/com/google/common/collect/HashBiMap.java#57 – TheLQ

+1

@TheLQ, no estaba hablando sobre la biblioteca específicamente . Solo en términos generales sobre para qué podría usarse un método de fábrica 'create()'. Además, ¿qué tiene que ver la anulación con nada? Si está sugiriendo que es difícil probar la unidad de este patrón, estoy totalmente de acuerdo. –

+0

@Kirk Estaba tratando de ser más general. ¿Debería usar este patrón en todo, o solo cuando sea absolutamente necesario para devolver implementaciones específicas? – TheLQ

0

Aunque no se aplica a este ejemplo de código particular, la práctica de ocultar el constructor detrás de un método estático es Singleton Pattern. Esto se usa cuando desea asegurarse de que se cree y use una única instancia de la clase.

+0

Eso es un poco exagerado, pero Supongo que para un solo método de ocultación de constructor puedes hacer esto. – TheLQ

+0

Incluso sin el patrón singleton, esta forma de creación de objetos es útil si desea que el objeto sea instanciado de una manera particular solo, por ejemplo: un hash-map con un tamaño inicial optimizado en lugar de depender del usuario API para proporcionar para ti. –

0

Existen muchas razones para utilizar este patrón de método de fábrica, pero una de las principales razones por las que Guava lo utiliza es que le permite evitar el uso de parámetros de tipo dos veces al crear una nueva instancia. Compare:

HashBiMap<Foo, Bar> bimap = new HashBiMap<Foo, Bar>(); 

HashBiMap<Foo, Bar> bimap = HashBiMap.create(); 

Guava también hace un buen uso del hecho de que los métodos de fábrica pueden tener nombres útiles, a diferencia de los constructores. Considere ImmutableList.of, ImmutableList.copyOf, Lists.newArrayListWithExpectedSize, etc.

También se aprovecha del hecho de que los métodos de fábrica no necesariamente tienen que crear un objeto nuevo. Por ejemplo, ImmutableList.copyOf, cuando se le da un argumento que es en sí mismo un ImmutableList, simplemente devolverá ese argumento en lugar de hacer una copia real.

Por último, los métodos de fábrica ImmutableList 's regresan subclases (no públicas) de ImmutableList como , SingletonImmutableList y RegularImmutableList en función de los argumentos.

Ninguna de estas cosas es posible con los constructores.

+0

Estoy bastante seguro de que 'HashBiMap bimap = new HashBiMap()' funcionaría bien ya que el '' está implícito. – TheLQ

+1

Funcionará, sí, pero también generará advertencias para el uso del tipo sin formato en lugar del tipo parametrizado ... no debería hacerlo. Los métodos estáticos pueden inferir los parámetros de tipo y hacer lo mismo sin advertencias. – ColinD

+0

También debería tener en cuenta que es incorrecto decir que el '' está implícito. No es. Solo está usando el tipo sin procesar y pre-genérico. En JDK7, podrá usar el operador de diamante, como este 'Map map = new HashMap <>()', que sí infiere los tipos similares a la forma en que lo hacen los métodos estáticos. – ColinD

6

A diferencia de los constructores, los métodos estáticos pueden tener nombres de métodos. Aquí está una clase reciente que escribí cuando esto era útil:

/** 
* A number range that can be min-constrained, max-constrained, 
* both-constrained or unconstrained. 
*/ 

public class Range { 
    private final long min; 
    private final long max; 
    private final boolean hasMin; 
    private final boolean hasMax; 

    private Range(long min, long max, boolean hasMin, boolean hasMax) { 
    // ... (private constructor that just assigns attributes) 
    } 

    // Static factory methods 

    public static Range atLeast (long min) { 
    return new Range(min, 0, true, false); 
    } 

    public static Range atMost (long max) { 
    return new Range(0, max, false, true); 
    } 

    public static Range between (long min, long max) { 
    return new Range(min, max, true, true); 
    } 

    public static Range unconstrained() { 
    return new Range (0, 0, false, false); 
    } 
} 

No se podía hacer esto utilizando sólo los constructores, como atLeast y atMost tendría la misma firma exacta (que ambos tienen una larga).

0

i tiene razón muy interesante para ocultar constructor de comprobarlo y por favor, hágamelo saber si hay alguna otra alternativa para lograr este

enter code here 

Class A 
{ 
    String val; 
    protected A() 
    { 
    } 
    protected A(String val) 
    { 
     this.val=val; 
    } 
    protected void setVal(String val) 
    { 
     this.val=val; 
    } 
    public String getVal() 
    { 
     return val; 
    }  
} 
class B extends A 
    { 
    B() 
    { 
     super(); 
    }  
    public val setVal(String val) 
    { 
     super.val=val;  
    } 
} 
class C extends A 
{ 
    C(String val) 
    { 
     super(val); 
    } 
} 
+0

No tiene un método de fábrica allí ... y para ser honesto, no sé dónde este código debería ser un buen ejemplo de cómo ocultar el constructor. Entonces deberías explicar eso. – Tom

0

Algunas de las razones principales

  • Principalmente se le da el poder para instanciar una (sub) clase diferente
  • Posibilidad de devolver null
  • Le permite devolver un alr objeto existente eady
Cuestiones relacionadas