2011-10-26 42 views
5

Tôi tìm thấy một phương pháp mới để kêu gọi nhiều phương pháp trong Java và tôi không thực sự hiểu những gì đang xảy ra đằng sau:Calling nhiều phương pháp trong 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; 
    } 

} 

Bây giờ lớp được khởi tạo sử dụng dòng này, và đây là nơi mà nó bị bối rối:

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

Tất cả đều có ý nghĩa cho đến NutritionFacts.Build (int, int), sau đó, chính xác những gì đang xảy ra? Tại sao các phương thức calories, sodium, carbohydrate từ lớp Trình dựng cần phải trả lại this? Nơi mà lớp địa chỉ đó đi vào?

Cảm ơn bạn!

Trả lời

9

Nó không "đi vào" bất cứ điều gì.

Các phương thức này trả về một giá trị. Trong trường hợp này, họ trả về cá thể hiện tại, this. Ví dụ đó có các phương pháp, như calories()carbohydrates().

foo.calories(12) trả về phiên bản và chúng tôi có thể gọi phương thức của nó: foo.calories(12).sodium(35).

Không khác gì "giá trị trả lại" từ hàm tạo, được định nghĩa ngầm định là phiên bản mới. Trong trường hợp này, đó là các phương thức bình thường, vẫn trả về một thể hiện - thể hiện hiện tại.

Nó giống như này:

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

Điều này có nghĩa là giá trị trả về từ foo.calories (12), được sử dụng để gọi phương thức tiếp theo trong dòng , trong trường hợp của chúng ta, natri (35)? –

+0

@ Cookie503 Tuyệt đối :) –

+0

@ Cookie503 Chính xác. –

2

Kỹ thuật này được gọi là "method chaining" và là một phong cách rất hay để làm quen. Bạn làm điều này thay vì phải nói điều gì đó như:

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

... dễ bị lỗi, vì thứ tự hoặc ý nghĩa của đối số trong hàm tạo có thể thay đổi. Hoặc cũng thay vì một cái gì đó như:

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

... mà có hại cho một vài lý do: thứ nhất vì nó không phải là "fluent" về mức độ dễ đọc, thứ hai vì setters được gọi là phi nguyên tử (đó là không mong muốn khi đối tượng được chia sẻ trên nhiều luồng) và thứ ba vì các trường calories, sodiumcarbohydratesbuộc là không final. Trong biến thể Builder, các trường đó có thể dễ dàng được thực hiện final vì chúng chỉ được đặt một lần trong hàm tạo.

Mẫu này, trong đó mọi cuộc gọi đến Builder trả về một tham chiếu đến chính nó, cho phép bạn chuỗi các cuộc gọi đó để giữ cho chúng dễ đọc và giữ nguyên cấu trúc đối tượng nguyên tử.

1

Bạn đã xác định Builder là lớp lồng nhau là NutritionFacts.Vì nó là tĩnh, nó không yêu cầu một cá thể NutritionFacts để tồn tại. Nếu nó không phải là tĩnh (một cái gọi là "lớp bên trong") nó sẽ yêu cầu một NutritionFacts tồn tại. Ngoài ra, một số trường Trình tạo của bạn sẽ ẩn một số trường NutritionFact nhất định, nhưng đó không phải là trường hợp bây giờ.

Bây giờ, vì bạn đã sử dụng tính năng này, bạn không thể chỉ xem nó là Builder. Bạn sẽ phải tham khảo nó là NutritionFacts.Builder. Vì vậy, khi trong mã thứ hai của bạn trích xuất bạn làm new NutritionFacts.Builder(240, 8), những gì bạn nhận được là một phiên bản Builder mới với khẩu phần serveSize 240 và 8. NutritionFacts không thực sự đi vào chơi được, nó chỉ có cho tên.

Ví dụ mới tạo đó có thể được gán cho một số biến hoặc có thể sử dụng ngay lập tức bằng cách gọi một số phương thức trên đó. Đó là những gì bạn đang làm, cụ thể là bạn gọi số .calories(100) trên đó, thiết lập trường calo của Trình tạo đó. Nhưng nếu bạn đi và xem xét phương pháp đó, bạn sẽ nhận thấy nó có Trình tạo kiểu trả về và kết quả trả về là this. Từ khóa này chỉ đơn giản đề cập đến trường hợp hiện tại: đó là cùng một Builder.

Tương tự sau đó tiếp tục cho .sodium(35).carbohydrate(27), sau đó bạn kết thúc gọi .build() trên Trình tạo của bạn. Nhìn vào phương pháp đó. Nó gọi hàm tạo của NutritionFacts. Hàm khởi tạo đó lấy một đối tượng Builder làm đối số và Trình xây dựng của chúng ta tự chuyển tiếp (một lần nữa với this). Bây giờ chúng ta cuối cùng cũng nhận được một cá thể NutritionFacts và nó được tạo ra bằng cách sử dụng các giá trị được lưu trữ trong cá thể Builder. Giống như Jere đã nói, các phương pháp thiết lập các chất dinh dưỡng của Builder sử dụng phương pháp chuỗi phương pháp để trả về đối tượng mà chúng được gọi, để nhiều phương pháp có thể được xích lại với nhau trên một dòng duy nhất. Trong thực tế, trích xuất thứ hai của bạn cũng có thể được viết như thế này:

NutritionFacts.Builder builder = new NutritionFacts.Builder(240, 8); 
builder.calories(100); 
builder.sodium(35); 
builder.carbohydrate(27); 
NutritionFacts cocaCola = builder.build(); 
Các vấn đề liên quan