Cách tốt nhất để cấu trúc lại một phương thức có quá nhiều (6+) tham số là gì?


102

Đôi khi tôi gặp các phương pháp với một số tham số khó chịu. Thường xuyên hơn không, chúng dường như là những người xây dựng. Có vẻ như phải có một cách tốt hơn, nhưng tôi không thể hiểu nó là gì.

return new Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)

Tôi đã nghĩ đến việc sử dụng cấu trúc để đại diện cho danh sách các tham số, nhưng điều đó dường như chỉ chuyển vấn đề từ nơi này sang nơi khác và tạo ra một kiểu khác trong quá trình này.

ShnizArgs args = new ShnizArgs(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
return new Shniz(args);

Vì vậy, đó có vẻ không phải là một cải tiến. Vậy cách tiếp cận tốt nhất là gì?


Bạn đã nói "struct". Thuật ngữ đó có ý nghĩa khác nhau trong các ngôn ngữ lập trình khác nhau. Bạn định nó có nghĩa là gì?
Jay Bazuzi

1
Nếu bạn đang tìm kiếm một ngôn ngữ cụ thể để phân biệt, hãy sử dụng C #. Nhưng về cơ bản, chỉ là một túi tài sản đơn giản. Nó có các thuộc tính được đặt tên khác nhau với các kiểu khác nhau. Có thể được định nghĩa là một lớp, bảng băm, cấu trúc hoặc bất cứ thứ gì.
đệ quy

Bài viết này có một số cái nhìn sâu sắc về chủ đề. Javascript cụ thể, nhưng các nguyên tắc có thể được áp dụng lại cho các ngôn ngữ khác.
lala

Câu trả lời:


94

Cách tốt nhất là tìm cách nhóm các lập luận lại với nhau. Điều này giả định và thực sự chỉ hoạt động nếu bạn kết thúc với nhiều "nhóm" đối số.

Ví dụ: nếu bạn đang chuyển thông số kỹ thuật cho một hình chữ nhật, bạn có thể chuyển x, y, width và height hoặc bạn chỉ có thể chuyển một đối tượng hình chữ nhật có chứa x, y, width và height.

Hãy tìm những thứ như thế này khi cấu trúc lại để làm sạch phần nào. Nếu các lập luận thực sự không thể kết hợp, hãy bắt đầu xem liệu bạn có vi phạm Nguyên tắc Trách nhiệm Đơn lẻ hay không.


4
Ý tưởng tốt nhưng ví dụ xấu; hàm tạo cho Rectangle sẽ phải có 4 đối số. Điều này sẽ có ý nghĩa hơn nếu phương pháp mong đợi 2 bộ tọa độ / kích thước hình chữ nhật. Sau đó, bạn có thể chuyển 2 hình chữ nhật thay vì x1, x2, y1, y2 ...
Outlaw Lập trình viên

2
Đủ công bằng. Như tôi đã nói, nó chỉ thực sự có ý nghĩa nếu bạn kết thúc với nhiều nhóm hợp lý.
Matthew Brubaker

23
+1: Đối với Trách nhiệm duy nhất, một trong số ít nhận xét trong tất cả các câu trả lời thực sự đề cập đến vấn đề thực sự. Đối tượng nào thực sự cần 7 giá trị độc lập để tạo thành bản sắc của nó.
AnthonyWJones

6
@AnthonyWJones Tôi không đồng ý. Dữ liệu về tình trạng thời tiết hiện tại có thể có nhiều giá trị độc lập hơn để tạo thành bản sắc của nó.
funct 7

107

Tôi sẽ cho rằng bạn có nghĩa là C # . Một số điều này cũng áp dụng cho các ngôn ngữ khác.

Bạn có một số tùy chọn:

chuyển từ phương thức khởi tạo sang bộ thiết lập thuộc tính . Điều này có thể làm cho mã dễ đọc hơn, bởi vì người đọc thấy rõ giá trị nào tương ứng với tham số nào. Cú pháp của Object Initializer làm cho nó trông đẹp mắt. Nó cũng đơn giản để thực hiện, vì bạn chỉ có thể sử dụng các thuộc tính được tạo tự động và bỏ qua việc viết các hàm tạo.

class C
{
    public string S { get; set; }
    public int I { get; set; }
}

new C { S = "hi", I = 3 };

Tuy nhiên, bạn mất tính bất biến và bạn mất khả năng đảm bảo rằng các giá trị cần thiết được đặt trước khi sử dụng đối tượng tại thời điểm biên dịch.

Mô hình trình xây dựng .

Suy nghĩ về mối quan hệ giữa stringStringBuilder. Bạn có thể nhận được điều này cho các lớp học của riêng bạn. Tôi thích triển khai nó như một lớp lồng nhau, vì vậy lớp Ccó lớp liên quan C.Builder. Tôi cũng thích một giao diện thông thạo trên trình xây dựng. Thực hiện đúng, bạn có thể nhận được cú pháp như sau:

C c = new C.Builder()
    .SetX(4)    // SetX is the fluent equivalent to a property setter
    .SetY("hello")
    .ToC();     // ToC is the builder pattern analog to ToString()

// Modify without breaking immutability
c = c.ToBuilder().SetX(2).ToC();

// Still useful to have a traditional ctor:
c = new C(1, "...");

// And object initializer syntax is still available:
c = new C.Builder { X = 4, Y = "boing" }.ToC();

Tôi có một tập lệnh PowerShell cho phép tôi tạo mã trình tạo để thực hiện tất cả điều này, trong đó đầu vào giống như sau:

class C {
    field I X
    field string Y
}

Vì vậy, tôi có thể tạo tại thời điểm biên dịch. partialcác lớp cho phép tôi mở rộng cả lớp chính và trình tạo mà không sửa đổi mã đã tạo.

Tái cấu trúc "Giới thiệu Đối tượng Tham số" . Xem Danh mục cấu trúc lại . Ý tưởng là bạn lấy một số tham số bạn đang truyền và đưa chúng vào một kiểu mới, sau đó chuyển một thể hiện của kiểu đó thay thế. Nếu bạn làm điều này mà không suy nghĩ, bạn sẽ quay lại nơi bạn bắt đầu:

new C(a, b, c, d);

trở thành

new C(new D(a, b, c, d));

Tuy nhiên, cách tiếp cận này có tiềm năng lớn nhất để tạo ra tác động tích cực đến mã của bạn. Vì vậy, hãy tiếp tục bằng cách làm theo các bước sau:

  1. Tìm kiếm các tập hợp con các tham số có ý nghĩa với nhau. Chỉ cần nhóm tất cả các tham số của một hàm với nhau một cách vô ý thức không giúp bạn được gì nhiều; mục tiêu là có các nhóm có ý nghĩa. Bạn sẽ biết mình đã hiểu đúng khi tên của kiểu mới hiển nhiên.

  2. Tìm những nơi khác nơi các giá trị này được sử dụng cùng nhau và sử dụng kiểu mới ở đó. Rất có thể, khi bạn đã tìm thấy một kiểu mới phù hợp cho một tập hợp các giá trị mà bạn đã sử dụng khắp nơi, thì kiểu mới đó cũng sẽ có ý nghĩa ở tất cả những nơi đó.

  3. Tìm kiếm chức năng có trong mã hiện có, nhưng thuộc loại mới.

Ví dụ: có thể bạn thấy một số mã giống như sau:

bool SpeedIsAcceptable(int minSpeed, int maxSpeed, int currentSpeed)
{
    return currentSpeed >= minSpeed & currentSpeed < maxSpeed;
}

Bạn có thể lấy minSpeedmaxSpeedthông số và đặt chúng trong một loại mới:

class SpeedRange
{
   public int Min;
   public int Max;
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return currentSpeed >= sr.Min & currentSpeed < sr.Max;
}

Điều này tốt hơn, nhưng để thực sự tận dụng lợi thế của kiểu mới, hãy chuyển các phép so sánh sang kiểu mới:

class SpeedRange
{
   public int Min;
   public int Max;

   bool Contains(int speed)
   {
       return speed >= min & speed < Max;
   }
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return sr.Contains(currentSpeed);
}

bây giờ chúng ta đang ở đâu đó: việc triển khai SpeedIsAcceptable()bây giờ nói lên ý bạn, và bạn có một lớp hữu ích, có thể tái sử dụng. (Các bước rõ ràng tiếp theo là thực hiện SpeedRangeđểRange<Speed> .)

Như bạn có thể thấy, Giới thiệu Đối tượng Tham số là một khởi đầu tốt, nhưng giá trị thực của nó là nó đã giúp chúng tôi khám phá ra một kiểu hữu ích đã bị thiếu trong mô hình của chúng tôi.


4
Tôi khuyên bạn nên thử "Giới thiệu Đối tượng Tham số" trước và chỉ dự phòng cho các tùy chọn khác nếu bạn không thể tìm thấy một đối tượng tham số tốt để tạo.
Douglas Leeder

4
câu trả lời xuất sắc. nếu bạn đã đề cập đến giải thích tái cấu trúc trước các đường cú pháp c #, điều này sẽ được bình chọn IMHO cao hơn.
rpattabi

10
Ôi! +1 cho "Bạn sẽ biết mình hiểu đúng khi tên của loại mới rõ ràng."
Sean McMillan

20

Nếu đó là một phương thức khởi tạo, đặc biệt nếu có nhiều biến thể quá tải, bạn nên xem mẫu Builder:

Foo foo = new Foo()
          .configBar(anything)
          .configBaz(something, somethingElse)
          // and so on

Nếu đó là một phương thức bình thường, bạn nên nghĩ về các mối quan hệ giữa các giá trị đang được truyền và có thể tạo một Đối tượng chuyển.


Trả lời xuất sắc. Có lẽ thậm chí còn phù hợp hơn câu trả lời "đặt các tham số trong một lớp" mà mọi người (kể cả tôi) đã đưa ra.
Wouter Lievens

1
Có lẽ là một ý tưởng tồi khi làm cho lớp của bạn có thể thay đổi chỉ để tránh truyền quá nhiều tham số cho hàm tạo.
Lập trình viên ngoài vòng pháp luật

@outlaw - nếu mối quan tâm về khả năng thay đổi, bạn có thể dễ dàng triển khai ngữ nghĩa "chạy một lần". Tuy nhiên, một số lượng lớn các tham số ctor thường chỉ ra nhu cầu cấu hình (hoặc, như những người khác đã lưu ý, một lớp đang cố gắng làm quá nhiều thứ). (tt)
kdgregory

Mặc dù bạn có thể ngoại hóa cấu hình, nhưng trong nhiều trường hợp điều đó không cần thiết, đặc biệt nếu nó được điều khiển bởi trạng thái chương trình hoặc là tiêu chuẩn cho một chương trình nhất định (hãy nghĩ rằng trình phân tích cú pháp XML, có thể nhận biết được không gian tên, xác thực bằng các công cụ khác nhau, & c).
kdgregory

Tôi thích mẫu trình tạo, nhưng tôi tách biệt các loại trình xây dựng bất biến và có thể thay đổi của mình, như string / StringBuilder, nhưng tôi sử dụng các lớp lồng nhau: Foo / Foo.Builder. Tôi có một tập lệnh PowerShell để tạo mã thực hiện việc này cho các lớp dữ liệu đơn giản.
Jay Bazuzi

10

Câu trả lời cổ điển cho điều này là sử dụng một lớp để đóng gói một số hoặc tất cả các tham số. Về lý thuyết thì điều đó nghe có vẻ tuyệt vời, nhưng tôi là kiểu người tạo ra các lớp cho các khái niệm có ý nghĩa trong miền, vì vậy không phải lúc nào cũng dễ dàng áp dụng lời khuyên này.

Ví dụ: thay vì:

driver.connect(host, user, pass)

Bạn đã có thể sử dụng

config = new Configuration()
config.setHost(host)
config.setUser(user)
config.setPass(pass)
driver.connect(config)

YMMV


5
Tôi chắc chắn muốn đoạn mã đầu tiên hơn. Tôi đồng ý rằng có một giới hạn nhất định, trên đó các thông số numbe rof trở nên xấu, nhưng đối với sở thích của tôi, 3 sẽ được chấp nhận.
blabla999

10

Điều này được trích dẫn từ cuốn sách của Fowler và Beck: "Tái cấu trúc"

Danh sách tham số dài

Trong những ngày đầu lập trình, chúng tôi đã được dạy để chuyển vào dưới dạng các tham số mà mọi thứ cần thiết theo quy trình. Điều này có thể hiểu được vì giải pháp thay thế là dữ liệu toàn cầu, và dữ liệu toàn cầu là xấu và thường gây đau đớn. Đối tượng thay đổi tình huống này bởi vì nếu bạn không có thứ bạn cần, bạn luôn có thể yêu cầu một đối tượng khác lấy nó cho bạn. Vì vậy, với các đối tượng mà bạn không truyền vào mọi thứ mà phương thức cần; thay vào đó, bạn vượt qua đủ để phương thức có thể truy cập mọi thứ nó cần. Rất nhiều thứ mà một phương thức cần có sẵn trên lớp chủ của phương thức. Trong chương trình hướng đối tượng, danh sách tham số có xu hướng nhỏ hơn nhiều so với các chương trình truyền thống. Điều này là tốt vì danh sách tham số dài khó hiểu, vì chúng trở nên không nhất quán và khó sử dụng, và bởi vì bạn luôn thay đổi chúng khi bạn cần thêm dữ liệu. Hầu hết các thay đổi được loại bỏ bằng cách chuyển các đối tượng vì bạn có nhiều khả năng chỉ cần thực hiện một vài yêu cầu để nhận được một phần dữ liệu mới. Sử dụng Thay thế Tham số bằng Phương thức khi bạn có thể lấy dữ liệu trong một tham số bằng cách đưa ra yêu cầu của một đối tượng mà bạn đã biết. Đối tượng này có thể là một trường hoặc nó có thể là một tham số khác. Sử dụng Bảo tồn Toàn bộ Đối tượng để lấy một loạt dữ liệu thu thập được từ một đối tượng và thay thế nó bằng chính đối tượng. Nếu bạn có một số mục dữ liệu không có đối tượng logic, hãy sử dụng Giới thiệu Đối tượng Tham số. Có một ngoại lệ quan trọng để thực hiện những thay đổi này. Đây là khi bạn rõ ràng không muốn tạo một phụ thuộc từ đối tượng được gọi đến đối tượng lớn hơn. Trong những trường hợp đó, việc giải nén dữ liệu và gửi nó cùng với các thông số là hợp lý, nhưng hãy chú ý đến vấn đề liên quan. Nếu danh sách tham số quá dài hoặc thay đổi quá thường xuyên, bạn cần xem xét lại cấu trúc phụ thuộc của mình.


7

Tôi không muốn nghe giống như một crack khôn ngoan, nhưng bạn cũng nên kiểm tra để đảm bảo rằng dữ liệu bạn đang truyền thực sự phải được chuyển xung quanh: Chuyển nội dung cho một phương thức khởi tạo (hoặc phương thức cho vấn đề đó) có mùi hơi giống như ít nhấn mạnh vào hành vi của một đối tượng.

Đừng hiểu sai ý tôi: Các phương thức và hàm tạo đôi khi sẽ có rất nhiều tham số. Nhưng khi gặp phải, hãy thử xem xét việc đóng gói dữ liệu bằng hành vi .

Loại mùi này (vì chúng ta đang nói về tái cấu trúc, từ kinh khủng này có vẻ thích hợp ...) cũng có thể được phát hiện đối với các đối tượng có nhiều thuộc tính (đọc: bất kỳ) hoặc getters / setters.


7

Khi tôi thấy danh sách tham số dài, câu hỏi đầu tiên của tôi là liệu hàm hoặc đối tượng này có đang làm quá nhiều hay không. Xem xét:

EverythingInTheWorld earth=new EverythingInTheWorld(firstCustomerId,
  lastCustomerId,
  orderNumber, productCode, lastFileUpdateDate,
  employeeOfTheMonthWinnerForLastMarch,
  yearMyHometownWasIncorporated, greatGrandmothersBloodType,
  planetName, planetSize, percentWater, ... etc ...);

Tất nhiên ví dụ này cố ý lố bịch, nhưng tôi đã thấy rất nhiều chương trình thực tế với các ví dụ chỉ hơi nực cười hơn một chút, trong đó một lớp được sử dụng để chứa nhiều thứ hầu như không liên quan hoặc không liên quan, dường như chỉ vì cùng một chương trình gọi cần cả hai hoặc vì lập trình viên tình cờ nghĩ về cả hai cùng một lúc. Đôi khi giải pháp dễ dàng là chỉ cần chia lớp thành nhiều phần, mỗi phần trong số đó làm một việc riêng.

Chỉ phức tạp hơn một chút là khi một lớp thực sự cần phải xử lý nhiều thứ logic, như cả đơn đặt hàng của khách hàng và thông tin chung về khách hàng. Trong những trường hợp này, hãy xếp một lớp cho khách hàng và một lớp cho đơn hàng, và để họ nói chuyện với nhau khi cần thiết. Vì vậy, thay vì:

 Order order=new Order(customerName, customerAddress, customerCity,
   customerState, customerZip,
   orderNumber, orderType, orderDate, deliveryDate);

Chúng ta có thể có:

Customer customer=new Customer(customerName, customerAddress,
  customerCity, customerState, customerZip);
Order order=new Order(customer, orderNumber, orderType, orderDate, deliveryDate);

Tất nhiên, tôi thích các hàm chỉ lấy 1 hoặc 2 hoặc 3 tham số, nhưng đôi khi chúng ta phải chấp nhận rằng, trên thực tế, hàm này chiếm rất nhiều và số lượng của chính nó không thực sự tạo ra sự phức tạp. Ví dụ:

Employee employee=new Employee(employeeId, firstName, lastName,
  socialSecurityNumber,
  address, city, state, zip);

Vâng, đó là một loạt các trường, nhưng có lẽ tất cả những gì chúng ta sẽ làm với chúng là lưu chúng vào bản ghi cơ sở dữ liệu hoặc ném chúng lên màn hình hoặc một số trường tương tự. Không thực sự có nhiều quá trình xử lý ở đây.

Khi danh sách tham số của tôi dài ra, tôi rất thích nếu tôi có thể cung cấp cho các trường các kiểu dữ liệu khác nhau. Giống như khi tôi thấy một chức năng như:

void updateCustomer(String type, String status,
  int lastOrderNumber, int pastDue, int deliveryCode, int birthYear,
  int addressCode,
  boolean newCustomer, boolean taxExempt, boolean creditWatch,
  boolean foo, boolean bar);

Và sau đó tôi thấy nó được gọi bằng:

updateCustomer("A", "M", 42, 3, 1492, 1969, -7, true, false, false, true, false);

Tôi lo lắng. Nhìn vào cuộc gọi, hoàn toàn không rõ ràng tất cả những con số, mã và cờ khó hiểu này có nghĩa là gì. Đây chỉ là xin lỗi. Một lập trình viên có thể dễ dàng nhầm lẫn về thứ tự của các tham số và vô tình chuyển đổi hai tham số, và nếu chúng cùng kiểu dữ liệu, trình biên dịch sẽ chấp nhận nó. Tôi muốn có một chữ ký trong đó tất cả những thứ này là enums, vì vậy một cuộc gọi được chuyển đến những thứ như Type.ACTIVE thay vì "A" và CreditWatch.NO thay vì "false", v.v.


5

Nếu một số tham số của phương thức khởi tạo là tùy chọn, thì việc sử dụng trình tạo sẽ hợp lý, sẽ lấy các tham số bắt buộc trong phương thức khởi tạo và có các phương thức cho các tham số tùy chọn, trả về trình tạo, được sử dụng như sau:

return new Shniz.Builder(foo, bar).baz(baz).quux(quux).build();

Chi tiết của điều này được mô tả trong Java hiệu quả, lần xuất bản thứ 2, tr. 11. Đối với các đối số phương thức, cùng một cuốn sách (trang 189) mô tả ba cách tiếp cận để rút gọn danh sách tham số:

  • Chia phương thức thành nhiều phương thức có ít đối số hơn
  • Tạo các lớp thành viên của trình trợ giúp tĩnh để đại diện cho các nhóm tham số, tức là chuyển một DinoDonkeythay vì dinodonkey
  • Nếu các tham số là tùy chọn, trình tạo ở trên có thể được sử dụng cho các phương thức, xác định một đối tượng cho tất cả các tham số, thiết lập các tham số bắt buộc và sau đó gọi một số phương thức thực thi trên đó

4

Tôi sẽ sử dụng phương thức khởi tạo và thiết lập thuộc tính mặc định. C # 3.0 có một số cú pháp hay để thực hiện việc này một cách tự động.

return new Shniz { Foo = foo,
                   Bar = bar,
                   Baz = baz,
                   Quuz = quux,
                   Fred = fred,
                   Wilma = wilma,
                   Barney = barney,
                   Dino = dino,
                   Donkey = donkey
                 };

Việc cải tiến mã là đơn giản hóa hàm tạo và không cần phải hỗ trợ nhiều phương thức để hỗ trợ các kết hợp khác nhau. Cú pháp "gọi" vẫn còn hơi "dài dòng", nhưng không thực sự tệ hơn việc gọi những người thiết lập tài sản theo cách thủ công.


2
Điều này sẽ cho phép một đối tượng t new Shniz () tồn tại. Một triển khai OO tốt sẽ tìm cách giảm thiểu khả năng các đối tượng tồn tại trạng thái chưa hoàn thiện.
AnthonyWJones

Nói chung, bất kỳ ngôn ngữ nào có cú pháp băm / từ điển bản địa đều đi kèm với một sự thay thế thích hợp cho các tham số được đặt tên (rất tuyệt và thường là những gì các tình huống này gọi, nhưng vì một số lý do, ngôn ngữ phổ biến duy nhất hỗ trợ chúng lại là ngôn ngữ tồi tệ nhất trên hành tinh) .
hỗn loạn

4

Bạn đã không cung cấp đủ thông tin để đảm bảo một câu trả lời tốt. Một danh sách tham số dài không phải là xấu.

Shniz (foo, bar, baz, quux, fred, wilma, barney, dino, donkey)

có thể được hiểu là:

void Shniz(int foo, int bar, int baz, int quux, int fred, 
           int wilma, int barney, int dino, int donkey) { ...

Trong trường hợp này, tốt hơn hết bạn nên tạo một lớp để đóng gói các tham số vì bạn cung cấp ý nghĩa cho các tham số khác nhau theo cách mà trình biên dịch có thể kiểm tra cũng như trực quan làm cho mã dễ đọc hơn. Nó cũng giúp bạn dễ dàng đọc và cấu trúc lại sau này.

// old way
Shniz(1,2,3,2,3,2,1,2);
Shniz(1,2,2,3,3,2,1,2); 

//versus
ShnizParam p = new ShnizParam { Foo = 1, Bar = 2, Baz = 3 };
Shniz(p);

Ngoài ra, nếu bạn có:

void Shniz(Foo foo, Bar bar, Baz baz, Quux quux, Fred fred, 
           Wilma wilma, Barney barney, Dino dino, Donkey donkey) { ...

Đây là một trường hợp khác xa vì tất cả các đối tượng đều khác nhau (và không có khả năng bị xáo trộn). Đồng ý rằng nếu tất cả các đối tượng đều cần thiết và chúng đều khác nhau, thì việc tạo một lớp tham số sẽ không có ý nghĩa gì.

Ngoài ra, một số tham số có phải là tùy chọn không? Có ghi đè phương thức nào không (tên phương thức giống nhau, nhưng chữ ký phương thức khác nhau?) Các loại chi tiết này đều quan trọng đến điều tốt nhất câu trả lời là gì.

* Một túi tài sản cũng có thể hữu ích, nhưng không tốt hơn cụ thể nếu không có lý lịch.

Như bạn thấy, có hơn 1 câu trả lời đúng cho câu hỏi này. Bạn chọn đi.


3

Bạn có thể cố gắng nhóm tham số của mình thành nhiều cấu trúc / lớp có ý nghĩa (nếu có thể).


2

Nói chung, tôi sẽ nghiêng về phương pháp tiếp cận cấu trúc - có lẽ phần lớn các tham số này có liên quan theo một cách nào đó và đại diện cho trạng thái của một số phần tử có liên quan đến phương pháp của bạn.

Nếu không thể tạo tập hợp các tham số thành một đối tượng có ý nghĩa, đó có thể là một dấu hiệu Shnizđang hoạt động quá nhiều và việc tái cấu trúc sẽ liên quan đến việc chia nhỏ phương thức thành các mối quan tâm riêng biệt.


2

Bạn có thể đánh đổi sự phức tạp cho các dòng mã nguồn. Nếu bản thân phương pháp làm quá nhiều (dao Thụy Sĩ), hãy cố gắng giảm một nửa nhiệm vụ của nó bằng cách tạo ra một phương pháp khác. Nếu phương thức đơn giản, nó cần quá nhiều tham số thì các đối tượng tham số được gọi là cách để đi.


2

Nếu ngôn ngữ của bạn hỗ trợ nó, hãy sử dụng các tham số được đặt tên và tạo càng nhiều tùy chọn (với giá trị mặc định hợp lý) càng tốt.


1

Tôi nghĩ rằng phương pháp bạn mô tả là cách để đi. Khi tôi tìm thấy một phương thức có nhiều tham số và / hoặc một phương thức có khả năng cần nhiều hơn trong tương lai, tôi thường tạo một đối tượng ShnizParams để chuyển qua, như bạn mô tả.


1

Làm thế nào về việc không thiết lập nó cùng một lúc tại các constructor mà thực hiện nó thông qua thuộc tính / setters ? Tôi đã thấy một số lớp .NET sử dụng phương pháp này, chẳng hạn như Processlớp:

        Process p = new Process();

        p.StartInfo.UseShellExecute = false;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.FileName = "cmd";
        p.StartInfo.Arguments = "/c dir";
        p.Start();

3
C # 3 thực sự có một cú pháp để thực hiện việc này một cách dễ dàng: bộ khởi tạo đối tượng.
Daren Thomas

1

Tôi đồng tình với cách tiếp cận chuyển các tham số thành một đối tượng tham số (struct). Thay vì chỉ gắn tất cả chúng vào một đối tượng, hãy xem lại xem các hàm khác có sử dụng các nhóm tham số tương tự hay không. Đối tượng paramater có giá trị hơn nếu nó được sử dụng với nhiều chức năng mà bạn mong đợi rằng tập hợp các tham số đó thay đổi nhất quán trên các chức năng đó. Có thể bạn chỉ đặt một số tham số vào đối tượng tham số mới.


1

Nếu bạn có nhiều tham số như vậy, rất có thể phương thức đang hoạt động quá nhiều, vì vậy hãy giải quyết vấn đề này trước tiên bằng cách tách phương thức thành nhiều phương thức nhỏ hơn. Nếu bạn vẫn có quá nhiều tham số sau đó, hãy thử nhóm các đối số hoặc chuyển một số tham số thành các thành viên cá thể.

Ưu tiên các lớp / phương thức nhỏ hơn là lớn. Hãy nhớ nguyên tắc trách nhiệm duy nhất.


Vấn đề với các thành viên và thuộc tính cá thể là chúng 1) phải có thể ghi, 2) có thể không được đặt. Trong trường hợp của một hàm tạo, có một số trường nhất định mà tôi muốn đảm bảo được lấp đầy trước khi một thể hiện được phép tồn tại.
đệ quy

@recursive - Tôi không đồng ý rằng các trường / thuộc tính luôn phải có thể ghi được. Đối với các lớp học nhỏ, có rất nhiều thời điểm mà các thành viên chỉ đọc có ý nghĩa.
Brian Rasmussen

1

Các đối số được đặt tên là một lựa chọn tốt (giả sử là một ngôn ngữ hỗ trợ chúng) để phân biệt danh sách tham số dài (hoặc thậm chí ngắn!) Đồng thời cho phép (trong trường hợp các hàm tạo) các thuộc tính của lớp là bất biến mà không đặt ra yêu cầu cho phép nó tồn tại ở trạng thái xây dựng một phần.

Tùy chọn khác mà tôi sẽ tìm kiếm khi thực hiện kiểu tái cấu trúc này sẽ là các nhóm tham số liên quan có thể được xử lý tốt hơn như một đối tượng độc lập. Sử dụng lớp Rectangle từ một câu trả lời trước đó làm ví dụ, hàm tạo lấy các tham số cho x, y, chiều cao và chiều rộng có thể tính x và y thành một đối tượng Point, cho phép bạn truyền ba tham số cho hàm tạo của Rectangle. Hoặc đi xa hơn một chút và biến nó thành hai tham số (UpperLeftPoint, LowerRightPoint), nhưng đó sẽ là một tái cấu trúc triệt để hơn.


0

Nó phụ thuộc vào loại đối số bạn có, nhưng nếu chúng là nhiều giá trị / tùy chọn boolean, bạn có thể sử dụng Flag Enum không?


0

Tôi nghĩ rằng vấn đề đó gắn liền với lĩnh vực của vấn đề mà bạn đang cố gắng giải quyết với lớp.

Trong một số trường hợp, hàm tạo 7 tham số có thể chỉ ra hệ thống phân cấp lớp không tốt: trong trường hợp đó, cấu trúc / lớp trợ giúp được đề xuất ở trên thường là một cách tiếp cận tốt, nhưng sau đó bạn cũng có xu hướng kết thúc với vô số cấu trúc chỉ là túi thuộc tính và không làm bất cứ điều gì hữu ích. Hàm tạo 8 đối số cũng có thể chỉ ra rằng lớp của bạn quá chung chung / quá đa năng, vì vậy nó cần nhiều tùy chọn để thực sự hữu ích. Trong trường hợp đó, bạn có thể cấu trúc lại lớp hoặc triển khai các hàm tạo tĩnh để ẩn các hàm tạo phức tạp thực sự: ví dụ. Shniz.NewBaz (foo, bar) thực sự có thể gọi hàm tạo thực truyền các tham số phù hợp.


0

Một điều cần cân nhắc là giá trị nào sẽ ở chế độ chỉ đọc khi đối tượng được tạo?

Các thuộc tính có thể ghi công khai có thể được chỉ định sau khi xây dựng.

Cuối cùng thì giá trị đến từ đâu? Có lẽ một số giá trị thực sự là bên ngoài trong khi những giá trị khác thực sự đến từ một số cấu hình hoặc dữ liệu toàn cục được thư viện duy trì.

Trong trường hợp này, bạn có thể che giấu hàm tạo khỏi việc sử dụng bên ngoài và cung cấp hàm Tạo cho nó. Hàm create nhận các giá trị bên ngoài trung thực và xây dựng đối tượng, sau đó chỉ sử dụng các trình truy cập có sẵn trong thư viện để hoàn thành việc tạo đối tượng.

Sẽ thực sự lạ khi có một đối tượng yêu cầu 7 tham số trở lên để cung cấp cho đối tượng một trạng thái hoàn chỉnh và tất cả đều thực sự là bản chất bên ngoài.


0

Khi một clas có một hàm tạo nhận quá nhiều đối số, đó thường là dấu hiệu cho thấy nó có quá nhiều trách nhiệm. Nó có thể được chia thành các lớp riêng biệt hợp tác để cung cấp các chức năng giống nhau.

Trong trường hợp bạn thực sự cần nhiều đối số cho một hàm tạo, mẫu Builder có thể giúp bạn. Mục đích là vẫn chuyển tất cả các đối số cho hàm tạo, vì vậy trạng thái của nó được khởi tạo ngay từ đầu và bạn vẫn có thể làm cho lớp trở nên bất biến nếu cần.

Xem bên dưới :

public class Toto {
    private final String state0;
    private final String state1;
    private final String state2;
    private final String state3;

    public Toto(String arg0, String arg1, String arg2, String arg3) {
        this.state0 = arg0;
        this.state1 = arg1;
        this.state2 = arg2;
        this.state3 = arg3;
    }

    public static class TotoBuilder {
        private String arg0;
        private String arg1;
        private String arg2;
        private String arg3;

        public TotoBuilder addArg0(String arg) {
            this.arg0 = arg;
            return this;
        }
        public TotoBuilder addArg1(String arg) {
            this.arg1 = arg;
            return this;
        }
        public TotoBuilder addArg2(String arg) {
            this.arg2 = arg;
            return this;
        }
        public TotoBuilder addArg3(String arg) {
            this.arg3 = arg;
            return this;
        }

        public Toto newInstance() {
            // maybe add some validation ...
            return new Toto(this.arg0, this.arg1, this.arg2, this.arg3);
        }
    }

    public static void main(String[] args) {
        Toto toto = new TotoBuilder()
            .addArg0("0")
            .addArg1("1")
            .addArg2("2")
            .addArg3("3")
            .newInstance();
    }

}
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.