Java - Có phải là một ý tưởng tồi khi có các lớp tĩnh hoàn toàn không?


16

Tôi đang làm việc trong một dự án solo lớn hơn và ngay bây giờ, và tôi có một vài lớp trong đó tôi không thấy bất kỳ lý do nào để tạo ra một ví dụ.

Ví dụ, lớp súc sắc của tôi lưu trữ tất cả dữ liệu của nó một cách tĩnh và tất cả các phương thức của nó đều tĩnh. Tôi không cần phải khởi tạo nó bởi vì khi tôi muốn tung xúc xắc và nhận được một giá trị mới, tôi chỉ cần sử dụng Dice.roll().

Tôi có một số lớp tương tự chỉ có một chức năng chính như thế này và tôi sắp bắt đầu làm việc với một loại "trình điều khiển" sẽ phụ trách tất cả các sự kiện (như khi người chơi di chuyển, và lần lượt hiện tại đó là) và tôi đã thấy rằng tôi có thể theo cùng một ý tưởng cho lớp học này. Tôi không bao giờ có kế hoạch tạo nhiều đối tượng cho các lớp cụ thể này, vậy sẽ là một ý tưởng tồi khi làm cho chúng hoàn toàn tĩnh?

Tôi đã tự hỏi nếu điều này được coi là "thực hành xấu" khi nói đến Java. Từ những gì tôi đã thấy, cộng đồng dường như bị chia rẽ về chủ đề này? Dù sao, tôi sẽ thích một số cuộc thảo luận về điều này và liên kết đến các tài nguyên cũng sẽ rất tuyệt!


1
Nếu chương trình của bạn là đầy đủ thủ tục. Tại sao bạn chọn Java?
Laiv

12
Hãy thử viết bài kiểm tra đơn vị cho các lớp này và bạn sẽ hiểu tại sao mọi người không thích các phương thức tĩnh truy cập trạng thái tĩnh.
Joeri Sebrechts

@Laiv Tôi vẫn chưa quen với lập trình, khoảng một năm về C ++, tôi đang học một lớp Java trong học kỳ này và tôi bắt đầu thích Java hơn rất nhiều, đặc biệt là các thư viện đồ họa.
HexTeke


5
Về các lớp tĩnh. Nếu các phương thức tĩnh là thuần túy (không có trạng thái, có các đối số đầu vào và kiểu trả về). Không có gì phải lo lắng
Laiv

Câu trả lời:


20

Không có gì sai với các lớp tĩnh thực sự tĩnh . Điều đó có nghĩa là, không có trạng thái nội bộ để nói về điều đó sẽ khiến đầu ra của các phương thức thay đổi.

Nếu Dice.roll()chỉ đơn giản là trả về một số ngẫu nhiên mới từ 1 đến 6, thì nó không thay đổi trạng thái. Cấp, bạn có thể đang chia sẻ một Randomví dụ, nhưng tôi sẽ không xem xét rằng việc thay đổi trạng thái theo định nghĩa, đầu ra luôn luôn sẽ tốt, ngẫu nhiên. Nó cũng là chủ đề an toàn vì vậy không có vấn đề ở đây.

Bạn sẽ thường thấy "Trình trợ giúp" cuối cùng hoặc các lớp tiện ích khác có hàm tạo riêng và các thành viên tĩnh. Hàm tạo riêng không chứa logic và chỉ phục vụ để ngăn người nào đó khởi tạo lớp. Công cụ sửa đổi cuối cùng chỉ mang ý tưởng này về nhà rằng đây không phải là một lớp học mà bạn muốn xuất phát. Nó chỉ đơn thuần là một lớp tiện ích. Nếu được thực hiện đúng cách, sẽ không có thành viên độc thân hoặc các thành viên khác trong lớp không phải là tĩnh và cuối cùng.

Vì vậy, miễn là bạn tuân theo các nguyên tắc này và bạn không tạo ra singletons, điều này hoàn toàn không có gì sai. Bạn đề cập đến một lớp trình điều khiển và điều này gần như chắc chắn sẽ yêu cầu thay đổi trạng thái, vì vậy tôi khuyên bạn không nên chỉ sử dụng các phương thức tĩnh. Bạn có thể phụ thuộc rất nhiều vào một lớp tiện ích tĩnh, nhưng bạn không thể biến nó thành một lớp tiện ích tĩnh.


Điều gì được coi là một sự thay đổi trong trạng thái cho một lớp học? Chà, cho phép loại trừ các số ngẫu nhiên trong một giây, vì chúng không xác định theo định nghĩa và do đó giá trị trả về thường thay đổi.

Hàm thuần túy là một hàm có tính xác định, nghĩa là, đối với một đầu vào nhất định, bạn sẽ nhận được một và chính xác một đầu ra. Bạn muốn các phương thức tĩnh là các hàm thuần túy. Trong Java có nhiều cách điều chỉnh hành vi của các phương thức tĩnh để giữ trạng thái, nhưng chúng hầu như không bao giờ là ý tưởng hay. Khi bạn khai báo một phương thức là tĩnh , lập trình viên điển hình sẽ giả sử ngay lập tức rằng đó là một hàm thuần túy. Khác biệt với hành vi dự kiến ​​là cách bạn có xu hướng tạo ra lỗi trong chương trình của mình, nói chung và nên tránh.

Một singleton là một lớp chứa các phương thức tĩnh đối nghịch với "hàm thuần túy" như bạn có thể. Một thành viên riêng tư tĩnh được giữ bên trong lớp được sử dụng để đảm bảo có chính xác một trường hợp. Đây không phải là cách thực hành tốt nhất và có thể khiến bạn gặp rắc rối sau này vì một số lý do. Để biết những gì chúng ta đang nói về, đây là một ví dụ đơn giản về một người độc thân:

// DON'T DO THIS!
class Singleton {
  private String name; 
  private static Singleton instance = null;

  private Singleton(String name) {
    this.name = name;
  }

  public static Singleton getInstance() {
    if(instance == null) {
      instance = new Singleton("George");
    }
    return instance;
  }

  public getName() {
    return name;
  }
}

assert Singleton.getInstance().getName() == "George"

7
Nếu tôi muốn kiểm tra xem điều gì sẽ xảy ra khi sáu số kép được cuộn, tôi bị kẹt ở đây vì bạn có lớp tĩnh với trình tạo số ngẫu nhiên tĩnh. Vì vậy, không, Dice.roll()không phải là một ngoại lệ hợp lệ đối với quy tắc không có nhà nước toàn cầu.
David Arno

1
@HexTeke Cập nhật câu trả lời.
Neil

1
@DavidArno Đúng, nhưng tôi cho rằng tại thời điểm đó chúng tôi thực sự gặp rắc rối nếu chúng tôi đang thử nghiệm một cuộc gọi đến random.nextInt(6) + 1. ;)
Neil

3
@Neil, xin lỗi, tôi đã không giải thích bản thân mình rất tốt. Chúng tôi không kiểm tra chuỗi số ngẫu nhiên, chúng tôi đang tác động đến chuỗi đó để hỗ trợ các thử nghiệm khác. Nếu chúng tôi đang thử nghiệm, ví dụ RollAndMove()cuộn lại khi chúng tôi cung cấp gấp đôi sáu, thì cách dễ nhất, mạnh nhất để làm điều đó là giả định hoặc là Dicetrình tạo số ngẫu nhiên. Ergo, Dicekhông muốn là một lớp tĩnh sử dụng một trình tạo ngẫu nhiên tĩnh.
David Arno

12
Bản cập nhật của bạn đánh vào vấn đề trên đầu: các phương thức tĩnh nên có tính xác định; Họ không nên có tác dụng phụ.
David Arno

9

Để đưa ra một ví dụ về những hạn chế của một staticlớp, điều gì sẽ xảy ra nếu một số game thủ của bạn muốn nhận được một phần thưởng nhỏ cho các cuộn chết của họ? Và họ sẵn sàng trả nhiều tiền! :-)

Có, bạn có thể thêm một tham số khác, vì vậy Dice.roll(bonus),

Sau này bạn cần D20s.

Dice.roll(bonus, sides)

Vâng, nhưng một số người chơi có kỳ tích "có khả năng cực kỳ cao" để họ không bao giờ có thể "dò dẫm" (cuộn 1).

Dice.roll(bonus, sides, isFumbleAllowed).

Điều này đang trở nên lộn xộn, phải không?


điều này dường như là trực giao với câu hỏi, nó trở nên lộn xộn cho dù đây là phương pháp tĩnh hay phương thức bình thường
jk.

4
@jk, tôi nghĩ tôi hiểu quan điểm của anh ấy. Không có nghĩa là có một lớp Dice tĩnh khi bạn nghĩ về nhiều loại xúc xắc hơn. Trong trường hợp đó, chúng ta có thể có các đối tượng súc sắc khác nhau, mô hình hóa chúng với các thực hành OOP tốt.
Dherik

1
@TimothyTruckle Tôi không tranh luận rằng, tất cả những gì tôi nói là câu trả lời này không thực sự trả lời câu hỏi, bởi vì loại creep phạm vi này không liên quan gì đến phương thức tĩnh hay không.
nvoigt

2
@nvoigt "Tôi không tranh cãi về điều đó" - Vâng, tôi làm. Tính năng mạnh mẽ nhất của ngôn ngữ OO là đa hình . Và truy cập tĩnh ngăn chặn hiệu quả chúng ta sử dụng điều đó. Và bạn đã đúng: new Dice().roll(...)phải được truy cập tĩnh cũng như Dice.roll(...). Chúng tôi chỉ có lợi nếu chúng tôi tiêm sự phụ thuộc đó.
Timothy Truckle

2
@jk sự khác biệt là sự staticlộn xộn xảy ra trong mỗi cuộc gọi và kiến ​​thức cần có là cần thiết trong mỗi cuộc gọi, vì vậy nó sẽ bị văng khắp nơi trên ứng dụng của bạn. Trong cấu trúc OOP, chỉ có nhà xây dựng / nhà máy cần phải lộn xộn và các chi tiết của khuôn đó được gói gọn. Sau đó, sử dụng đa hình và chỉ cần gọi roll(). Tôi có thể chỉnh sửa câu trả lời này để làm rõ.
user949300

3

Trong trường hợp cụ thể của lớp Dice, tôi nghĩ rằng việc sử dụng các phương thức cá thể thay vì thống kê sẽ giúp việc kiểm tra dễ dàng hơn đáng kể.

Nếu bạn muốn kiểm tra đơn vị thứ gì đó sử dụng một thể hiện của Dice (ví dụ: lớp Trò chơi) thì từ các thử nghiệm của bạn, bạn có thể tiêm một số dạng thử nghiệm kép của Dice luôn trả về một chuỗi các giá trị cố định. Thử nghiệm của bạn có thể kiểm tra xem trò chơi có kết quả đúng cho những cuộn súc sắc đó không.

Tôi không phải là nhà phát triển Java nhưng tôi nghĩ sẽ khó hơn nhiều khi làm điều đó với lớp Dice hoàn toàn tĩnh. Xem /programming/4482315/why-does-mockito-not-mock-static-methods


Chỉ dành cho các bản ghi: Trong Java, chúng ta có PowerMock để thay thế các phụ thuộc tĩnh bằng cách thao tác mã byte. Nhưng sử dụng nó chỉ là sự đầu hàng cho thiết kế tồi ...
Timothy Truckle

0

Đây thực sự được gọi là mẫu Monostate , trong đó mọi trường hợp (và thậm chí là "không có trường hợp") đang chia sẻ trạng thái của chúng. Mỗi thành viên là một thành viên của lớp (tức là không có thành viên thể hiện). Chúng thường được sử dụng để triển khai các lớp "bộ công cụ" bao gồm một tập hợp các phương thức và hằng số liên quan đến một trách nhiệm hoặc yêu cầu duy nhất nhưng không cần một trạng thái để hoạt động (chúng hoàn toàn có chức năng). Trong thực tế, Java đi kèm với một số trong số chúng được gói (ví dụ: Math ).

Hơi lạc đề: Tôi hiếm khi đồng ý với việc đặt tên từ khóa trong VisualBasic, nhưng trong trường hợp này, tôi nghĩ sharedchắc chắn rõ ràng và tốt hơn về mặt ngữ nghĩa (nó được chia sẻ giữa chính lớp và tất cả các trường hợp của nó) so với static(vẫn còn sau vòng đời của phạm vi nó được khai báo).

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.