ไม่มีความแตกต่างระหว่างวัตถุ; คุณมีHashMap<String, Object>
ทั้งสองกรณี มีความแตกต่างในส่วนต่อประสานที่คุณมีกับวัตถุ ในกรณีแรกที่อินเตอร์เฟซเป็นในขณะที่สองมันเป็นHashMap<String, Object>
Map<String, Object>
แต่วัตถุต้นแบบนั้นเหมือนกัน
ข้อได้เปรียบในการใช้Map<String, Object>
คือคุณสามารถเปลี่ยนวัตถุต้นแบบให้เป็นแผนที่ชนิดอื่นโดยไม่ทำลายสัญญาของคุณด้วยรหัสใด ๆ ที่ใช้งาน หากคุณประกาศเป็นHashMap<String, Object>
คุณต้องเปลี่ยนสัญญาของคุณหากคุณต้องการเปลี่ยนการใช้งานพื้นฐาน
ตัวอย่าง: สมมุติว่าฉันเขียนคลาสนี้:
class Foo {
private HashMap<String, Object> things;
private HashMap<String, Object> moreThings;
protected HashMap<String, Object> getThings() {
return this.things;
}
protected HashMap<String, Object> getMoreThings() {
return this.moreThings;
}
public Foo() {
this.things = new HashMap<String, Object>();
this.moreThings = new HashMap<String, Object>();
}
// ...more...
}
ชั้นมีแผนที่ภายในของวัตถุ string-> ที่ใช้ร่วมกัน (ผ่านวิธีการเข้าถึง) กับ subclasses สมมติว่าฉันเขียนด้วยHashMap
s เพื่อเริ่มต้นด้วยเพราะฉันคิดว่านั่นเป็นโครงสร้างที่เหมาะสมที่จะใช้เมื่อเขียนชั้นเรียน
ต่อมาแมรีเขียนรหัสการทำคลาสย่อย เธอมีบางอย่างที่เธอต้องการทำกับทั้งคู่things
และmoreThings
โดยปกติแล้วเธอวางไว้ในวิธีการทั่วไปและเธอใช้ประเภทเดียวกับที่ฉันใช้บนgetThings
/ getMoreThings
เมื่อกำหนดวิธีการของเธอ:
class SpecialFoo extends Foo {
private void doSomething(HashMap<String, Object> t) {
// ...
}
public void whatever() {
this.doSomething(this.getThings());
this.doSomething(this.getMoreThings());
}
// ...more...
}
ต่อมาผมตัดสินใจว่าจริง ๆ แล้วมันจะดีกว่าถ้าผมใช้TreeMap
แทนในHashMap
Foo
ฉันปรับปรุงFoo
เปลี่ยนแปลงไปHashMap
TreeMap
ตอนนี้SpecialFoo
ไม่ได้รวบรวมอีกต่อไปเพราะฉันทำผิดสัญญา: Foo
เคยบอกว่ามันให้HashMap
s แต่ตอนนี้มันให้TreeMaps
แทน ดังนั้นเราต้องแก้ไขทันทีSpecialFoo
(และสิ่งนี้สามารถกระเพื่อมผ่าน codebase)
นอกจากว่าฉันมีเหตุผลที่ดีจริงๆสำหรับการแบ่งปันว่าการนำไปใช้ของฉันกำลังใช้HashMap
(และนั่นเกิดขึ้น) สิ่งที่ฉันควรทำคือประกาศgetThings
และgetMoreThings
กลับมาMap<String, Object>
โดยไม่ต้องเจาะจงมากไปกว่านั้น ในความเป็นจริงการจํากัดเป็นเหตุผลที่ดีที่จะทำอย่างอื่นแม้จะอยู่ในFoo
ฉันอาจจะประกาศthings
และmoreThings
เป็นMap
ไม่HashMap
/ TreeMap
:
class Foo {
private Map<String, Object> things; // <== Changed
private Map<String, Object> moreThings; // <== Changed
protected Map<String, Object> getThings() { // <== Changed
return this.things;
}
protected Map<String, Object> getMoreThings() { // <== Changed
return this.moreThings;
}
public Foo() {
this.things = new HashMap<String, Object>();
this.moreThings = new HashMap<String, Object>();
}
// ...more...
}
สังเกตว่าตอนนี้ฉันกำลังใช้Map<String, Object>
ทุกที่ฉันทำได้เฉพาะเจาะจงเมื่อฉันสร้างวัตถุจริงเท่านั้น
ถ้าฉันทำอย่างนั้นแล้วแมรี่ก็จะทำสิ่งนี้:
class SpecialFoo extends Foo {
private void doSomething(Map<String, Object> t) { // <== Changed
// ...
}
public void whatever() {
this.doSomething(this.getThings());
this.doSomething(this.getMoreThings());
}
}
... และการเปลี่ยนแปลงFoo
จะไม่ทำให้SpecialFoo
การคอมไพล์หยุด
อินเทอร์เฟซ (และคลาสพื้นฐาน) ให้เราเปิดเผยเท่าที่จำเป็นเท่านั้นทำให้ความยืดหยุ่นของเราอยู่ภายใต้การคุ้มครองเพื่อทำการเปลี่ยนแปลงตามความเหมาะสม โดยทั่วไปเราต้องการให้การอ้างอิงของเราเป็นพื้นฐานที่สุด ถ้าเราไม่จำเป็นต้องรู้ว่ามันเป็นเพียงแค่เรียกมันว่าHashMap
Map
นี่ไม่ใช่กฎตาบอด แต่โดยทั่วไปการเข้ารหัสไปยังอินเทอร์เฟซทั่วไปส่วนใหญ่จะมีความเปราะน้อยกว่าการเขียนโค้ดลงในบางสิ่งที่เฉพาะเจาะจงมากขึ้น ถ้าฉันจำได้ว่าฉันจะไม่ได้สร้างที่ตั้งแมรี่สำหรับความล้มเหลวด้วยFoo
SpecialFoo
หากแมรี่จำได้ว่าถึงแม้ฉันจะทำผิดFoo
เธอก็จะประกาศวิธีการส่วนตัวของเธอMap
แทนHashMap
และFoo
สัญญาการเปลี่ยนแปลงของฉันจะไม่ส่งผลกระทบต่อรหัสของเธอ
บางครั้งคุณทำไม่ได้บางครั้งคุณต้องเจาะจง แต่ถ้าคุณไม่มีเหตุผลให้ไปที่ส่วนต่อประสานที่เจาะจงน้อยที่สุด