Phương pháp tốt nhất để chuyển nhiều đối số cho phương thức?


94

Đôi khi, chúng ta phải viết các phương thức nhận được nhiều đối số, ví dụ:

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

Khi gặp vấn đề kiểu này, tôi thường gói gọn các đối số vào một bản đồ.

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

Đây không phải là một thực hành tốt, việc đóng gói các tham số vào một bản đồ là hoàn toàn lãng phí hiệu quả. Điều tốt là, chữ ký rõ ràng, dễ dàng thêm các thông số khác với ít sửa đổi nhất. phương pháp tốt nhất cho loại vấn đề này là gì?

Câu trả lời:


139

Trong Java hiệu quả , Chương 7 (Phương pháp), Mục 40 (Thiết kế chữ ký phương pháp cẩn thận), Bloch viết:

Có ba kỹ thuật để rút ngắn danh sách tham số quá dài:

  • chia phương thức thành nhiều phương thức, mỗi phương thức chỉ yêu cầu một tập hợp con của các tham số
  • tạo các lớp trợ giúp để chứa nhóm tham số (thường là các lớp thành viên tĩnh)
  • điều chỉnh mẫu Builder từ việc xây dựng đối tượng sang lời gọi phương thức.

Để biết thêm chi tiết, tôi khuyến khích bạn mua cuốn sách, nó thực sự đáng giá.


"Tham số quá dài" là gì? Khi nào chúng ta có thể nói rằng một phương thức có quá nhiều tham số? Có một con số cụ thể hoặc một phạm vi?
Red M

2
@RedM Tôi luôn coi bất cứ thứ gì nhiều hơn 3 hoặc 4 thông số là "quá dài"
jtate

2
@jtate là lựa chọn cá nhân hay bạn đang theo dõi một tài liệu chính thức?
Red M

2
@RedM sở thích cá nhân :)
jtate

2
Với phiên bản thứ ba của Java hiệu quả, đây là chương 8 (phương pháp), mục 51
GarethOwen

71

Sử dụng bản đồ với các phím Chuỗi thần kỳ là một ý tưởng không tồi. Bạn mất thời gian biên dịch và thực sự không rõ các thông số bắt buộc. Bạn cần phải viết tài liệu rất đầy đủ để bù đắp cho nó. Bạn sẽ nhớ trong vài tuần tới các Chuỗi đó là gì mà không cần nhìn vào mã? Nếu bạn mắc lỗi chính tả thì sao? Sử dụng sai loại? Bạn sẽ không tìm ra cho đến khi bạn chạy mã.

Thay vào đó hãy sử dụng một mô hình. Tạo một lớp sẽ là vùng chứa cho tất cả các tham số đó. Bằng cách đó, bạn giữ an toàn kiểu Java. Bạn cũng có thể chuyển đối tượng đó sang các phương thức khác, đặt nó vào bộ sưu tập, v.v.

Tất nhiên nếu tập hợp các tham số không được sử dụng ở nơi khác hoặc được chuyển đi xung quanh, thì một mô hình chuyên dụng có thể quá mức cần thiết. Cần phải có một sự cân bằng, vì vậy hãy sử dụng cách hiểu thông thường.


24

Nếu bạn có nhiều tham số tùy chọn, bạn có thể tạo API thông thạo: thay thế một phương pháp bằng chuỗi phương thức

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

Sử dụng nhập tĩnh, bạn có thể tạo các API thông thạo bên trong:

... .datesBetween(from(date1).to(date2)) ...

3
Điều gì sẽ xảy ra nếu mọi tham số là bắt buộc, không phải tùy chọn?
emeraldhieu

1
Bạn cũng có thể có các tham số mặc định theo cách này. Ngoài ra, mô hình trình tạo có liên quan đến các giao diện thông thạo. Đây thực sự nên là câu trả lời, tôi nghĩ. Ngoài việc chia nhỏ một hàm tạo dài thành các phương thức khởi tạo nhỏ hơn là tùy chọn.
Ehtesh Choudhury

13

Nó được gọi là "Giới thiệu Đối tượng Tham số". Nếu bạn thấy mình chuyển cùng một danh sách tham số ở một số nơi, chỉ cần tạo một lớp chứa tất cả chúng.

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

Ngay cả khi bạn không thấy mình truyền cùng một danh sách tham số thường xuyên, việc tái cấu trúc dễ dàng vẫn sẽ cải thiện khả năng đọc mã của bạn, điều này luôn tốt. Nếu bạn nhìn lại mã của mình 3 tháng sau, bạn sẽ dễ dàng hiểu được khi nào bạn cần sửa lỗi hoặc thêm một tính năng.

Tất nhiên đó là triết lý chung và vì bạn chưa cung cấp bất kỳ chi tiết nào nên tôi cũng không thể đưa ra lời khuyên chi tiết hơn cho bạn. :-)


thu gom rác sẽ là một vấn đề?
rupinderjeet

1
Không phải nếu bạn đặt đối tượng tham số phạm vi cục bộ trong hàm người gọi và nếu bạn không thay đổi nó. Rất có thể nó sẽ được thu thập và bộ nhớ của nó được sử dụng lại khá nhanh trong những trường hợp như vậy.
dimitarvp

Imo, bạn cũng nên có XXXParameter param = new XXXParameter();sẵn, và sau đó sử dụng XXXParameter.setObjA(objA); vv ...
satibel

10

Đầu tiên, tôi sẽ cố gắng cấu trúc lại phương thức. Nếu nó sử dụng nhiều tham số như vậy, nó có thể quá dài. Chia nhỏ nó sẽ cải thiện mã và có khả năng giảm số lượng tham số cho mỗi phương thức. Bạn cũng có thể cấu trúc lại toàn bộ hoạt động thành lớp riêng của nó. Thứ hai, tôi muốn tìm các trường hợp khác trong đó tôi đang sử dụng cùng (hoặc tập siêu) của cùng một danh sách tham số. Nếu bạn có nhiều trường hợp, thì có khả năng các thuộc tính này thuộc về nhau. Trong trường hợp đó, hãy tạo một lớp để chứa các tham số và sử dụng nó. Cuối cùng, tôi sẽ đánh giá xem số lượng tham số có đáng để tạo một đối tượng bản đồ để cải thiện khả năng đọc mã hay không. Tôi nghĩ đây là một lời kêu gọi cá nhân - mỗi cách có một khó khăn với giải pháp này và điểm đánh đổi có thể khác nhau. Đối với sáu thông số, tôi có thể sẽ không làm điều đó. Đối với 10 tôi có thể sẽ làm (nếu không có phương pháp nào khác hoạt động trước).


8

Đây thường là một vấn đề khi xây dựng các đối tượng.

Trong trường hợp đó, hãy sử dụng mẫu đối tượng của trình xây dựng , nó hoạt động tốt nếu bạn có một danh sách lớn các tham số và không phải lúc nào cũng cần tất cả chúng.

Bạn cũng có thể điều chỉnh nó để gọi phương thức.

Nó cũng làm tăng khả năng đọc lên rất nhiều.

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

Bạn cũng có thể đặt logic xác minh vào các phương thức Builder set .. () và build ().


Bạn sẽ giới thiệu điều gì nếu nhiều lĩnh vực của bạn final? Đây là điều chính khiến tôi không thể viết các hàm trợ giúp. Tôi cho rằng tôi có thể đặt các trường ở chế độ riêng tư và đảm bảo rằng tôi không sửa đổi chúng không chính xác trong mã của lớp đó, nhưng tôi hy vọng một cái gì đó thanh lịch hơn.
ragerdl

7

Có một mẫu được gọi là đối tượng Tham số .

Ý tưởng là sử dụng một đối tượng thay cho tất cả các tham số. Bây giờ, ngay cả khi bạn cần thêm tham số sau này, bạn chỉ cần thêm nó vào đối tượng. Giao diện phương thức vẫn như cũ.


5

Bạn có thể tạo một lớp để giữ dữ liệu đó. Cần phải đủ ý nghĩa, nhưng tốt hơn nhiều so với việc sử dụng bản đồ (OMG).


Tôi không nghĩ rằng việc tạo một lớp để chứa một tham số phương thức là cần thiết.
Sawyer

Tôi chỉ tạo lớp nếu có nhiều trường hợp truyền các tham số giống nhau. Điều này sẽ báo hiệu rằng các tham số có liên quan và có thể thuộc về nhau. Nếu bạn đang tạo một lớp cho một phương pháp duy nhất, cách chữa có thể còn tồi tệ hơn căn bệnh.
tvanfosson

Có - bạn có thể di chuyển các thông số liên quan vào DTO hoặc Đối tượng giá trị. Một số nhiều tham số có phải là tùy chọn không, tức là phương thức chính bị quá tải với các tham số bổ sung này? Trong những trường hợp như vậy - tôi thấy có thể chấp nhận được.
JoseK

Đó là những gì tôi muốn nói là phải đủ ý nghĩa.
Johannes Rudolph

4

Code Complete * gợi ý một số điều:

  • "Giới hạn số tham số của một thói quen trong khoảng bảy. Bảy là một con số kỳ diệu đối với sự hiểu biết của mọi người" (tr 108).
  • "Đặt các tham số theo thứ tự đầu vào-sửa đổi-đầu ra ... Nếu một số quy trình sử dụng các tham số tương tự, hãy đặt các tham số tương tự theo thứ tự nhất quán" (tr 105).
  • Đặt các biến trạng thái hoặc lỗi sau cùng.
  • Như tvanfosson đã đề cập, chỉ chuyển các phần của một biến có cấu trúc (đối tượng) mà quy trình cần. Điều đó nói rằng, nếu bạn đang sử dụng hầu hết biến có cấu trúc trong hàm, thì chỉ cần chuyển toàn bộ cấu trúc, nhưng hãy lưu ý rằng điều này thúc đẩy việc ghép nối ở một mức độ nào đó.

* Phiên bản đầu tiên, tôi biết tôi nên cập nhật. Ngoài ra, có khả năng một số lời khuyên này có thể đã thay đổi kể từ khi ấn bản thứ hai được viết khi OOP bắt đầu trở nên phổ biến hơn.


2

Thực hành tốt là tái cấu trúc. Điều gì về những đối tượng này có nghĩa là chúng nên được chuyển vào phương thức này? Chúng có nên được gói gọn vào một đối tượng duy nhất không?


vâng họ nên. Ví dụ, một biểu mẫu tìm kiếm lớn, có nhiều ràng buộc không liên quan và cần phân trang. bạn cần pass currentPageNumber, searchCriteria, pageSize ...
Sawyer

2

Sử dụng Bản đồ là một cách đơn giản để làm sạch chữ ký cuộc gọi nhưng sau đó bạn lại gặp phải một vấn đề khác. Bạn cần phải xem bên trong phần thân của phương thức để xem phương thức mong đợi điều gì trong Bản đồ đó, tên khóa là gì hoặc loại giá trị nào có.

Một cách rõ ràng hơn sẽ là nhóm tất cả các tham số trong một bean đối tượng nhưng điều đó vẫn không khắc phục được hoàn toàn vấn đề.

Những gì bạn có ở đây là một vấn đề thiết kế. Với hơn 7 tham số cho một phương thức, bạn sẽ bắt đầu gặp vấn đề khi nhớ chúng đại diện cho những gì và thứ tự chúng có. Từ đây, bạn sẽ gặp rất nhiều lỗi khi gọi phương thức không đúng thứ tự tham số.

Bạn cần một thiết kế ứng dụng tốt hơn không phải là phương pháp hay nhất để gửi nhiều tham số.


1

Tạo một lớp bean và đặt tất cả các tham số (phương thức setter) và truyền đối tượng bean này cho phương thức.


1
  • Xem mã của bạn và xem lý do tại sao tất cả các tham số đó được chuyển vào. Đôi khi có thể tự cấu trúc lại phương thức.

  • Sử dụng bản đồ khiến phương pháp của bạn dễ bị tấn công. Điều gì sẽ xảy ra nếu ai đó sử dụng phương thức của bạn viết sai chính tả tên tham số hoặc đăng một chuỗi mà phương thức của bạn yêu cầu một UDT?

  • Xác định đối tượng chuyển giao . Nó sẽ cung cấp cho bạn ít nhất việc kiểm tra kiểu; thậm chí bạn có thể thực hiện một số xác nhận tại điểm sử dụng thay vì trong phương pháp của bạn.



0

Nếu bạn đang chuyển quá nhiều tham số thì hãy thử cấu trúc lại phương thức. Có thể nó đang làm rất nhiều thứ mà nó không được cho là làm. Nếu không đúng như vậy, hãy thử thay thế các tham số bằng một lớp duy nhất. Bằng cách này, bạn có thể đóng gói mọi thứ trong một cá thể lớp duy nhất và truyền cá thể xung quanh chứ không phải các tham số.


0

Tôi sẽ nói hãy gắn bó với cách bạn đã làm trước đây. Số lượng các tham số trong ví dụ của bạn không nhiều, nhưng các lựa chọn thay thế thì kinh khủng hơn nhiều.

  1. Bản đồ - Có điều hiệu quả mà bạn đã đề cập, nhưng vấn đề lớn hơn ở đây là:

    • Người gọi không biết gửi cho bạn thông tin gì nếu không đề cập đến thứ
      khác ... Bạn có javadocs cho biết chính xác khóa và
      giá trị nào được sử dụng không? Nếu bạn làm (điều đó thật tuyệt), thì việc có nhiều tham số cũng không phải là vấn đề.
    • Rất khó để chấp nhận các kiểu lập luận khác nhau. Bạn có thể hạn chế các tham số đầu vào cho một loại duy nhất hoặc sử dụng Bản đồ <Chuỗi, Đối tượng> và ép kiểu tất cả các giá trị. Cả hai lựa chọn đều khủng khiếp hầu hết thời gian.
  2. Đối tượng trình bao bọc - điều này chỉ giải quyết vấn đề vì bạn cần điền vào đối tượng trình bao bọc ngay từ đầu - thay vì trực tiếp vào phương thức của bạn, nó sẽ là phương thức khởi tạo của đối tượng tham số. Để xác định vấn đề di chuyển có phù hợp hay không phụ thuộc vào việc tái sử dụng đối tượng đã nói. Ví dụ:

Sẽ không sử dụng nó: Nó sẽ chỉ được sử dụng một lần trong cuộc gọi đầu tiên, vì vậy rất nhiều mã bổ sung để xử lý trên 1 dòng ...?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

Có thể sử dụng nó: Ở đây, nó có thể làm được nhiều hơn một chút. Đầu tiên, nó có thể tính các tham số cho 3 cuộc gọi phương thức. nó cũng có thể thực hiện 2 dòng khác trong chính nó ... vì vậy nó trở thành một biến trạng thái theo nghĩa ...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. Mô hình xây dựng - đây là một mô hình chống lại theo quan điểm của tôi. Cơ chế xử lý lỗi mong muốn nhất là phát hiện sớm hơn, không muộn hơn; nhưng với mẫu trình tạo, các cuộc gọi bị thiếu (lập trình viên không nghĩ là bao gồm nó) các tham số bắt buộc được chuyển từ thời gian biên dịch sang thời gian chạy. Tất nhiên nếu lập trình viên cố tình đặt null hoặc tương tự vào vị trí, đó sẽ là thời gian chạy, nhưng vẫn bắt được một số lỗi sớm hơn là một lợi thế lớn hơn nhiều để phục vụ cho những lập trình viên không chịu nhìn vào tên tham số của phương thức họ đang gọi. Tôi thấy nó chỉ thích hợp khi xử lý số lượng lớn các tham số tùy chọn và thậm chí khi đó, lợi ích ở mức tốt nhất. Tôi rất chống lại "khuôn mẫu" của người xây dựng.

Một điều khác mà mọi người quên cân nhắc là vai trò của IDE trong tất cả những điều này. Khi các phương thức có tham số, các IDE tạo hầu hết mã cho bạn và bạn có các dòng màu đỏ nhắc nhở bạn những gì bạn cần cung cấp / thiết lập. Khi sử dụng tùy chọn 3 ... bạn hoàn toàn mất điều này. Bây giờ, lập trình viên phải làm đúng và không có dấu hiệu nào trong thời gian viết mã và biên dịch ... lập trình viên phải kiểm tra nó để tìm ra.

Hơn nữa, các phương án 2 và 3, nếu được áp dụng rộng rãi một cách không cần thiết, sẽ có tác động tiêu cực lâu dài về mặt bảo trì do số lượng lớn mã trùng lặp mà nó tạo ra. Mã càng có nhiều thì càng phải bảo trì, càng tốn nhiều thời gian và tiền bạc để duy trì.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.