2011-10-26 16 views
5

he encontrado una nueva forma de llamar a varios métodos en Java y que realmente no entiende lo que está pasando detrás:Llamada a varios métodos en Java

public class NutritionFacts { 
private final int servingSize; 
private final int servings; 
private final int calories; 
private final int fat; 
private final int sodium; 
private final int carbohydrate; 

public static class Builder { 
    // Required parameters 
    private final int servingSize; 
    private final int servings; 
    // Optional parameters - initialized to default values 
    private int calories  = 0; 
    private int fat   = 0; 
    private int carbohydrate = 0; 
    private int sodium  = 0; 
    public Builder(int servingSize, int servings) { 
     this.servingSize = servingSize; 
     this.servings = servings; 
    } 
     public Builder calories(int val) 
      { calories = val;  return this; } 
     public Builder fat(int val) 
      { fat = val;   return this; } 
     public Builder carbohydrate(int val) 
      { carbohydrate = val; return this; } 
     public Builder sodium(int val) 
      { sodium = val;  return this; } 
     public NutritionFacts build() { 
      return new NutritionFacts(this); 
     } 
} 

    private NutritionFacts(Builder builder) { 
     servingSize = builder.servingSize; 
     servings  = builder.servings; 
     calories  = builder.calories; 
    } 

} 

Ahora la clase se instancia utilizando esta línea, y aquí es donde se vuelve confuso:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build(); 

todo tiene sentido hasta que el NutritionFacts.Build (int, int), después de eso, ¿qué está sucediendo? ¿Por qué los métodos calories, sodium, carbohydrate de la clase Builder deben devolver this? ¿A dónde va esa dirección de clase?

¡Gracias!

Respuesta

9

No "entra" en nada.

Estos métodos devuelven un valor. En este caso, devuelven la instancia actual, this. Esa instancia tiene métodos, como calories() y carbohydrates().

foo.calories(12) devuelve la instancia, y podemos llamar a sus métodos: foo.calories(12).sodium(35).

No es diferente del "valor de retorno" del constructor, implícitamente definido como la nueva instancia. En este caso, se trata de métodos normales, que aún devuelven una instancia, la actual.

Es lo mismo que esto:

Builder foo = new Builder(1, 2); // The "return" value of a ctor is the reference, foo 
foo.sodium(10); // Returns foo, but we ignore it 
foo.calories(42); // Returns foo, but we ignore it 

(foo.sodium(10)).calories(42); 
^^^^^^^^^^^^^^^^ foo, with the side effect of setting the sodium value 

Here's an SO question with some good examples.

+0

¿Quiere decir esto que el valor devuelto por foo.calories (12), que es la instancia, se utiliza para llamar al siguiente método de acuerdo , en nuestro caso, sodio (35)? –

+0

@ Cookie503 Absolutamente :) –

+0

@ Cookie503 Exactamente. –

2

Esta técnica se llama "method chaining", y es un estilo muy agradable para familiarizarse. Esto se hace en lugar de tener que decir algo como:

NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 35, 27); 

... que es propenso a errores, porque el orden o el significado de los argumentos en el constructor podrían cambiar. O también en lugar de algo como:

NutritionFacts cocaCola = new NutritionFacts(240, 8); 
cocaCola.setCalories(100); 
cocaCola.setSodium(35); 
cocaCola.setCarbohydrates(27); 

... lo cual es malo por varias razones: en primer lugar porque no es tan "fluent" en términos de legibilidad, en segundo lugar, porque los emisores se denominan no atómicamente (que es indeseable cuando el objeto se comparte entre varios subprocesos) y tercero porque los campos calories, sodium y carbohydrates son forzados que no son final. En la variante Builder, esos campos se pueden hacer fácilmente final porque solo se establecen una vez en el constructor.

Este patrón, en el que cada llamada al Builder devuelve una referencia a sí mismo, le permite encadenar esas llamadas para mantenerlas fácilmente legibles y para mantener la construcción del objeto resultante atómica.

1

Has definido Builder como una clase anidada de NutritionFacts.Dado que es estático, no requiere una instancia de NutritionFacts para existir. Si no fuera estático (una llamada "clase interna") requeriría que existiera un NutritionFacts. Además, algunos de sus campos de Builder estarían ocultando ciertos campos de NutritionFact, pero ese no es el caso ahora.

Ahora, ya que ha usado esta clase de clase anidada, no puede referirse a ella como Builder. Tendrá que referirse a él como NutritionFacts.Builder. Entonces, cuando en el segundo extracto de código hace new NutritionFacts.Builder(240, 8), lo que obtiene es una nueva instancia de generador con un servingSize 240 y 8 porciones. NutritionFacts realmente no entra en juego todavía, solo está ahí para el nombre.

Esa nueva instancia de creación podría asignarse a alguna variable, o podría usarse inmediatamente, digamos, llamando a algún método. Eso es lo que estás haciendo, es decir, llamas al .calories(100) y establece el campo de calorías de ese Constructor. Pero si vas y echas un vistazo a ese método, verás que tiene un tipo de devolución Builder y lo que termina devolviendo es this. La palabra clave this simplemente se refiere a la instancia actual: ese mismo Builder una vez más.

Lo mismo pasa para .sodium(35) y .carbohydrate(27), después de lo cual usted termina llamando al .build() en su Constructor. Mira ese método. Llama al constructor de NutritionFacts. Ese constructor toma una instancia de Builder como argumento, y nuestro Builder se transfiere a sí mismo (nuevamente con this). Ahora finalmente estamos obteniendo una instancia de NutritionFacts, y se crea utilizando los valores que se almacenaron en la instancia de Builder.

Como dijo Jere, los métodos que establecen los nutrientes del generador utilizan el método de encadenamiento de métodos para devolver el objeto al que fueron llamados, de modo que se pueden unir varios métodos convenientemente en una sola línea. En realidad, el segundo extracto del mismo modo que se puede escribir así:

NutritionFacts.Builder builder = new NutritionFacts.Builder(240, 8); 
builder.calories(100); 
builder.sodium(35); 
builder.carbohydrate(27); 
NutritionFacts cocaCola = builder.build(); 
Cuestiones relacionadas