การสืบทอด vs คุณสมบัติเพิ่มเติมที่มีค่า Null


12

สำหรับคลาสที่มีฟิลด์เป็นตัวเลือกจะดีกว่าถ้าใช้การสืบทอดหรือคุณสมบัติที่เป็นโมฆะ? ลองพิจารณาตัวอย่างนี้:

class Book {
    private String name;
}
class BookWithColor extends Book {
    private String color;
}

หรือ

class Book {
    private String name;
    private String color; 
    //when this is null then it is "Book" otherwise "BookWithColor"
}

หรือ

class Book {
    private String name;
    private Optional<String> color;
    //when isPresent() is false it is "Book" otherwise "BookWithColor"
}

รหัสขึ้นอยู่กับ 3 ตัวเลือกเหล่านี้จะเป็น:

if (book instanceof BookWithColor) { ((BookWithColor)book).getColor(); }

หรือ

if (book.getColor() != null) { book.getColor(); }

หรือ

if (book.getColor().isPresent()) { book.getColor(); }

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


1
ฉันเกรงว่าทางออกจะเป็นไปตามนิยามของกฎเกณฑ์ทางธุรกิจของคุณ ฉันหมายความว่าถ้าหนังสือของคุณทุกเล่มมีสี แต่สีเป็นตัวเลือกการสืบทอดนั้นมากเกินไปและไม่จำเป็นจริง ๆ เพราะคุณต้องการให้หนังสือทุกเล่มของคุณรู้ว่ามีสีอยู่จริง ในทางกลับกันถ้าคุณสามารถมีหนังสือทั้งคู่ที่ไม่รู้อะไรเกี่ยวกับสีเลยทีเดียวและหนังสือที่มีสีการสร้างคลาสเฉพาะไม่เลว ถึงอย่างนั้นฉันก็อาจจะแต่งเพลงมากกว่ามรดก
Andy

เพื่อให้มีความเฉพาะเจาะจงมากขึ้น - มีหนังสือ 2 ประเภท - เล่มหนึ่งที่มีสีและไม่มีหนังสือ ดังนั้นหนังสือทุกเล่มอาจมีสีไม่ได้
Bojan VukasovicTest

1
หากมีกรณีที่หนังสือไม่ได้มีสีเลยในกรณีของคุณมรดกเป็นวิธีที่ง่ายที่สุดวิธีการขยายคุณสมบัติของหนังสือที่มีสีสัน ในกรณีนี้ดูเหมือนว่าคุณจะไม่ได้รับความเสียหายใด ๆ โดยการรับคลาสฐานหนังสือและเพิ่มคุณสมบัติสีลงไป คุณสามารถอ่านเกี่ยวกับหลักการทดแทน liskov เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับกรณีและปัญหาเมื่อขยายชั้นเรียนและเมื่อไม่ได้รับอนุญาต
Andy

4
คุณสามารถระบุพฤติกรรมที่คุณต้องการจากหนังสือที่มีการระบายสีได้หรือไม่? คุณพบพฤติกรรมทั่วไปของหนังสือที่มีและไม่มีสีหรือไม่ซึ่งหนังสือที่มีสีควรมีพฤติกรรมแตกต่างกันเล็กน้อย ถ้าเป็นเช่นนั้นนี่เป็นกรณีที่ดีสำหรับ OOP ที่มีประเภทแตกต่างกันและความคิดก็คือการย้ายพฤติกรรมไปยังชั้นเรียนแทนการซักถามการมีอยู่ของทรัพย์สินและค่าภายนอก
Erik Eidt

1
หากทราบสีของหนังสือที่เป็นไปได้ล่วงหน้าฟิลด์ Enum สำหรับ BookColor พร้อมตัวเลือกสำหรับ BookColor.NoColor เป็นอย่างไร
เกรแฮม

คำตอบ:


7

มันขึ้นอยู่กับสถานการณ์ ตัวอย่างที่เฉพาะเจาะจงไม่สมจริงเนื่องจากคุณไม่มีคลาสย่อยที่เรียกว่าBookWithColorในโปรแกรมจริง

แต่โดยทั่วไปคุณสมบัติที่เหมาะสมสำหรับคลาสย่อยบางอย่างควรมีอยู่ในคลาสย่อยเหล่านั้นเท่านั้น

ตัวอย่างเช่นถ้าBookมีPhysicalBookและDigialBookเป็นลูกหลานแล้วPhysicalBookอาจจะมีweightทรัพย์สินและคุณสมบัติ แต่จะไม่ได้และในทางกลับกัน จะไม่มีคุณสมบัติเนื่องจากคลาสควรมีคุณสมบัติที่ใช้ร่วมกันโดยผู้สืบทอดทั้งหมดDigitalBooksizeInKbDigitalBookweightBook

ตัวอย่างที่ดีกว่าคือการดูคลาสจากซอฟต์แวร์จริง องค์ประกอบมีสนามJSlider majorTickSpacingเนื่องจากแถบเลื่อนมี "เห็บ" ฟิลด์นี้จึงเหมาะสมสำหรับJSliderและผู้สืบทอดเท่านั้น มันจะสับสนมากถ้าส่วนประกอบอื่น ๆ ของพี่น้องเช่นJButtonมีmajorTickSpacingสนาม


หรือคุณอาจ bastract น้ำหนักเพื่อให้สามารถสะท้อนทั้งสอง แต่นั่นอาจมากเกินไป
Walfrat

3
@ Walrat: มันจะเป็นความคิดที่ดีจริงๆที่จะมีสาขาเดียวกันแสดงสิ่งที่แตกต่างอย่างสิ้นเชิงในคลาสย่อยที่แตกต่างกัน
JacquesB

จะเป็นอย่างไรถ้าหนังสือมีทั้งฉบับจริงและดิจิทัล? คุณจะต้องสร้างคลาสที่แตกต่างกันสำหรับการเรียงสับเปลี่ยนทุกครั้งที่มีหรือไม่มีแต่ละฟิลด์ตัวเลือก ฉันคิดว่าทางออกที่ดีที่สุดจะยอมให้มีการข้ามบางฟิลด์หรือไม่ขึ้นอยู่กับหนังสือ คุณอ้างถึงสถานการณ์ที่แตกต่างอย่างสิ้นเชิงโดยที่ทั้งสองคลาสนั้นจะทำงานแตกต่างกันไปตามหน้าที่ นั่นไม่ใช่สิ่งที่คำถามนี้มีความหมาย พวกเขาต้องการสร้างแบบจำลองโครงสร้างข้อมูล
4castle

@ 4castle: คุณจะต้องแยกชั้นเรียนต่อฉบับเนื่องจากแต่ละรุ่นอาจมีราคาแตกต่างกัน คุณไม่สามารถแก้ปัญหานี้ด้วยฟิลด์ตัวเลือก แต่เราไม่ทราบจากตัวอย่างหาก op ต้องการพฤติกรรมที่แตกต่างกันสำหรับหนังสือที่มีสีหรือ "หนังสือที่ไม่มีสี" ดังนั้นจึงเป็นไปไม่ได้ที่จะรู้ว่าแบบจำลองที่ถูกต้องในกรณีนี้คืออะไร
JacquesB

3
เห็นด้วยกับ @JacquesB คุณไม่สามารถให้ทางออกที่ถูกต้องในระดับสากลสำหรับ "การสร้างแบบจำลองหนังสือ" เพราะมันขึ้นอยู่กับปัญหาที่คุณพยายามแก้ไขและธุรกิจของคุณคืออะไร ฉันคิดว่ามันค่อนข้างปลอดภัยที่จะบอกว่าการกำหนดลำดับชั้นของคลาสที่คุณละเมิด liskov นั้นผิดหมวดหมู่ (tm) แม้ว่า (ใช่ใช่กรณีของคุณน่าจะพิเศษมากและรับประกัน (แม้ว่าฉันจะสงสัย))
sara

5

จุดสำคัญที่ไม่ได้กล่าวถึง: ในภาษาส่วนใหญ่อินสแตนซ์ของคลาสไม่สามารถเปลี่ยนคลาสที่เป็นอินสแตนซ์ของ ดังนั้นหากคุณมีหนังสือที่ไม่มีสีและคุณต้องการที่จะเพิ่มสีคุณต้องสร้างวัตถุใหม่ถ้าคุณใช้คลาสที่แตกต่างกัน แล้วคุณอาจต้องแทนที่การอ้างอิงทั้งหมดไปยังวัตถุเก่าด้วยการอ้างอิงถึงวัตถุใหม่

หาก "หนังสือที่ไม่มีสี" และ "หนังสือที่มีสี" เป็นอินสแตนซ์ของคลาสเดียวกันการเพิ่มสีหรือการลบสีจะมีปัญหาน้อยกว่ามาก (หากส่วนต่อประสานผู้ใช้ของคุณแสดงรายการ "หนังสือที่มีสี" และ "หนังสือที่ไม่มีสี" ดังนั้นส่วนติดต่อผู้ใช้นั้นจะต้องเปลี่ยนอย่างชัดเจน แต่ฉันคาดว่านั่นเป็นสิ่งที่คุณต้องจัดการอยู่ดีคล้ายกับรายการ "สีแดง" หนังสือ "และ" รายการหนังสือสีเขียว ")


นี่เป็นจุดสำคัญจริงๆ! มันกำหนดว่าควรพิจารณาชุดของคุณสมบัติที่เป็นตัวเลือกแทนแอตทริบิวต์คลาส
Chromanoid

1

คิดว่า JavaBean (เหมือนของคุณBook) เป็นบันทึกในฐานข้อมูล คอลัมน์ตัวเลือกคือnullเมื่อไม่มีค่า แต่เป็นกฎหมายที่สมบูรณ์ ดังนั้นตัวเลือกที่สองของคุณ:

class Book {
    private String name;
    private String color; // null when not applicable
}

มีความสมเหตุสมผลที่สุด 1

ระวังด้วยวิธีการใช้Optionalคลาส ตัวอย่างเช่นไม่ใช่Serializableซึ่งโดยปกติจะเป็นลักษณะของ JavaBean นี่คือเคล็ดลับบางส่วนจากStephen Colebourne :

  1. ไม่ได้ประกาศตัวแปรเช่นใด ๆ Optionalของประเภท
  2. ใช้nullเพื่อระบุข้อมูลเพิ่มเติมภายในขอบเขตส่วนตัวของคลาส
  3. ใช้Optionalสำหรับ getters ที่เข้าถึงฟิลด์ตัวเลือก
  4. ห้ามใช้Optionalใน setters หรือ constructors
  5. ใช้Optionalเป็นชนิดส่งคืนสินค้าสำหรับวิธีการตรรกะทางธุรกิจอื่น ๆ ที่มีผลลัพธ์เป็นตัวเลือก

ดังนั้นภายในชั้นเรียนของคุณคุณควรจะใช้nullเพื่อเป็นตัวแทนว่าสนามไม่อยู่ แต่เมื่อcolor ใบBook (เป็นประเภทผลตอบแทน) Optionalมันควรจะห่อกับ

return Optional.ofNullable(color); // inside the class

book.getColor().orElse("No color"); // outside the class

นี้ให้การออกแบบที่ชัดเจนและอ่านง่ายขึ้น


1ถ้าคุณตั้งใจสำหรับBookWithColorการ encapsulate เป็น "ระดับ" ทั้งหนังสือที่มีความเชี่ยวชาญความสามารถมากกว่าหนังสือเล่มอื่น ๆแล้วมันจะทำให้รู้สึกถึงการใช้งานมรดก


1
ใครพูดอะไรเกี่ยวกับฐานข้อมูล หากรูปแบบ OO ทั้งหมดต้องสอดคล้องกับกระบวนทัศน์ฐานข้อมูลเชิงสัมพันธ์เราจะต้องเปลี่ยนรูปแบบ OO จำนวนมาก
alexanderbird

ฉันจะยืนยันการเพิ่มคุณสมบัติที่ไม่ได้ใช้ "ในกรณี" เป็นตัวเลือกที่ชัดเจนน้อยที่สุด ดังที่คนอื่น ๆ กล่าวขึ้นอยู่กับกรณีการใช้งาน แต่สถานการณ์ที่เป็นที่ต้องการมากที่สุดคือไม่ต้องพกพาคุณสมบัติที่แอพพลิเคชั่นภายนอกจำเป็นต้องรู้ว่าทรัพย์สินที่มีอยู่นั้นถูกใช้จริงหรือไม่
Volker Kueffel

@Volker เราเห็นด้วยที่จะไม่เห็นด้วยเพราะฉันสามารถอ้างอิงหลายคนที่เล่นมือในการเพิ่มOptionalชั้นเรียนเพื่อ Java เพื่อวัตถุประสงค์ที่แน่นอนนี้ อาจไม่ใช่ OO แต่แน่นอนว่าเป็นความเห็นที่ถูกต้อง
4castle

@Alexanderbird ฉันได้แก้ปัญหา JavaBean โดยเฉพาะซึ่งควรจะเป็นอันดับ หากฉันไม่อยู่ที่หัวข้อด้วยการนำ JavaBeans มาแสดงว่านั่นเป็นความผิดพลาดของฉัน
4castle

@ Alexanderbird ฉันไม่ได้คาดหวังว่ารูปแบบทั้งหมดจะตรงกับฐานข้อมูลเชิงสัมพันธ์ แต่ฉันคาดว่า Java Bean จะไป
4castle

0

คุณควรใช้อินเตอร์เฟส (ไม่ใช่การสืบทอด) หรือกลไกคุณสมบัติบางอย่าง (อาจเป็นบางสิ่งที่ทำงานได้เหมือนกรอบคำอธิบายทรัพยากร)

ดังนั้นทั้ง

interface Colored {
  Color getColor();
}
class ColoredBook extends Book implements Colored {
  ...
}

หรือ

class PropertiesHolder {
  <T> extends Property<?>> Optional<T> getProperty( Class<T> propertyClass ) { ... }
  <V, T extends Property<V>> Optional<V> getPropertyValue( Class<T> propertyClass ) { ... }
}
interface Property<T> {
  String getName();
  T getValue();
}
class ColorProperty implements Property<Color> {
  public Color getValue() { ... }
  public String getName() { return "color"; }
}
class Book extends PropertiesHolder {
}

การชี้แจง (แก้ไข):

เพียงเพิ่มฟิลด์ตัวเลือกในคลาสเอนทิตี

โดยเฉพาะอย่างยิ่งกับ Wrapper ที่เป็นตัวเลือก (แก้ไข: ดูคำตอบของ4castle ) ฉันคิดว่านี่ (การเพิ่มเขตข้อมูลในเอนทิตีดั้งเดิม) เป็นวิธีที่ทำงานได้เพื่อเพิ่มคุณสมบัติใหม่ในขนาดเล็ก ปัญหาที่ใหญ่ที่สุดด้วยวิธีนี้คือมันอาจทำงานกับการมีเพศสัมพันธ์ต่ำ

ลองนึกภาพคลาสหนังสือของคุณถูกกำหนดไว้ในโครงการเฉพาะสำหรับแบบจำลองโดเมนของคุณ ตอนนี้คุณเพิ่มโครงการอื่นที่ใช้โมเดลโดเมนเพื่อทำงานพิเศษ ภารกิจนี้ต้องการคุณสมบัติเพิ่มเติมในคลาสหนังสือ ไม่ว่าคุณจะได้รับมรดก (ดูด้านล่าง) หรือคุณต้องเปลี่ยนรูปแบบโดเมนทั่วไปเพื่อให้งานใหม่เป็นไปได้ ในกรณีหลังคุณอาจจบลงด้วยโครงการมากมายที่ขึ้นอยู่กับคุณสมบัติของตัวเองที่เพิ่มเข้ากับชั้นหนังสือในขณะที่ชั้นเรียนของตัวเองขึ้นอยู่กับโครงการเหล่านี้เพราะคุณไม่สามารถเข้าใจชั้นหนังสือได้โดยไม่ต้อง โครงการเพิ่มเติม

เหตุใดการสืบทอดจึงเป็นปัญหาเมื่อหาวิธีให้คุณสมบัติเพิ่มเติม

เมื่อฉันเห็นชั้นเรียนของคุณ "หนังสือ" ฉันคิดถึงวัตถุในโดเมนที่มักจะจบลงด้วยการมีฟิลด์และตัวเลือกย่อยมากมาย แค่คิดว่าคุณต้องการเพิ่มอสังหาริมทรัพย์สำหรับหนังสือที่มีซีดี ขณะนี้มีหนังสือสี่ชนิด: หนังสือหนังสือที่มีสีหนังสือที่มีซีดีหนังสือที่มีสีและซีดี คุณไม่สามารถอธิบายสถานการณ์นี้ด้วยการสืบทอดใน Java

ด้วยส่วนต่อประสานที่คุณหลีกเลี่ยงปัญหานี้ คุณสามารถเขียนคุณสมบัติของคลาสหนังสือเฉพาะกับอินเทอร์เฟซได้อย่างง่ายดาย การมอบหมายและการจัดองค์ประกอบจะทำให้ง่ายต่อการได้รับชั้นที่คุณต้องการ ด้วยการสืบทอดคุณมักจะจบลงด้วยคุณสมบัติทางเลือกบางอย่างในคลาสที่มีเพียงเพราะคลาสพี่น้องต้องการพวกเขา

อ่านเพิ่มเติมว่าทำไมการสืบทอดมักเป็นแนวคิดที่มีปัญหา:

เหตุใดโดยทั่วไปการรับมรดกจึงมองว่าเป็นสิ่งเลวร้ายโดยผู้เสนอ OOP

JavaWorld: ทำไมการขยายความชั่วร้าย

ปัญหาเกี่ยวกับการนำชุดอินเตอร์เฟสไปใช้เพื่อสร้างชุดของคุณสมบัติ

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

แก้ไข: อีกด้านที่สำคัญคือการกล่าวถึงโดยgnasher729 คุณมักจะต้องการเพิ่มคุณสมบัติทางเลือกให้กับวัตถุที่มีอยู่แบบไดนามิก ด้วยการสืบทอดหรืออินเทอร์เฟซคุณจะต้องสร้างวัตถุทั้งหมดด้วยคลาสอื่นซึ่งอยู่ห่างจากทางเลือก

เมื่อคุณคาดว่าส่วนขยายของโมเดลวัตถุของคุณจะดีขึ้นโดยการสร้างแบบจำลองความเป็นไปได้ของส่วนขยายแบบไดนามิกอย่างชัดเจน ฉันเสนอสิ่งต่าง ๆ ข้างต้นโดยที่ "ส่วนขยาย" แต่ละรายการ (ในกรณีของคุณสมบัตินี้) มีคลาสของตัวเอง ชั้นทำงานเป็น namespace และตัวระบุสำหรับส่วนขยาย เมื่อคุณตั้งค่าอนุสัญญาการตั้งชื่อแพ็คเกจด้วยวิธีนี้จะอนุญาตให้มีการขยายจำนวนอนันต์โดยไม่ทำให้เกิดมลภาวะในเนมสเปซสำหรับวิธีการในคลาสเอนทิตีดั้งเดิม

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


และยังไม่ได้ตอบคำถาม "วิธีตัดสินใจระหว่างตัวเลือกเหล่านี้" เป็นเพียงอีกตัวเลือกหนึ่ง ทำไมสิ่งนี้ถึงดีกว่าตัวเลือกทั้งหมดที่ OP แสดงเสมอ
alexanderbird

คุณพูดถูกฉันจะปรับปรุงคำตอบ
chromanoid

@ 4castle ในอินเทอร์เฟซ Java ไม่ใช่การสืบทอด! การใช้อินเทอร์เฟซเป็นวิธีปฏิบัติตามสัญญาในขณะที่การสืบทอดคลาสนั้นเป็นการขยายลักษณะการทำงาน คุณสามารถใช้หลายอินเตอร์เฟสในขณะที่คุณไม่สามารถขยายหลายคลาสในหนึ่งคลาส ในตัวอย่างของฉันคุณขยายด้วยอินเตอร์เฟสในขณะที่คุณใช้การสืบทอดเพื่อสืบทอดการทำงานของเอนทิตีดั้งเดิม
chromanoid

มีเหตุผล. ในตัวอย่างของคุณPropertiesHolderอาจเป็นคลาสนามธรรม แต่สิ่งที่คุณจะแนะนำให้มีการดำเนินการสำหรับgetPropertyหรือgetPropertyValue? ข้อมูลจะต้องถูกเก็บไว้ในฟิลด์อินสแตนซ์บางประเภท หรือคุณจะใช้ a Collection<Property>เพื่อจัดเก็บคุณสมบัติแทนการใช้ฟิลด์?
4castle

1
ฉันBookขยายเวลาออกไปPropertiesHolderเพื่อเหตุผลที่ชัดเจน โดยPropertiesHolderปกติควรเป็นสมาชิกในคลาสทั้งหมดที่ต้องใช้ฟังก์ชันนี้ การดำเนินการจะถือMap<Class<? extends Property<?>>,Property<?>>หรืออะไรเช่นนี้ นี่เป็นส่วนที่น่าเกลียดของการนำไปใช้ แต่มีกรอบงานที่ดีมากที่สามารถใช้งานได้กับ JAXB และ JPA
Chromanoid

-1

ถ้าBookคลาสสามารถดำเนินการได้โดยไม่มีคุณสมบัติสีดังนี้

class E_Book extends Book{
   private String type;
}

การรับมรดกนั้นสมเหตุสมผล


ฉันไม่แน่ใจว่าฉันเข้าใจตัวอย่างของคุณหรือไม่ ถ้าcolorเป็นแบบส่วนตัวแล้วคลาสที่ได้รับใด ๆ ก็ไม่ควรที่จะกังวลcolorอยู่ดี เพียงแค่เพิ่มการก่อสร้างบางอย่างเพื่อBookที่จะไม่สนใจทั้งหมดและมันจะเริ่มต้นcolor null
4castle
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.