ความหลากหลายและการถ่ายทอดทางพันธุกรรมใน Java

ตามตำนาน Venkat Subramaniam ความหลากหลายเป็นแนวคิดที่สำคัญที่สุดในการเขียนโปรแกรมเชิงวัตถุ ความหลากหลาย - หรือความสามารถของอ็อบเจ็กต์ในการดำเนินการเฉพาะตามประเภท - คือสิ่งที่ทำให้โค้ด Java มีความยืดหยุ่น รูปแบบการออกแบบเช่น Command, Observer, Decorator, Strategy และอื่น ๆ อีกมากมายที่สร้างโดย Gang Of Four ล้วนแล้วแต่ใช้ความหลากหลายรูปแบบ การเรียนรู้แนวคิดนี้ช่วยเพิ่มความสามารถในการคิดแก้ปัญหาเกี่ยวกับการเขียนโปรแกรมได้อย่างมาก

รับรหัส

คุณสามารถรับซอร์สโค้ดสำหรับความท้าทายนี้และทำการทดสอบของคุณเองได้ที่นี่: //github.com/rafadelnero/javaworld-challengers

การเชื่อมต่อและการถ่ายทอดทางพันธุกรรมในความหลากหลาย

ด้วย Java Challenger นี้เรามุ่งเน้นไปที่ความสัมพันธ์ระหว่างความหลากหลายและการถ่ายทอดทางพันธุกรรม สิ่งสำคัญที่ต้องเก็บไว้ในใจก็คือว่าต้อง polymorphism มรดกหรืออินเตอร์เฟซการใช้งาน คุณสามารถดูสิ่งนี้ได้ในตัวอย่างด้านล่างซึ่งมี Duke และ Juggy:

 public abstract class JavaMascot { public abstract void executeAction(); } public class Duke extends JavaMascot { @Override public void executeAction() { System.out.println("Punch!"); } } public class Juggy extends JavaMascot { @Override public void executeAction() { System.out.println("Fly!"); } } public class JavaMascotTest { public static void main(String... args) { JavaMascot dukeMascot = new Duke(); JavaMascot juggyMascot = new Juggy(); dukeMascot.executeAction(); juggyMascot.executeAction(); } } 

ผลลัพธ์จากรหัสนี้จะเป็น:

 Punch! Fly! 

เนื่องจากการใช้งานเฉพาะของพวกเขาการดำเนินการทั้งสองDukeและJuggyจะถูกดำเนินการ

วิธีการเป็น polymorphism มากเกินไปหรือไม่?

โปรแกรมเมอร์หลายคนสับสนเกี่ยวกับความสัมพันธ์ของความหลากหลายกับวิธีการแทนที่และวิธีการโอเวอร์โหลด ในความเป็นจริงการลบล้างวิธีการเท่านั้นคือความหลากหลายที่แท้จริง การโอเวอร์โหลดใช้ชื่อเมธอดเดียวกัน แต่พารามิเตอร์ต่างกัน Polymorphism เป็นคำที่กว้างดังนั้นจึงมักจะมีการอภิปรายเกี่ยวกับหัวข้อนี้

จุดประสงค์ของความหลากหลายคืออะไร?

ข้อดีและจุดประสงค์ของการใช้ความหลากหลายคือการแยกคลาสไคลเอนต์ออกจากโค้ดการใช้งาน แทนที่จะเป็นการฮาร์ดโค้ดคลาสไคลเอนต์จะได้รับการนำไปใช้เพื่อดำเนินการตามที่จำเป็น ด้วยวิธีนี้คลาสไคลเอนต์จึงรู้เพียงพอที่จะดำเนินการซึ่งเป็นตัวอย่างของการมีเพศสัมพันธ์แบบหลวม ๆ

เพื่อให้เข้าใจจุดประสงค์ของความหลากหลายมากขึ้นให้ดูที่SweetCreator:

 public abstract class SweetProducer { public abstract void produceSweet(); } public class CakeProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cake produced"); } } public class ChocolateProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Chocolate produced"); } } public class CookieProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cookie produced"); } } public class SweetCreator { private List sweetProducer; public SweetCreator(List sweetProducer) { this.sweetProducer = sweetProducer; } public void createSweets() { sweetProducer.forEach(sweet -> sweet.produceSweet()); } } public class SweetCreatorTest { public static void main(String... args) { SweetCreator sweetCreator = new SweetCreator(Arrays.asList(new CakeProducer(), new ChocolateProducer(), new CookieProducer())); sweetCreator.createSweets(); } } 

ในตัวอย่างนี้คุณจะเห็นว่าSweetCreatorคลาสเท่านั้นที่รู้จัก SweetProducer คลาส Sweetมันไม่ทราบว่าการดำเนินงานของแต่ละคน การแยกดังกล่าวทำให้เรามีความยืดหยุ่นในการอัปเดตและนำคลาสของเรากลับมาใช้ใหม่และทำให้การบำรุงรักษาโค้ดง่ายขึ้น เมื่อออกแบบโค้ดของคุณให้มองหาวิธีที่จะทำให้มีความยืดหยุ่นและบำรุงรักษาได้มากที่สุด polymorphism เป็นเทคนิคที่ทรงพลังมากที่จะใช้เพื่อจุดประสงค์เหล่านี้

เคล็ดลับ : @Overrideคำอธิบายประกอบบังคับให้โปรแกรมเมอร์ต้องใช้ลายเซ็นวิธีเดียวกับที่ต้องถูกแทนที่ หากวิธีการไม่ถูกแทนที่จะมีข้อผิดพลาดในการคอมไพล์

ประเภทการส่งคืน Covariant ในการแทนที่เมธอด

เป็นไปได้ที่จะเปลี่ยนประเภทการส่งคืนของวิธีการที่ถูกลบล้างหากเป็นประเภทโควาเรีย ประเภท covariantเป็นพื้น subclass ของชนิดการส่งคืน ลองพิจารณาตัวอย่าง:

 public abstract class JavaMascot { abstract JavaMascot getMascot(); } public class Duke extends JavaMascot { @Override Duke getMascot() { return new Duke(); } } 

เนื่องจากDukeเป็น a JavaMascotเราจึงสามารถเปลี่ยนประเภทการส่งคืนเมื่อทำการลบล้าง

Polymorphism กับคลาส Java หลัก

เราใช้ความหลากหลายตลอดเวลาในคลาส Java หลัก ตัวอย่างง่ายๆอย่างหนึ่งคือเมื่อเราสร้างอินสแตนซ์ArrayListคลาสที่ประกาศ   Listอินเทอร์เฟซเป็นประเภท:

 List list = new ArrayList(); 

หากต้องการดำเนินการเพิ่มเติมให้พิจารณาตัวอย่างโค้ดนี้โดยใช้ Java Collections API ที่ไม่มีความหลากหลาย:

 public class ListActionWithoutPolymorphism { // Example without polymorphism void executeVectorActions(Vector vector) {/* Code repetition here*/} void executeArrayListActions(ArrayList arrayList) {/*Code repetition here*/} void executeLinkedListActions(LinkedList linkedList) {/* Code repetition here*/} void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList copyOnWriteArrayList) { /* Code repetition here*/} } public class ListActionInvokerWithoutPolymorphism { listAction.executeVectorActions(new Vector()); listAction.executeArrayListActions(new ArrayList()); listAction.executeLinkedListActions(new LinkedList()); listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList()); } 

รหัสน่าเกลียดไม่ใช่เหรอ? นึกว่าจะพยายามรักษา! ตอนนี้ดูตัวอย่างเดียวกันกับความหลากหลาย:

 public static void main(String … polymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(); } public class ListAction { void executeListActions(List list) { // Execute actions with different lists } } public class ListActionInvoker { public static void main(String... masterPolymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(new Vector()); listAction.executeListActions(new ArrayList()); listAction.executeListActions(new LinkedList()); listAction.executeListActions(new CopyOnWriteArrayList()); } } 

ประโยชน์ของความหลากหลายคือความยืดหยุ่นและความสามารถในการขยายตัว แทนที่จะสร้างวิธีการที่แตกต่างกันเราสามารถประกาศเพียงวิธีเดียวที่ได้รับListประเภททั่วไป

การเรียกใช้วิธีการเฉพาะในการเรียกเมธอด polymorphic

เป็นไปได้ที่จะเรียกใช้วิธีการเฉพาะในการเรียกแบบหลายรูปแบบ แต่การทำเช่นนี้ต้องแลกมาด้วยความยืดหยุ่น นี่คือตัวอย่าง:

 public abstract class MetalGearCharacter { abstract void useWeapon(String weapon); } public class BigBoss extends MetalGearCharacter { @Override void useWeapon(String weapon) { System.out.println("Big Boss is using a " + weapon); } void giveOrderToTheArmy(String orderMessage) { System.out.println(orderMessage); } } public class SolidSnake extends MetalGearCharacter { void useWeapon(String weapon) { System.out.println("Solid Snake is using a " + weapon); } } public class UseSpecificMethod { public static void executeActionWith(MetalGearCharacter metalGearCharacter) { metalGearCharacter.useWeapon("SOCOM"); // The below line wouldn't work // metalGearCharacter.giveOrderToTheArmy("Attack!"); if (metalGearCharacter instanceof BigBoss) { ((BigBoss) metalGearCharacter).giveOrderToTheArmy("Attack!"); } } public static void main(String... specificPolymorphismInvocation) { executeActionWith(new SolidSnake()); executeActionWith(new BigBoss()); } } 

เทคนิคที่เราใช้ในที่นี้คือการแคสต์หรือจงใจเปลี่ยนประเภทวัตถุในขณะรันไทม์

โปรดทราบว่าเป็นไปได้ที่จะเรียกใช้วิธีการเฉพาะเมื่อแคสต์ประเภททั่วไปเป็นประเภทเฉพาะเท่านั้น การเปรียบเทียบที่ดีคือการพูดกับคอมไพเลอร์อย่างชัดเจนว่า“ เฮ้ฉันรู้ว่าฉันกำลังทำอะไรอยู่ที่นี่ดังนั้นฉันจะโยนวัตถุไปยังประเภทที่เฉพาะเจาะจงและใช้วิธีการเฉพาะ”  

จากตัวอย่างข้างต้นมีเหตุผลสำคัญที่คอมไพลเลอร์ปฏิเสธที่จะยอมรับการเรียกใช้เมธอดเฉพาะ: คลาสที่กำลังส่งผ่านอาจเป็นSolidSnakeได้ ในกรณีนี้ไม่มีวิธีใดที่คอมไพลเลอร์จะตรวจสอบให้แน่ใจว่าทุกคลาสย่อยMetalGearCharacterมีgiveOrderToTheArmyวิธีการที่ประกาศ

instanceofคำหลักที่สงวนไว้

ใส่ใจกับคำสงวนinstanceof. ก่อนที่จะเรียกใช้วิธีการเฉพาะที่เราเคยถามว่าคือ“MetalGearCharacter ” ถ้ามันไม่ได้เช่นเราจะได้รับข้อความแสดงข้อยกเว้นต่อไปนี้:instanceofBigBossBigBoss

 Exception in thread "main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss 

superคำหลักที่สงวนไว้

จะเป็นอย่างไรหากเราต้องการอ้างอิงแอตทริบิวต์หรือวิธีการจาก Java superclass? ในกรณีนี้เราสามารถใช้superคำสงวน ตัวอย่างเช่น:

 public class JavaMascot { void executeAction() { System.out.println("The Java Mascot is about to execute an action!"); } } public class Duke extends JavaMascot { @Override void executeAction() { super.executeAction(); System.out.println("Duke is going to punch!"); } public static void main(String... superReservedWord) { new Duke().executeAction(); } } 

Using the reserved word super in Duke’s executeAction method  invokes the superclass method.  We then execute the specific action from Duke. That’s why we can see both messages in the output below:

 The Java Mascot is about to execute an action! Duke is going to punch! 

Take the polymorphism challenge!

Let’s try out what you’ve learned about polymorphism and inheritance. In this challenge, you’re given a handful of methods from Matt Groening’s The Simpsons, and your challenge is to deduce what the output for each class will be. To start, analyze the following code carefully:

 public class PolymorphismChallenge { static abstract class Simpson { void talk() { System.out.println("Simpson!"); } protected void prank(String prank) { System.out.println(prank); } } static class Bart extends Simpson { String prank; Bart(String prank) { this.prank = prank; } protected void talk() { System.out.println("Eat my shorts!"); } protected void prank() { super.prank(prank); System.out.println("Knock Homer down"); } } static class Lisa extends Simpson { void talk(String toMe) { System.out.println("I love Sax!"); } } public static void main(String... doYourBest) { new Lisa().talk("Sax :)"); Simpson simpson = new Bart("D'oh"); simpson.talk(); Lisa lisa = new Lisa(); lisa.talk(); ((Bart) simpson).prank(); } } 

What do you think? What will the final output be? Don’t use an IDE to figure this out! The point is to improve your code analysis skills, so try to determine the output for yourself.

Choose your answer and you’ll be able to find the correct answer below.

 A) I love Sax! D'oh Simpson! D'oh B) Sax :) Eat my shorts! I love Sax! D'oh Knock Homer down C) Sax :) D'oh Simpson! Knock Homer down D) I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down 

What just happened? Understanding polymorphism

For the following method invocation:

 new Lisa().talk("Sax :)"); 

the output will be “I love Sax!” This is  because we are passing a String to the method and Lisa has the method.

For the next invocation:

 Simpson simpson = new Bart("D'oh");

simpson.talk();

The output will be "Eat my shorts!" This is because we’re instantiating  the Simpson type with Bart.

Now check this one, which is a little trickier:

 Lisa lisa = new Lisa(); lisa.talk(); 

Here, we are using method overloading with inheritance. We are not passing anything to the talk method, which is why the Simpson talk method is invoked.  In this case the output will be:

 "Simpson!" 

Here’s one more:

 ((Bart) simpson).prank(); 

In this case, the prank String was passed when we instantiated the Bart class with new Bart("D'oh");. In this case,  first the super.prank method will be invoked, followed by the specific prank method from Bart. The output will be:

 "D'oh" "Knock Homer down" 

Video challenge! Debugging Java polymorphism and inheritance

Debugging is one of the easiest ways to fully absorb programming concepts while also improving your code. In this video you can follow along while I debug and explain the Java polymorphism challenge:

Common mistakes with polymorphism

It’s a common mistake to think it’s possible to invoke a specific method without using casting.

Another mistake is being unsure what method will be invoked when instantiating a class polymorphically. Remember that the method to be invoked is the method of the created instance.

Also remember that method overriding is not method overloading.

It’s impossible to override a method if the parameters are different. It is possible to change the return type of the overridden method if the return type is a subclass of the superclass method.

สิ่งที่ต้องจำเกี่ยวกับความหลากหลาย

  • อินสแตนซ์ที่สร้างขึ้นจะกำหนดวิธีการที่จะเรียกใช้เมื่อใช้ความหลากหลาย
  • @Overrideคำอธิบายประกอบ obligates โปรแกรมเมอร์ที่จะใช้วิธีการแทนที่; ถ้าไม่เป็นเช่นนั้นจะมีข้อผิดพลาดของคอมไพเลอร์
  • Polymorphism สามารถใช้ได้กับคลาสปกติคลาสนามธรรมและอินเทอร์เฟซ
  • รูปแบบการออกแบบส่วนใหญ่ขึ้นอยู่กับความหลากหลายของรูปแบบบางอย่าง
  • วิธีเดียวที่จะใช้วิธีการเฉพาะในคลาสย่อย polymorphic ของคุณคือการใช้การหล่อ
  • เป็นไปได้ที่จะออกแบบโครงสร้างที่มีประสิทธิภาพในโค้ดของคุณโดยใช้ความหลากหลาย
  • ทำการทดสอบของคุณ ทำเช่นนี้คุณจะสามารถควบคุมแนวคิดอันทรงพลังนี้ได้!

คีย์คำตอบ

คำตอบนี้ผู้ท้าชิง Java เป็นD ผลลัพธ์จะเป็น:

 I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down 

เรื่องนี้ "Polymorphism and inheritance in Java" เผยแพร่ครั้งแรกโดย JavaWorld