Là cấu trúc mã này có lợi trong bất kỳ cách nào?


8

Gần đây tôi đã bị ném vào một dự án ứng dụng web Java và tôi đã bắt gặp một số lớp theo định dạng này:

public class MyThingy {
   private final int p1;
   private final String p2;
   

   public MyThingy (int p1, String p2, …) {
      this.p1 = p1;
      this.p2 = p2;
      
   }

   public static void doSomething(int p1, String p2, …) throws Throwable     {
      final MyThingy myThingy = new MyThingy(p1, p2, …);
      myThingy.execute();
   }

   private void execute() throws Throwable {
      //do stuff 
   }
}

Có vẻ như điều này có thể được thực hiện với đoạn mã sau, mà theo tôi có vẻ dễ đọc hơn.

public class MyThingy {

   public static void doSomething (int p1, String p2, …) throws Throwable {
      //do stuff 
   }

}

Lợi ích duy nhất có thể tôi có thể thấy khi thực hiện theo cách đầu tiên, là nếu bạn phải chia nhỏ tệp thực thi () thành các phần nhỏ hơn, tất cả chúng có thể chia sẻ các tham số ban đầu mà không cần phải vượt qua chúng một cách rõ ràng. Nhưng điều này có thể chỉ có lợi cho bộ mã hóa lười biếng, vì người đọc trở nên khó khăn trong việc cho biết phương thức nào cần tham số nào và khi nào các giá trị có thể được thay đổi (gần giống với các biến toàn cục.)

Có thiếu điều gì không? Luồng, hiệu suất?

Chỉnh sửa: Tôi nên đã đề cập, mặc dù các nhà xây dựng là công khai, nó không được gọi. Cách sử dụng duy nhất là như thế này:

MyThingy.doSomething(p1, p2...);

Ngoài ra, bản thân nó cũng có vấn đề khi thử nghiệm, tôi không thể thấy bất kỳ lý do nào để không đưa logic thực thi () trực tiếp vào doS Something (). Ngay cả khi chúng ta đã loại bỏ hàm tĩnh, thì hàm tạo vẫn không có ý nghĩa với tôi; Tôi nghĩ rằng các tham số nên được truyền trực tiếp đến phương thức sẽ sử dụng chúng.


Tôi không cho rằng tiêu chuẩn công ty nói bất cứ điều gì về việc tránh các chức năng tĩnh? Ngoài ra, loại thử nghiệm tự động nào đang được thực hiện với mã này? Mocking trở nên gần như không thể khi các chức năng tĩnh được giới thiệu.
KChaloux

3
Cũng như một lưu ý: throws Throwablelà một thực tiễn xấu, nó nên ít nhất throws Exception, hoặc một cái gì đó cụ thể hơn, nếu có thể. Và, vì các thực tiễn xấu thường đi cùng nhau, tôi muốn nói rằng mẫu mã này chỉ là một thực tiễn xấu khác.
scriptin

Phương thức tĩnh chỉ là một trợ giúp để thực hiện new(...)+ execute()trong một cuộc gọi.
Kasey speakman

Bạn có thể biến nó theo một trong hai cách: đây là một giải pháp xấu xí. Sẽ tốt hơn nếu tách biệt việc tạo đối tượng khỏi tiêu dùng.
Thomas Junk

1
Cũng lưu ý rằng tác giả rõ ràng dự định cho đối tượng được xây dựng là bất biến (dữ liệu là final). Phương thức tĩnh là API duy nhất vào đối tượng vì tất cả các thành viên là riêng tư. Chỉ trong mã bạn đã chia sẻ, tôi không thấy bất kỳ lợi ích nào cho việc này khi tạo executephương thức tĩnh lấy p1, p2, ....
Kasey speakman

Câu trả lời:


2

Tôi nghĩ rằng trực giác của bạn về vấn đề này là đúng.

  • Những gì chúng ta có ở đây là một phương thức đơn giản (trong trường hợp này là tĩnh) mà việc thực hiện đã được "chia nhỏ" thành một lớp.
  • Kỹ thuật "phương thức như một lớp" làm giảm số lượng các biến bạn phải vượt qua một cách rõ ràng, trong khi thực tế che khuất cách dữ liệu chảy bằng cách sử dụng các biến "toàn cầu" là gì.
  • Không ai trong số này thực sự theo tinh thần của OO, vì thể hiện là sự vứt bỏ và trạng thái của nó là phù du.
  • Tuy nhiên, đây là một mô hình phổ biến, đặc biệt là trong các sách Java giới thiệu và có thể đã có trong tâm trí của các nhà thiết kế Java (người đã giới thiệu rất nhiều cách để khai báo các lớp, dường như để hỗ trợ việc sử dụng này).

Vì vậy, bạn nên sử dụng nó ?

  • Chắc chắn, đôi khi.

Trong thực tế, vấn đề không chỉ là "phương thức thuần túy". Trong các lớp học bình thường, bạn có thể bị cám dỗ tạo ra các trường không giữ trạng thái ở giữa các yêu cầu của phương thức công khai. Chúng chỉ được sử dụng để tránh truyền tham số giữa các phương thức riêng tư. Trước đây, tôi đã cố gắng tránh nó bằng mọi giá, nhưng sau đó nhận ra rằng danh sách tham số dài cũng bị hút.

Tôi cố gắng tránh các "lớp phương pháp" thuần túy vì tôi nghĩ rằng chúng đi kèm với rất nhiều hành lý tinh thần (rất nhiều tiềm năng). (Tôi cũng dựa vào các trường dùng một lần trong các lớp thông thường.) Nhưng nếu có hàng tấn biến để vượt qua, thì việc giành được sự sạch sẽ của mã có thể đáng giá.

Tôi cố gắng sử dụng các lớp khi có một điểm cho nó, và trên thực tế, nó thường dễ hình dung ra một lớp. Có lẽ trong tương lai bạn sẽ mở rộng lớp học bằng các triển khai bổ sung. Có lẽ cá thể có thể được coi là một đối tượng chức năng mà bạn muốn khởi tạo và vượt qua. Và sau đó, lớp không còn là "lớp phương thức" giả.


1
Tôi thích những gì bạn đề xuất trong đoạn cuối ở đây. Nếu phương thức exec () là công khai, tôi có thể tạo (và khởi tạo) đối tượng, sau đó có một cái gì đó thực thi nó sau. Điều đó có thể có ý nghĩa, và cũng sẽ cải thiện khả năng kiểm tra. Trong trường hợp đó, nó thực sự là hàm tĩnh không liên quan.
Mishelle

Đây là câu trả lời câu hỏi này đã được chờ đợi. +1
cbojar

6

Việc xác định thêm thông qua lệnh gọi phương thức tĩnh tách biệt mối quan tâm về cách tạo đối tượng từ mã sử dụng đối tượng. Những gì bạn có ở đây là một cái gì đó rất giống với một phương thức xuất xưởng đơn giản (nó không thực sự trả về đối tượng, nhưng sự gián tiếp của việc tạo đối tượng là như nhau).

Điều này có thể hữu ích nếu bạn cần kiểm soát loại đối tượng được tạo. Trong ví dụ đơn giản này, không có quyết định nào được đưa ra, nhưng sẽ dễ dàng thêm logic đó.

Mã tĩnh có nghĩa gì với người gọi là "Tôi cần làm gì đó với trạng thái này nhưng không biết cách ủy quyền." Phương thức tĩnh sau đó có thể ủy quyền cho việc thực hiện đúng dựa trên đối tượng được cung cấp.

Có lẽ khi chạy trong một bài kiểm tra đơn vị có một đối tượng giả đang được sử dụng. Có lẽ dựa trên các tham số, một đối tượng khác có thể được hoán đổi trong đó thực hiện một thuật toán khác để làm bất cứ điều gì nó cần làm. Bằng cách bản địa hóa mã đó đến một địa điểm, quyết định có thể được đưa ra ở một nơi chứ không phải nhiều người tùy tiện.


Cho biết câu hỏi của bạn được diễn đạt như thế nào và thực tế là không có quyết định xây dựng nào được đưa ra trong các phương pháp tĩnh, tôi có cảm giác đây có thể chỉ là một trường hợp quá kỹ thuật. Hầu hết các mã không cần phải ủy quyền cho một nhà máy. Thực tế là bạn tiếp tục gặp phải mã như thế này mà không đưa ra bất kỳ quyết định nào như tôi mong đợi một phương pháp giống như nhà máy tĩnh sẽ cho tôi biết ai đó đã đọc cuốn sách GoF và chạy amok.


1
Bạn thấy phương pháp Factory ở đâu? Không có ví dụ nào của OP trả về bất cứ điều gì.
Robert Harvey

@RobertHarvey: Đó thực sự là một phương pháp nhà máy được bao bọc. Chỉ vì nó không được tiếp xúc với các cuộc gọi không có nghĩa là nó không phải là một nhà máy!
Các cuộc đua nhẹ nhàng trong quỹ đạo

1
@RobertHarvey Tôi nghĩ đó là vấn đề lựa chọn từ ngữ. Nó hoạt động tương tự như một phương thức của nhà máy, nhưng thực tế không trả lại bất cứ thứ gì. Sau đó, một lần nữa, tôi không bao giờ tuyên bố nó đã làm, chỉ có điều nó ủy thác, đó vẫn là một tuyên bố chính xác.

Tôi đồng ý với @RobertHarvey. Một đối tượng được xây dựng và sử dụng hoàn toàn trong một phương thức là một chi tiết triển khai, không phải là sản phẩm của nhà máy.
cbojar

Hấp dẫn. Tôi đã sửa đổi thuật ngữ trong câu trả lời của mình, nhưng điểm tôi đưa ra là giống hệt nhau.

0

Chà, nếu ví dụ thứ hai của bạn (hiển thị mã hoàn chỉnh ) trông như thế này:

public static void doSomething(int p1, String p2, …) throws Throwable     {
      final MyThingy myThingy = new MyThingy(p1, p2, …);
      myThingy.execute();
   }

sau đó bạn vẫn sẽ cần đến nhà xây dựng

public MyThingy (int p1, String p2, …) {
      this.p1 = p1;
      this.p2 = p2;
      
   }

mà lần lượt đặt

   private final int p1;
   private final String p2;

và bây giờ bạn trở lại nơi bạn bắt đầu.

Tất nhiên, bạn có thể chỉ cần loại bỏ hàm tạo và đi với getters và setters thay vào đó

private final int p1;
private final String p2;

public void SetP1(int p1)
{
    this.p1 = p1;
}

public int GetP1()
{
    return p1;
}
// repeat for p2

... nhưng điều này sẽ làm phức tạp cuộc gọi phương thức tĩnh của bạn, vì bây giờ bạn sẽ cần một vài dòng mã để gọi setters trước khi gọi phương thức tĩnh của bạn, trong đó một dòng sử dụng lệnh gọi của hàm tạo sẽ có hiệu lực.

Tất nhiên, điều này chỉ hoạt động nếu các biến thành viên của bạn không final. Do đó, bạn vẫn sẽ cần nhà xây dựng.


Không có nghĩa là có một setter công khai trên một finalbiến. Nó chỉ có thể được khởi tạo một lần.
Kasey speakman

1
@KaseySpeakman: Ergo, bạn vẫn cần một người xây dựng.
Robert Harvey

Tôi không hoàn toàn chắc chắn rằng câu trả lời này nói lên câu hỏi ...
cbojar

Hơn nữa, nó không hợp lệ (có lỗi biên dịch): ideone.com/0nDRgY --- giá trị cuối cùng phải được chứng minh là không thể giải quyết ở bất kỳ nơi nào khác. Có một setter làm cho điều đó là không thể, và do đó là một lỗi biên dịch.

@MichaelT: Tôi đã nói rằng trong câu trả lời của tôi đã có (trong đoạn cuối).
Robert Harvey

0

Thông thường một lớp có rất nhiều phương thức cá thể và một khi đối tượng được tạo, việc thực thi thực thi có thể tận dụng tất cả các phương thức cá thể này. Nếu bạn từ chối tạo một đối tượng, tất cả các phương thức cá thể này đều vô dụng.


Tôi nghĩ rằng OP đang nói rằng không có phương thức cá thể nào khác bên cạnh execute. Vì vậy, hỏi tại sao không nội tuyến toàn bộ điều.
cbojar

Vâng, đó chính xác là câu hỏi của tôi!
Mishelle
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.