1. 程式人生 > >當遇到多個構造器引數時可以考慮使用構建器

當遇到多個構造器引數時可以考慮使用構建器

假設有一個場景,對於一個類在構建時,其中的一些成員變數需要進行初始化(當然可能會有預設的值)。我們假設這個類中的成員變數有一些是必須在初始化的時候進行賦值的,還有一些是可選的,解決方案有以下幾種:

  • 重疊構造器:提供一個只有必要引數的構造器,第二個構造器有一個可選引數,第三個構造器有兩個可選引數,以此類推,最後一個構造器包含所有的可選引數,例子如下:

    
    /**
     * @author Liufeng
     * Created on 2018年9月16日 上午11:32:29
     */
    public class NutritionFacts {
    	
    	private final int servingSize;    // required
    	private final int servings;       // required
    	private final int calories;       // optional
    	private final int fat;            // optional
    	private final int sodium;         // optional
    	private final int carbohydrate;   // optional
    	
    	public NutritionFacts(int servingSize, int servings) {
    		this(servingSize, servings, 0);
    	}
    	
    	public NutritionFacts(int servingSize, int servings, int calories) {
    		this(servingSize, servings, calories, 0);
    	}
    	
    	public NutritionFacts(int servingSize, int servings, int calories, 
    			int fat) {
    		this(servingSize, servings, calories, fat, 0);
    	}
    	
    	public NutritionFacts(int servingSize, int servings, int calories, 
    			int fat, int sodium) {
    		this(servingSize, servings, calories, fat, sodium, 0);
    	}
    	
    	public NutritionFacts(int servingSize, int servings, int calories, 
    			int fat, int sodium, int carbohydrate) {
    		this.servingSize = servingSize;
    		this.servings = servings;
    		this.calories = calories;
    		this.fat = fat;
    		this.sodium = sodium;
    		this.carbohydrate = carbohydrate;
    	}
    
    }

         重疊構造器模式可行,但是當有許多引數的時候,客戶端程式碼會很難編寫,並且仍然較難以閱讀。​​​​​​​​​​​​​​

  • JavaBeans模式。通過一個無參構造器來建立物件,然後呼叫setter方法來設定每個必要的引數,以及每個相關的可選引數。

       遺憾的是,JavaBeans模式有著很嚴重的缺點,因為構造過程被分到幾個呼叫中,在構造過程中JavaBeans可能處於不一致的狀態。類無法僅僅通過檢驗構造器引數的有效性來保證一致性。另外,JavaBeans模式阻止了把類做成不可變的可能,這就需要程式設計師付出額外的努力來確保它的執行緒安全。

  • Builder模式。不直接生成想要的物件,而是讓客戶端利用所有必要的引數呼叫構造器(或者靜態工廠),得到一個builder物件,然後客戶端在builder物件上呼叫類似於setter的方法,來設定每個相關的可選引數。最後客戶端呼叫無參的builder方法來生成不可變的物件。這個builder是它構建的類的靜態成員類。示例如下:
    
    /**
     * @author Liufeng
     * Created on 2018年9月16日 上午11:32:29
     */
    public class NutritionFacts {
    	
    	private final int servingSize;    // required
    	private final int servings;       // required
    	private final int calories;       // optional
    	private final int fat;            // optional
    	private final int sodium;         // optional
    	private final int carbohydrate;   // optional
    	
    	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;
    		fat = builder.fat;
    		sodium = builder.sodium;
    		carbohydrate = builder.carbohydrate;
    	}
    
    }

    注意NutritionFacts是不可變的,所有的預設引數都單獨放在一個地方。builder的setter方法返回builder本身,以便可以把呼叫連線起來。

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

    這樣的客戶端程式碼很容易編寫,可以對其引數強加約束條件,build方法檢驗這些約束條件。將引數從build方法拷貝到物件中之後,在物件域中而不是在builder域中對他們進行檢驗。