Có rất nhiều điều có thể nói về văn hóa Java, nhưng tôi nghĩ rằng trong trường hợp bạn phải đối mặt ngay bây giờ, có một vài khía cạnh quan trọng:
- Mã thư viện được viết một lần nhưng được sử dụng thường xuyên hơn nhiều. Mặc dù thật tuyệt khi giảm thiểu chi phí viết thư viện, nhưng về lâu dài có thể đáng viết hơn theo cách giảm thiểu chi phí sử dụng thư viện.
- Điều đó có nghĩa là các kiểu tự viết tài liệu rất hay: tên phương thức giúp làm rõ những gì đang xảy ra và những gì bạn đang thoát ra khỏi một đối tượng.
- Gõ tĩnh là một công cụ rất hữu ích để loại bỏ các lớp lỗi nhất định. Nó chắc chắn không sửa tất cả mọi thứ (mọi người thích nói đùa về Haskell rằng một khi bạn có được hệ thống loại chấp nhận mã của mình, điều đó có thể đúng), nhưng nó rất dễ biến những điều sai nhất định thành không thể.
- Viết mã thư viện là về việc chỉ định hợp đồng. Xác định giao diện cho loại đối số và loại kết quả của bạn làm cho ranh giới của hợp đồng của bạn được xác định rõ ràng hơn. Nếu một cái gì đó chấp nhận hoặc tạo ra một tuple, thì không thể nói đó là loại tuple mà bạn thực sự nên nhận hay sản xuất, và có rất ít cách thức ràng buộc đối với một loại chung như vậy (thậm chí nó có đúng số lượng phần tử không? Họ thuộc loại mà bạn đang mong đợi?).
Các lớp "Struct" với các trường
Như các câu trả lời khác đã đề cập, bạn chỉ có thể sử dụng một lớp với các trường công khai. Nếu bạn thực hiện những điều cuối cùng này, thì bạn sẽ có được một lớp bất biến và bạn sẽ khởi tạo chúng với hàm tạo:
class ParseResult0 {
public final long millis;
public final boolean isSeconds;
public final boolean isLessThanOneMilli;
public ParseResult0(long millis, boolean isSeconds, boolean isLessThanOneMilli) {
this.millis = millis;
this.isSeconds = isSeconds;
this.isLessThanOneMilli = isLessThanOneMilli;
}
}
Tất nhiên, điều này có nghĩa là bạn bị ràng buộc với một lớp cụ thể và bất kỳ thứ gì cần sản xuất hoặc tiêu thụ kết quả phân tích đều phải sử dụng lớp này. Đối với một số ứng dụng, điều đó tốt. Đối với những người khác, điều đó có thể gây ra một số nỗi đau. Nhiều mã Java là về việc xác định các hợp đồng và điều đó thường sẽ đưa bạn vào các giao diện.
Một điều đáng tiếc nữa là với cách tiếp cận dựa trên lớp, bạn đang phơi bày các trường và tất cả các trường đó phải có giá trị. Ví dụ, isSeconds và millis luôn phải có một số giá trị, ngay cả khi isLessThanOneMilli là đúng. Việc giải thích giá trị của trường millis là gì khi isLessThanOneMilli là đúng?
"Cấu trúc" là Giao diện
Với các phương thức tĩnh được phép trong các giao diện, thực sự tương đối dễ dàng để tạo các loại bất biến mà không cần nhiều chi phí cú pháp. Ví dụ, tôi có thể triển khai loại cấu trúc kết quả mà bạn đang nói như một thứ như thế này:
interface ParseResult {
long getMillis();
boolean isSeconds();
boolean isLessThanOneMilli();
static ParseResult from(long millis, boolean isSeconds, boolean isLessThanOneMill) {
return new ParseResult() {
@Override
public boolean isSeconds() {
return isSeconds;
}
@Override
public boolean isLessThanOneMilli() {
return isLessThanOneMill;
}
@Override
public long getMillis() {
return millis;
}
};
}
}
Tôi vẫn hoàn toàn đồng ý, tôi hoàn toàn đồng ý, nhưng cũng có một số lợi ích và tôi nghĩ rằng họ bắt đầu trả lời một số câu hỏi chính của bạn.
Với cấu trúc như kết quả phân tích cú pháp này, hợp đồng của trình phân tích cú pháp của bạn được xác định rất rõ ràng. Trong Python, một tuple không thực sự khác biệt với một tuple khác. Trong Java, gõ tĩnh có sẵn, vì vậy chúng tôi đã loại trừ một số loại lỗi nhất định. Chẳng hạn, nếu bạn trả lại một tuple bằng Python và bạn muốn trả lại tuple (millis, isSeconds, isLessThanOneMilli), bạn có thể vô tình làm:
return (true, 500, false)
khi bạn có nghĩa là:
return (500, true, false)
Với loại giao diện Java này, bạn không thể biên dịch:
return ParseResult.from(true, 500, false);
ở tất cả. Bạn phải làm:
return ParseResult.from(500, true, false);
Đó là một lợi ích của ngôn ngữ gõ tĩnh nói chung.
Cách tiếp cận này cũng bắt đầu cung cấp cho bạn khả năng hạn chế những giá trị bạn có thể nhận được. Chẳng hạn, khi gọi getMillis (), bạn có thể kiểm tra xem liệuLessThanOneMilli () có đúng không, và nếu có, hãy ném IllegalStateException (ví dụ), vì trường hợp đó không có giá trị ý nghĩa của millis.
Làm cho nó khó để làm điều sai
Trong ví dụ về giao diện ở trên, bạn vẫn gặp vấn đề là bạn có thể vô tình trao đổi các đối số isSeconds và isLessThanOneMilli, vì chúng có cùng loại.
Trong thực tế, bạn thực sự có thể muốn sử dụng TimeUnit và thời lượng, để bạn có kết quả như sau:
interface Duration {
TimeUnit getTimeUnit();
long getDuration();
static Duration from(TimeUnit unit, long duration) {
return new Duration() {
@Override
public TimeUnit getTimeUnit() {
return unit;
}
@Override
public long getDuration() {
return duration;
}
};
}
}
interface ParseResult2 {
boolean isLessThanOneMilli();
Duration getDuration();
static ParseResult2 from(TimeUnit unit, long duration) {
Duration d = Duration.from(unit, duration);
return new ParseResult2() {
@Override
public boolean isLessThanOneMilli() {
return false;
}
@Override
public Duration getDuration() {
return d;
}
};
}
static ParseResult2 lessThanOneMilli() {
return new ParseResult2() {
@Override
public boolean isLessThanOneMilli() {
return true;
}
@Override
public Duration getDuration() {
throw new IllegalStateException();
}
};
}
}
Đó là việc nhận được nhiều mã hơn, nhưng bạn chỉ cần viết một lần và (giả sử bạn đã ghi lại đúng thứ), những người cuối cùng sử dụng mã của bạn không phải đoán kết quả có nghĩa là gì, và không thể vô tình làm những việc như result[0]
khi chúng có ý nghĩa result[1]
. Bạn vẫn có thể tạo các cá thể khá ngắn gọn và việc lấy dữ liệu từ chúng cũng không quá khó:
ParseResult2 x = ParseResult2.from(TimeUnit.MILLISECONDS, 32);
ParseResult2 y = ParseResult2.lessThanOneMilli();
Lưu ý rằng bạn thực sự có thể làm một cái gì đó như thế này với cách tiếp cận dựa trên lớp, quá. Chỉ cần xác định các nhà xây dựng cho các trường hợp khác nhau. Tuy nhiên, bạn vẫn có vấn đề về việc khởi tạo các trường khác để làm gì và bạn không thể ngăn truy cập vào chúng.
Một câu trả lời khác đề cập rằng bản chất kiểu Java của doanh nghiệp có nghĩa là phần lớn thời gian, bạn đang soạn thư viện khác đã tồn tại hoặc viết thư viện cho người khác sử dụng. API công khai của bạn không cần nhiều thời gian tư vấn tài liệu để giải mã các loại kết quả nếu có thể tránh được.
Bạn chỉ viết các cấu trúc này một lần, nhưng bạn tạo chúng nhiều lần, vì vậy bạn vẫn muốn tạo ra súc tích đó (mà bạn nhận được). Việc gõ tĩnh đảm bảo rằng dữ liệu bạn nhận được từ chúng là những gì bạn mong đợi.
Bây giờ, tất cả những gì đã nói, vẫn còn những nơi mà các bộ hoặc danh sách đơn giản có thể có nhiều ý nghĩa. Có thể có ít chi phí hơn trong việc trả lại một mảng của một cái gì đó, và nếu đó là trường hợp (và chi phí đó là đáng kể, mà bạn xác định bằng cách định hình), thì sử dụng một mảng đơn giản các giá trị bên trong có thể có ý nghĩa. API công khai của bạn vẫn có thể có các loại được xác định rõ ràng.