將多個參數傳遞給 Java 方法的最佳實踐
一、概述
在 Java 中將許多參數傳遞給方法可能具有挑戰性,尤其是當參數數量很大或數據類型很複雜時。在這種情況下,理解方法的目的和維護代碼可能具有挑戰性。
本文討論了將許多參數傳遞給 Java 方法的一些最佳實踐。
2.問題陳述
假設我們有一個有很多參數的方法:
public class VehicleProcessor {
Vehicle processVehicle(String make, String model, String color, int weight, boolean status) {
return new Vehicle(make, model, color, weight, status);
}
}
將許多參數傳遞給一個方法可能會影響代碼的質量:
- 通過使方法簽名更難閱讀和理解。跟踪每個參數的順序和用途可能具有挑戰性,尤其是當參數具有相似的數據類型或名稱不正確時。
- 通過在不影響調用代碼的情況下使添加或刪除參數變得困難。更改具有許多參數的方法的簽名可能非常耗時且容易出錯,從而導致維護成本增加和引入錯誤的風險更高。
- 通過增加調用者和方法之間的耦合。如果調用者與方法簽名緊密耦合,則對簽名的任何更改都可能導致調用代碼出現問題。
- 通過增加錯誤的風險,例如傳遞錯誤的參數類型或以錯誤的順序傳遞參數。這可能會導致難以追踪的錯誤。
- 通過增加處理可選值或默認值的難度。這會導致代碼重複或創建參數列表略有不同的多個方法,從而降低代碼的靈活性。
- 通過影響效率,尤其是當參數是大型或複雜數據類型時。傳遞封裝所有必需數據的單個對象可以更有效。
Parameter Object Pattern、Java Bean Pattern、Java Varargs 或 Builder Pattern 等設計模式可以緩解這些問題,並使我們的代碼更具可讀性、可維護性和效率。
3.Java對象
參數對像模式和 Java Bean 模式是我們在 Java 中用來在對象之間傳遞數據的設計模式。儘管它們有一些相似之處,但它們也有一些顯著差異。
這些模式之間的主要區別之一是參數對象通常設計為不可變類,而 Java Bean 類是可變的。兩種模式之間的另一個區別是它們的實例化方法。
當需要的參數很多且不可變性很重要時,參數對像很有用。同時,當我們需要在其生命週期的不同時間修改對象的狀態時,我們使用 Java Bean。
在深入討論每種模式之前,讓我們看一個通過傳遞多個參數來調用方法的示例:
VehicleProcessor vehicleProcessor = new VehicleProcessor();
vehicleProcessor.processVehicle("Ford", "Focus", "red", 2200, true);
3.1.參數對象
參數對像模式是一種模式,我們將包含所有必需參數的單個對像傳遞給方法。同時,這種做法可以使方法簽名更具可讀性和可維護性。
現在,讓我們創建一個包含所有必需字段的類,而不是使用具有許多參數的方法:
public class Vehicle {
static String defaultValue = "DEFAULT";
private String make = defaultValue;
private String model = defaultValue;
private String color = defaultValue;
private int weight = 0;
private boolean statusNew = true;
public Vehicle() {
super();
}
public Vehicle(String make, String model, String color, int weight, boolean statusNew) {
this.make = make;
this.model = model;
this.color = color;
this.weight = weight;
this.statusNew = statusNew;
}
public Vehicle(Vehicle vehicle) {
this(vehicle.make, vehicle.model, vehicle.color, vehicle.weight, vehicle.statusNew);
}
}
然後,我們可以將該類的一個實例傳遞給該方法:
Vehicle vehicle = new Vehicle("Ford", "Focus", "red", 2200, true);
vehicleProcessor.processVehicle(vehicle);
這種方法的優點是:
- 方法簽名更具可讀性和自解釋性。
- 將來添加或刪除參數會更容易。
- 它允許我們在將參數傳遞給方法之前驗證參數。
- 它允許我們在需要時為參數提供默認值。
- 如果我們需要在多個方法中使用相同的參數,它會促進代碼重用。
使用參數對象的主要缺點是它需要為使用這種方法的每個方法創建一個新類,這對於只有幾個參數的方法來說是過大的殺傷力。此外,這可能會導致額外的樣板代碼,並且可能不是簡單用例的最有效解決方案。
3.2.爪哇豆
JavaBean 模式類似於參數對象方法。儘管如此,它仍然允許使用無參數構造函數創建對象,然後在對像生命週期的不同時間使用 setter 方法修改或更新對象。
讓我們創建一個 JavaBean 對象,該對象具有無參數構造函數以及每個參數的 getter 和 setter:
public class Motorcycle extends Vehicle implements Serializable {
private int year;
private String features = "";
public Motorcycle() {
super();
}
public Motorcycle(String make, String model, String color, int weight, boolean statusNew, int year) {
super(make, model, color, weight, statusNew);
this.year = year;
}
public Motorcycle(Vehicle vehicle, int year) {
super(vehicle);
this.year = year;
}
// standard setters and getters
}
使用 JavaBean 模式,我們可以使用標準的 getter 和 setter 訪問字段,簡化代碼並在對象的生命週期內更新對象:
Motorcycle motorcycle = new Motorcycle("Ducati", "Monster", "yellow", 235, true, 2023);
motorcycle.setFeatures("GPS");
vehicleProcessor.processVehicle(motorcycle);
使用 Java Bean 將參數傳遞給方法的主要缺點之一是我們需要一個單獨的類,其中包含每個參數的 getter 和 setter 方法,這會導致冗長和不必要的複雜性。此外,Java Bean不適合不可變對象,因為它們通過 getter 和 setter 方法依賴於可變狀態。
4.Java可變參數
另一個有效的做法是使用 Java 的可變參數特性,它允許方法接受指定類型的可變數量的參數:
public void addMotorcycleFeatures(String... features) {
StringBuilder str = new StringBuilder(this.getFeatures());
for (String feature : features) {
if (!str.isEmpty())
str.append(", ");
str.append(feature);
}
this.setFeatures(str.toString());
}
如果參數的數量不固定並且可能因情況而異,這種做法很有用。
讓我們使用可變參數特性來調用帶有任意數量字符串的方法:
Motorcycle motorcycle = new Motorcycle("Ducati", "Monster", "red", 350, true, 2023);
motorcycle.addMotorcycleFeatures("abs");
motorcycle.addMotorcycleFeatures("navi", "charger");
motorcycle.addMotorcycleFeatures("wifi", "phone", "satellite");
請記住,使用具有巨大參數的可變參數會導致性能問題,因此明智地使用它們至關重要。
Java Varargs 的局限性包括僅適用於相同類型的參數,在處理許多參數時可能會降低代碼的可讀性,以及無法與其他可變參數結合使用。
這些缺點會限制 Varargs 在更複雜的場景和處理不同參數類型時的實用性。
5.建造者模式
另一種廣泛使用的做法是 Builder 模式,它使我們能夠流暢且可讀地逐步創建對象。
這種模式也有助於創建不可變對象。
例如,如果我們有一個包含多個字段的類,並且想要創建此類的不可變實例,我們可以為每個字段定義一個帶有參數的構造函數:
public class Car {
private final String make;
private final String model;
private final int year;
private final String color;
private final boolean automatic;
private final int numDoors;
private final String features;
public Car(String make, String model, int year, String color, boolean automatic, int numDoors, String features) {
this.make = make;
this.model = model;
this.year = year;
this.color = color;
this.automatic = automatic;
this.numDoors = numDoors;
this.features = features;
}
}
雖然此構造函數很簡單,但它不可擴展或不靈活:
- 如果我們想給
Car
類添加更多的字段,我們需要修改構造函數和調用代碼,這很麻煩。 - 如果我們想創建帶有一些可選字段或默認值的
Car
實例,我們需要創建重載的構造函數或使用空值,從而使代碼更難閱讀和維護。
為了解決這些問題,我們可以使用構建器模式來創建Car
類的不可變實例。
首先,我們介紹一個CarBuilder
類,它具有設置Car
類的每個字段的方法:
public static class CarBuilder {
private final String make;
private final String model;
private final int year;
private String color = "unknown";
private boolean automatic = false;
private int numDoors = 4;
private String features = "";
public CarBuilder(String make, String model, int year) {
this.make = make;
this.model = model;
this.year = year;
}
public CarBuilder color(String color) {
this.color = color;
return this;
}
public CarBuilder automatic(boolean automatic) {
this.automatic = automatic;
return this;
}
public CarBuilder numDoors(int numDoors) {
this.numDoors = numDoors;
return this;
}
public CarBuilder features(String features) {
this.features = features;
return this;
}
public Car build() {
return new Car(this);
}
}
Car
類本身有一個私有構造函數,它接受一個CarBuilder
實例並使用它來創建一個新的Car
對象:
public class Car {
private final String make;
private final String model;
private final int year;
private final String color;
private final boolean automatic;
private final int numDoors;
private final String features;
private Car(CarBuilder carBuilder) {
this.make = carBuilder.make;
this.model = carBuilder.model;
this.year = carBuilder.year;
this.color = carBuilder.color;
this.automatic = carBuilder.automatic;
this.numDoors = carBuilder.numDoors;
this.features = carBuilder.features;
}
// standard getters
}
這種模式是創建對象的一種強大而靈活的方式,它使我們的代碼更具可讀性、更易於維護且不易出錯:
Car car = new Car.CarBuilder("Ford", "Focus", 2023).color("blue")
.automatic(true)
.features("abs, navi, charger, wifi, phone, satellite")
.build();
vehicleProcessor.processCar(car);
構建器模式的主要缺點是它需要為使用這種方法的每個類創建一個單獨的構建器類,這會導致額外的樣板代碼和復雜性,主要是在處理少量參數時。此外,為大類實現構建器模式可能很複雜,並且可能不是對所有用例最有效的解決方案。
6.總結
讓我們總結一下我們在本文中看到的每個最佳實踐的優缺點:
實踐 | 優點 | 缺點 |
---|---|---|
參數對象 | 將相關參數封裝到單個對像中。 | 需要為每個方法創建一個新類。 |
提高代碼的可讀性和可維護性。 | 對於參數很少的方法可能有點矯枉過正。 | |
在不更改方法簽名的情況下更容易擴展或修改。 | ||
爪哇豆 | 使用簡單的類封裝相關參數。 | 需要為每個方法創建一個新類。 |
提供 getter 和 setter 方法以便於訪問。 | 可能會很冗長,有很多 getter 和 setter 方法。 | |
可以用作參數文檔的一種形式。 | 不適合不可變對象。 | |
可以設置參數的默認值。 | 需要創建 getter/setter。 | |
無需更改方法簽名即可輕鬆添加或刪除參數。 | ||
Java可變參數 | 允許相同類型的可變數量的參數。 | 僅適用於相同類型的參數。 |
減少對重載方法的需要。 | 在處理許多參數時可讀性較差,導致大量參數的性能問題。 | |
無需創建額外的類或方法。 | 類型安全性較低且容易出現運行時錯誤。 | |
建造者模式 | 允許逐步構建對象。 | 需要創建一個單獨的構建器類。 |
通過使用方法鏈接提高代碼的可讀性。 | 處理少量參數時可能會很冗長。 | |
支持可選參數和默認值。 | 對於大類來說實施起來可能很複雜。 | |
適用於不可變對象。 | 性能可能低於其他替代方案。 |
七、結論
正如我們所見,將許多參數傳遞給 Java 方法可以採用不同的方式處理,每種方法都各有利弊。但是,通過遵循這些最佳實踐,我們可以提高代碼的質量。
與往常一樣,可以在 GitHub 上找到本文的完整代碼示例。