มีหลายสิ่งหลายอย่างที่สามารถพูดได้เกี่ยวกับวัฒนธรรม Java แต่ฉันคิดว่าในกรณีที่คุณกำลังเผชิญหน้ากับตอนนี้มีแง่มุมที่สำคัญบางประการ:
- รหัสห้องสมุดถูกเขียนครั้งเดียว แต่ใช้บ่อยกว่ามาก ในขณะที่มันเป็นการดีที่จะลดค่าใช้จ่ายในการเขียนห้องสมุดให้น้อยลงมันอาจคุ้มค่ากว่าในระยะยาวที่จะเขียนด้วยวิธีที่ลดค่าใช้จ่ายในการใช้ห้องสมุดให้น้อยที่สุด
- ซึ่งหมายความว่าประเภทการบันทึกเอกสารด้วยตนเองนั้นยอดเยี่ยม: ชื่อเมธอดช่วยให้ชัดเจนว่าเกิดอะไรขึ้นและสิ่งที่คุณได้รับจากวัตถุ
- การพิมพ์สแตติกเป็นเครื่องมือที่มีประโยชน์มากสำหรับการขจัดข้อผิดพลาดบางประเภท แน่นอนว่ามันไม่ได้แก้ไขทุกอย่าง (ผู้คนชอบพูดตลกเกี่ยวกับ Haskell ว่าเมื่อคุณได้รับระบบพิมพ์เพื่อรับรหัสของคุณมันอาจจะถูกต้อง) แต่มันทำให้ง่ายมากที่จะทำสิ่งผิดปกติบางอย่างเป็นไปไม่ได้
- การเขียนรหัสห้องสมุดเป็นเรื่องเกี่ยวกับการระบุสัญญา การกำหนดอินเทอร์เฟซสำหรับอาร์กิวเมนต์และประเภทผลลัพธ์ของคุณจะทำให้ขอบเขตของสัญญาของคุณชัดเจนยิ่งขึ้น หากสิ่งที่ยอมรับหรือก่อให้เกิด tuple ไม่มีอะไรบอกได้เลยว่ามันเป็น tuple ที่คุณควรจะได้รับหรือสร้างขึ้นมาจริง ๆ และมีข้อ จำกัด น้อยมากในประเภททั่วไปเช่นนี้ (มันมีองค์ประกอบที่ถูกต้องหรือไม่? เป็นประเภทที่คุณคาดหวังหรือไม่)
คลาส "โครงสร้าง" พร้อมฟิลด์
ดังที่คำตอบอื่น ๆ ได้กล่าวไว้คุณสามารถใช้คลาสกับฟิลด์สาธารณะได้ หากคุณทำขั้นตอนสุดท้ายคุณจะได้คลาสที่ไม่เปลี่ยนรูปและคุณจะเริ่มต้นพวกมันด้วย Constructor:
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;
}
}
แน่นอนนี่หมายความว่าคุณผูกติดอยู่กับชั้นเรียนเฉพาะและสิ่งใดก็ตามที่จำเป็นในการสร้างหรือใช้ผลการแยกวิเคราะห์จะต้องใช้คลาสนี้ สำหรับแอปพลิเคชั่นบางตัวก็ใช้ได้ สำหรับคนอื่นที่อาจทำให้เกิดความเจ็บปวด รหัส Java ส่วนใหญ่เกี่ยวกับการกำหนดสัญญาและโดยทั่วไปจะนำคุณไปสู่ส่วนต่อประสาน
ข้อผิดพลาดอีกอย่างหนึ่งก็คือด้วยวิธีการตามคลาสคุณจะเปิดเผยฟิลด์และฟิลด์เหล่านั้นทั้งหมดต้องมีค่า เช่น isSeconds และ millis ต้องมีค่าบางอย่างแม้ว่า isLessThanOneMilli จะเป็นจริง การตีความมูลค่าของฟิลด์มิลลิวินาทีควรเป็นอย่างไรเมื่อ isLessThanOneMilli เป็นจริง
"โครงสร้าง" เป็นอินเทอร์เฟซ
ด้วยวิธีการแบบคงที่ที่ได้รับอนุญาตในส่วนติดต่อจริง ๆ แล้วมันค่อนข้างง่ายในการสร้างประเภทที่ไม่เปลี่ยนรูปได้โดยไม่ต้องมีค่าโสหุ้ยทั้งหมด ตัวอย่างเช่นฉันอาจใช้โครงสร้างผลชนิดที่คุณพูดถึงในลักษณะนี้:
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;
}
};
}
}
นั่นก็ยังคงเป็นจำนวนมากฉันเห็นด้วยอย่างแน่นอน แต่ก็มีประโยชน์สองสามอย่างเช่นกันและฉันคิดว่าสิ่งเหล่านั้นจะเริ่มตอบคำถามหลักของคุณ
ด้วยโครงสร้างเช่นผลลัพธ์การแยกวิเคราะห์นี้สัญญาของตัวแยกวิเคราะห์ของคุณจะถูกกำหนดไว้อย่างชัดเจนมาก ใน Python หนึ่ง tuple ไม่แตกต่างจาก tuple อื่น ใน Java การพิมพ์แบบสแตติกสามารถใช้ได้ดังนั้นเราจึงแยกแยะข้อผิดพลาดบางประเภท ตัวอย่างเช่นหากคุณส่งคืน tuple ใน Python และคุณต้องการส่งคืน tuple (มิลลิวินาที, isSeconds, isLessThanOneMilli) คุณสามารถทำได้โดยไม่ตั้งใจ:
return (true, 500, false)
เมื่อคุณหมายถึง:
return (500, true, false)
ด้วยอินเตอร์เฟส Java ชนิดนี้คุณไม่สามารถรวบรวมได้:
return ParseResult.from(true, 500, false);
เลย คุณต้องทำ:
return ParseResult.from(500, true, false);
นั่นเป็นข้อดีของภาษาที่พิมพ์แบบคงที่โดยทั่วไป
วิธีการนี้เริ่มต้นด้วยการให้ความสามารถในการ จำกัด ค่าที่คุณจะได้รับ ตัวอย่างเช่นเมื่อเรียกใช้ getMillis () คุณสามารถตรวจสอบว่า isLessThanOneMilli () เป็นจริงได้หรือไม่และโยนทิ้ง IllegalStateException (เช่น) เนื่องจากไม่มีค่าที่มีความหมายของมิลลิวินาทีในกรณีนั้น
ทำให้เป็นเรื่องยากที่จะทำผิด
ในตัวอย่างอินเทอร์เฟซด้านบนคุณยังคงมีปัญหาที่คุณสามารถสลับอาร์กิวเมนต์ isSeconds และ isLessThanOneMilli โดยบังเอิญได้เนื่องจากมีชนิดเดียวกัน
ในทางปฏิบัติคุณอาจต้องการใช้ประโยชน์จาก TimeUnit และระยะเวลาเพื่อให้คุณได้ผลลัพธ์ดังนี้:
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();
}
};
}
}
นั่นคือการได้รับโค้ดที่มากขึ้น แต่คุณต้องเขียนเพียงครั้งเดียวและ (สมมติว่าคุณได้ทำเอกสารอย่างถูกต้อง) ผู้ที่จบลงด้วยการใช้รหัสของคุณไม่จำเป็นต้องเดาว่าผลลัพธ์มีความหมายอย่างไรและ ไม่สามารถตั้งใจทำสิ่งที่ต้องการเมื่อพวกเขาหมายถึงresult[0]
result[1]
คุณยังคงได้รับการสร้างอินสแตนซ์ที่ค่อนข้างรัดกุมและการดึงข้อมูลออกมาไม่ได้ยากอย่างนั้น:
ParseResult2 x = ParseResult2.from(TimeUnit.MILLISECONDS, 32);
ParseResult2 y = ParseResult2.lessThanOneMilli();
โปรดทราบว่าคุณสามารถทำอะไรแบบนี้ได้จริงด้วยวิธีการเรียนแบบพื้นฐานเช่นกัน เพียงระบุตัวสร้างสำหรับกรณีที่แตกต่างกัน คุณยังคงมีปัญหาของสิ่งที่จะเริ่มต้นเขตข้อมูลอื่น ๆ ถึงและคุณไม่สามารถป้องกันการเข้าถึงพวกเขา
คำตอบอีกข้อหนึ่งกล่าวว่าลักษณะประเภทองค์กรของ Java หมายถึงเวลาส่วนใหญ่คุณกำลังเขียนไลบรารีอื่น ๆ ที่มีอยู่แล้วหรือเขียนไลบรารีเพื่อให้ผู้อื่นใช้ API สาธารณะของคุณไม่ควรใช้เวลานานในการอ่านเอกสารเพื่อถอดรหัสประเภทผลลัพธ์หากสามารถหลีกเลี่ยงได้
คุณเขียนโครงสร้างเหล่านี้เพียงครั้งเดียว แต่คุณสร้างได้หลายครั้งดังนั้นคุณยังต้องการการสร้างที่กระชับ (ซึ่งคุณได้รับ) การพิมพ์แบบคงที่ทำให้แน่ใจได้ว่าข้อมูลที่คุณได้รับนั้นเป็นสิ่งที่คุณคาดหวัง
ตอนนี้ทุกอย่างที่กล่าวว่ายังคงมีสถานที่ที่สิ่งอันดับหรือรายการง่าย ๆ สามารถสร้างความรู้สึกได้อย่างมากมาย อาจมีค่าใช้จ่ายน้อยลงในการส่งคืนอาเรย์ของบางสิ่งและหากเป็นเช่นนั้น (และค่าใช้จ่ายนั้นมีความสำคัญซึ่งคุณต้องพิจารณาด้วยการทำโปรไฟล์) ดังนั้นการใช้อาเรย์แบบง่าย ๆ ของค่าภายในอาจมีเหตุผลมากมาย API สาธารณะของคุณยังควรมีประเภทที่กำหนดไว้อย่างชัดเจน