เมื่อใดก็ตามที่ฉันเห็นวิธีที่พฤติกรรมเปลี่ยนชนิดของพารามิเตอร์ฉันจะพิจารณาก่อนทันทีว่าวิธีนั้นเป็นของพารามิเตอร์วิธีหรือไม่ ตัวอย่างเช่นแทนที่จะมีวิธีการเช่น:
public void sort(List values) {
if (values instanceof LinkedList) {
// do efficient linked list sort
} else { // ArrayList
// do efficient array list sort
}
}
ฉันจะทำสิ่งนี้:
values.sort();
// ...
class ArrayList {
public void sort() {
// do efficient array list sort
}
}
class LinkedList {
public void sort() {
// do efficient linked list sort
}
}
เราย้ายพฤติกรรมไปยังสถานที่ที่รู้ว่าจะใช้เมื่อใด เราสร้างสิ่งที่เป็นนามธรรมอย่างแท้จริงโดยที่คุณไม่จำเป็นต้องรู้ประเภทหรือรายละเอียดของการใช้งาน สำหรับสถานการณ์ของคุณก็อาจทำให้รู้สึกมากขึ้นที่จะย้ายวิธีนี้จากระดับเดิม (ซึ่งผมจะเรียกO
) ในการพิมพ์และแทนที่มันอยู่ในประเภทA
B
หากวิธีการที่เรียกว่าdoIt
วัตถุบางย้ายdoIt
ไปและแทนที่ด้วยพฤติกรรมที่แตกต่างกันในA
B
หากมีบิตข้อมูลจากที่doIt
เดิมเรียกว่าหรือหากมีการใช้วิธีการในสถานที่ที่เพียงพอคุณสามารถออกจากวิธีการเดิมและผู้รับมอบสิทธิ์:
class O {
int x;
int y;
public void doIt(A a) {
a.doIt(this.x, this.y);
}
}
เราสามารถดำน้ำลึกลงไปอีกหน่อย ลองดูข้อเสนอแนะเพื่อใช้พารามิเตอร์บูลีนแทนและดูสิ่งที่เราสามารถเรียนรู้เกี่ยวกับวิธีที่เพื่อนร่วมงานของคุณคิด ข้อเสนอของเขาคือต้องทำ:
public void doIt(A a, boolean isTypeB) {
if (isTypeB) {
// do B stuff
} else {
// do A stuff
}
}
นี่ดูน่ากลัวมากอย่างที่instanceof
ฉันใช้ในตัวอย่างแรกของฉันยกเว้นว่าเรากำลังทำการตรวจสอบภายนอก ซึ่งหมายความว่าเราจะต้องเรียกมันด้วยวิธีใดวิธีหนึ่งจากสองวิธี:
o.doIt(a, a instanceof B);
หรือ:
o.doIt(a, true); //or false
ในวิธีแรกจุดโทรไม่ทราบว่าเป็นประเภทA
ใด ดังนั้นเราควรผ่านบูลีนไปเรื่อย ๆ หรือไม่? นั่นเป็นรูปแบบที่เราต้องการทั่วฐานรหัสหรือไม่ จะเกิดอะไรขึ้นหากมีประเภทที่สามที่เราต้องพิจารณา หากนี่เป็นวิธีการที่เรียกว่าเราควรย้ายไปที่ประเภทและให้ระบบเลือกการใช้งานสำหรับเรา polymorphically
ในวิธีที่สองเราต้องทราบชนิดของa
ที่จุดโทรแล้ว โดยทั่วไปนั่นหมายถึงเรากำลังสร้างอินสแตนซ์ที่นั่นหรือรับอินสแตนซ์ของประเภทนั้นเป็นพารามิเตอร์ การสร้างวิธีการO
ที่ใช้B
ที่นี่จะได้ผล คอมไพเลอร์จะทราบวิธีการเลือก เมื่อเราขับรถผ่านการเปลี่ยนแปลงเช่นนี้การทำซ้ำก็ดีกว่าการสร้างสิ่งที่ผิดอย่างน้อยก็อย่างน้อยก็จนกว่าเราจะรู้ว่าเรากำลังจะไปไหน แน่นอนฉันขอแนะนำว่าเราไม่ได้ทำจริงไม่ว่าสิ่งที่เราเปลี่ยนไปถึงจุดนี้
เราต้องดูอย่างใกล้ชิดที่ความสัมพันธ์ระหว่างและA
B
โดยทั่วไปเราจะบอกว่าเราควรจะให้ประโยชน์แก่องค์ประกอบมรดก นี้ไม่เป็นความจริงในทุกกรณี แต่มันเป็นความจริงในจำนวนที่น่าแปลกใจของกรณีเมื่อเราขุดใน. B
สืบทอดมาจากA
ความหมายที่เราเชื่อว่าเป็นB
ควรใช้เหมือนยกเว้นว่ามันใช้งานได้แตกต่างกันเล็กน้อย แต่ความแตกต่างเหล่านั้นคืออะไร? เราจะให้ชื่อที่เป็นรูปธรรมมากขึ้นได้หรือไม่? คือมันไม่ได้เป็นแต่จริงๆมีที่อาจจะหรือ? รหัสของเราจะเป็นอย่างไรถ้าเราทำอย่างนั้น?A
B
A
B
A
A
X
A'
B'
หากเราย้ายวิธีการไปA
ตามที่แนะนำไว้ก่อนหน้านี้เราสามารถแทรกตัวอย่างของX
เข้าไปA
และมอบหมายวิธีการนั้นให้กับX
:
class A {
X x;
A(X x) {
this.x = x;
}
public void doIt(int x, int y) {
x.doIt(x, y);
}
}
เราสามารถดำเนินการA'
และและกำจัดB'
B
เราได้ปรับปรุงโค้ดโดยให้ชื่อกับแนวคิดที่อาจมีความหมายโดยนัยมากกว่าและอนุญาตให้เรากำหนดพฤติกรรมนั้นในเวลารันไทม์แทนการรวบรวมเวลา A
จริง ๆ แล้วก็กลายเป็นนามธรรมน้อยลงเช่นกัน แทนที่จะเป็นความสัมพันธ์ที่สืบทอดเพิ่มเติมการเรียกใช้เมธอดบนวัตถุที่มอบหมาย วัตถุนั้นเป็นนามธรรม แต่เน้นเฉพาะความแตกต่างในการนำไปใช้
มีสิ่งสุดท้ายที่ต้องพิจารณาคือ ลองย้อนกลับไปที่ข้อเสนอของเพื่อนร่วมงานของคุณ หากไซต์การโทรทั้งหมดที่เราทราบประเภทของA
เราอย่างชัดเจนเราควรทำการโทรเช่น:
B b = new B();
o.doIt(b, true);
เราสันนิษฐานว่าก่อนหน้านี้เมื่อเขียนว่าA
มีX
ที่เป็นอย่างใดอย่างหนึ่งหรือA'
B'
แต่บางทีข้อสันนิษฐานนี้อาจไม่ถูกต้อง นี่คือสถานที่เดียวที่แตกต่างกันนี้ระหว่างA
และB
เรื่อง? ถ้าเป็นเช่นนั้นบางทีเราสามารถใช้แนวทางที่แตกต่างกันเล็กน้อย เรายังมีX
ที่เป็นอย่างใดอย่างหนึ่งA'
หรือแต่มันไม่ได้อยู่ในB'
A
เพียงO.doIt
ใส่ใจเกี่ยวกับเรื่องนี้จึงขอเพียงผ่านไปO.doIt
:
class O {
int x;
int y;
public void doIt(A a, X x) {
x.doIt(a, x, y);
}
}
ตอนนี้ไซต์การโทรของเราดูเหมือนว่า:
A a = new A();
o.doIt(a, new B'());
อีกครั้งหนึ่งที่หายไปและย้ายเข้าสู่นามธรรมเน้นB
X
แม้ว่าเวลาA
นี้จะง่ายกว่าโดยไม่รู้น้อย มันเป็นนามธรรมที่น้อยลง
สิ่งสำคัญคือการลดการทำซ้ำในฐานของรหัส แต่เราต้องพิจารณาว่าทำไมการทำซ้ำเกิดขึ้นตั้งแต่แรก การทำสำเนาอาจเป็นสัญญาณของ abstractions ที่ลึกล้ำที่พยายามจะออกไป