เมื่อใดที่ฉันควรขยายคลาส Java Swing


35

ความเข้าใจในปัจจุบันของฉันเกี่ยวกับการใช้งานมรดกคือควรขยายชั้นเรียนเฉพาะเมื่อมีความสัมพันธ์แบบ IS-A หากคลาสพาเรนต์สามารถมีประเภทลูกที่เฉพาะเจาะจงเพิ่มเติมด้วยฟังก์ชันการทำงานที่แตกต่างกัน แต่จะแบ่งปันองค์ประกอบทั่วไปที่มีนามธรรมในผู้ปกครอง

ฉันกำลังถามความเข้าใจนั้นเพราะสิ่งที่อาจารย์ Java ของฉันแนะนำให้เราทำ เขาแนะนำว่าสำหรับJSwingแอปพลิเคชันที่เรากำลังสร้างในชั้นเรียน

หนึ่งควรขยายทุกJSwingชั้นเรียน ( JFrame, JButton, JTextBoxฯลฯ ) ในชั้นเรียนที่กำหนดเองแยกต่างหากและระบุ GUI การปรับแต่งที่เกี่ยวข้องในพวกเขา (เช่นขนาดส่วนประกอบฉลากส่วนประกอบ ฯลฯ )

จนถึงตอนนี้ดีมาก แต่เขาก็ยังแนะนำต่อไปว่า JButton ทุกคนควรมีคลาสแบบขยายที่กำหนดเองของตนเองแม้ว่าปัจจัยที่แตกต่างเท่านั้นคือฉลากของพวกเขา

สำหรับเช่นหาก GUI มีสองปุ่มโอเคและยกเลิก เขาแนะนำให้ขยายเวลาดังต่อไปนี้:

class OkayButton extends JButton{
    MainUI mui;
    public OkayButton(MainUI mui) {
        setSize(80,60);
        setText("Okay");
        this.mui = mui;
        mui.add(this);        
    }
}

class CancelButton extends JButton{
    MainUI mui;
    public CancelButton(MainUI mui) {
        setSize(80,60);
        setText("Cancel");
        this.mui = mui;
        mui.add(this);        
    }
}

อย่างที่คุณเห็นความแตกต่างเพียงอย่างเดียวคือในsetTextฟังก์ชั่น

ดังนั้นการปฏิบัติมาตรฐานนี้คืออะไร?

Btw หลักสูตรที่กล่าวถึงนี้เรียกว่าBest Programming Practices ใน Java

[ตอบจากศาสตราจารย์]

ดังนั้นฉันจึงพูดถึงปัญหากับอาจารย์และยกประเด็นทั้งหมดที่ระบุไว้ในคำตอบ

เหตุผลของเขาคือ subclassing ให้โค้ดที่นำมาใช้ซ้ำได้ในขณะที่ปฏิบัติตามมาตรฐานการออกแบบ GUI ตัวอย่างเช่นหากผู้พัฒนาได้ใช้กำหนดเองOkayและCancelปุ่มในหนึ่งหน้าต่างมันจะง่ายกว่าที่จะวางปุ่มเดียวกันใน Windows อื่น ๆ เช่นกัน

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

หลังจากนั้นนักพัฒนาซอฟต์แวร์รายใดสามารถโทรsetTextไปที่Okayปุ่มและเปลี่ยนโดยไม่ได้ตั้งใจ คลาสย่อยจะกลายเป็นความรำคาญในกรณีนั้น


3
ทำไมต้องขยายJButtonและเรียกวิธีการสาธารณะในนวกรรมิกเมื่อคุณสามารถสร้างJButtonและเรียกวิธีการสาธารณะแบบเดียวกันเหล่านั้นนอกห้องเรียน?

17
อยู่ห่างจากอาจารย์คนนั้นถ้าคุณทำได้ นี้ค่อนข้างห่างไกลจากการปฏิบัติที่ดีที่สุด การทำสำเนารหัสเช่นคุณแสดงเป็นกลิ่นที่ไม่ดีมาก
njzk2

7
แค่ถามเขาว่าทำไมอย่าลังเลที่จะสื่อสารแรงจูงใจของเขาที่นี่
Alex

9
ฉันต้องการ downvote เพราะมันเป็นรหัสที่แย่มาก แต่ฉันก็อยากจะ upvote เพราะมันยอดเยี่ยมมากที่คุณตั้งคำถามแทนการยอมรับสิ่งที่อาจารย์ผู้ชั่วพูดแทน
Nic Hartley

1
@Alex ฉันได้อัปเดตคำถามด้วยเหตุผลของศาสตราจารย์แล้ว
Paras

คำตอบ:


21

สิ่งนี้ละเมิดหลักการการทดแทน Liskovเนื่องจากOkayButtonไม่สามารถทดแทนได้ในทุกที่ที่Buttonคาดว่าจะ ตัวอย่างเช่นคุณสามารถเปลี่ยนป้ายของปุ่มใดก็ได้ตามต้องการ แต่การทำเช่นนั้นกับผู้OkayButtonละเมิดละเมิดค่าคงที่ภายใน

นี่คือการสืบทอดแบบคลาสสิกสำหรับการใช้รหัสซ้ำ ใช้วิธีการช่วยเหลือแทน

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


สิ่งนี้ไม่ได้ละเมิด LSP เว้นแต่ว่าจะOkayButtonมีค่าคงที่ที่คุณคิด OkayButtonอาจเรียกร้องไม่ให้มีค่าคงที่พิเศษใด ๆ ก็แค่อาจพิจารณาข้อความที่เป็นค่าเริ่มต้นและความตั้งใจที่จะสนับสนุนอย่างเต็มที่ปรับเปลี่ยนข้อความ ยังไม่ใช่ความคิดที่ดี แต่ไม่ใช่ด้วยเหตุผลนั้น
hvd

มันไม่ได้ละเมิดเพราะคุณทำได้ เช่นถ้าวิธีการที่คาดว่าปุ่มคุณสามารถให้ตัวอย่างของactivateButton(Button btn) OkayButtonหลักการไม่ได้หมายความว่ามันจะต้องมีว่าการทำงานเดียวกันเช่นนี้จะทำให้มรดกสวยมากไร้ประโยชน์
Sebb

1
@Sebb แต่คุณไม่สามารถใช้กับฟังก์ชั่นได้setText(button, "x")เพราะมันละเมิดค่าคงที่ เป็นเรื่องจริงที่ OkayButton ตัวนี้อาจไม่มีค่าคงที่ที่น่าสนใจดังนั้นฉันเดาว่าฉันเลือกตัวอย่างที่ไม่ดี แต่มันอาจ ตัวอย่างเช่นสิ่งที่ถ้าจัดการคลิกกล่าวว่าif (myText == "OK") ProcessOK(); else ProcessCancel();? จากนั้นข้อความเป็นส่วนหนึ่งของค่าคงที่
usr

@usr ฉันไม่ได้คิดถึงข้อความที่เป็นส่วนหนึ่งของค่าคงที่ คุณพูดถูกมันถูกละเมิด
Sebb

50

มันแย่มากในทุกวิถีทาง อย่างมากให้ใช้ฟังก์ชั่นจากโรงงานเพื่อผลิต JButtons คุณควรสืบทอดจากพวกเขาหากคุณมีความต้องการส่วนขยายที่ร้ายแรง


20
+1 สำหรับการอ้างอิงเพิ่มเติมให้ดูที่ลำดับชั้นสวิงชั้นAbstractButton แล้วมองลำดับชั้นของJToggleButton การสืบทอดใช้เพื่อกำหนดแนวความคิดต่าง ๆของปุ่ม แต่ไม่ได้ใช้เพื่อแยกแนวคิดทางธุรกิจที่แตกต่าง ( ใช่, ไม่, ดำเนินการต่อ, ยกเลิก ... )
Laiv

2
@Laiv: แม้จะมีประเภทที่แตกต่างสำหรับเช่นJButton, JCheckBox, JRadioButtonเป็นเพียงสัมปทานให้กับนักพัฒนาเป็นคุ้นเคยกับชุดเครื่องมืออื่น ๆ เช่น AWT ธรรมดาที่ประเภทดังกล่าวมีมาตรฐาน โดยหลักการแล้วพวกเขาทั้งหมดสามารถจัดการได้ด้วยคลาสปุ่มเดียว เป็นการรวมกันของแบบจำลองและตัวแทน UI ซึ่งสร้างความแตกต่างที่แท้จริง
Holger

ใช่พวกเขาสามารถจัดการได้ด้วยปุ่มเดียว อย่างไรก็ตามลำดับชั้นที่เกิดขึ้นจริงสามารถเปิดเผยเป็นแนวทางปฏิบัติที่ดี ในการสร้างปุ่มใหม่หรือเพียงแค่มอบหมายกิจกรรมและควบคุม behaivor ให้กับองค์ประกอบอื่นนั้นเป็นเรื่องของการตั้งค่า ถ้าเป็นฉันฉันจะขยายส่วนประกอบของ Swing เพื่อสร้างแบบจำลองปุ่ม owm ของฉันและใส่ใน behaivors, action, ... เพื่อให้ง่ายต่อการนำไปใช้ในโครงการและทำให้เป็นเรื่องง่ายสำหรับรุ่นน้อง ในเว็บ envs ฉันชอบองค์ประกอบของเว็บมากกว่าการจัดการเหตุการณ์มากเกินไป อย่างไรก็ตาม. คุณพูดถูกเราสามารถแยก UI จาก Behaivors ได้
Laiv

12

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

(อย่างไรก็ตามGoogle scholar ไม่เคยได้ยินบทความที่คุณอ้างถึง - คุณมีข้อมูลอ้างอิงที่แม่นยำกว่านี้ไหม)


"วิธีมาตรฐาน" คืออะไร? ฉันยอมรับว่าการเขียนคลาสย่อยสำหรับทุกองค์ประกอบน่าจะเป็นความคิดที่ไม่ดี แต่บทเรียนการสวิงอย่างเป็นทางการยังคงสนับสนุนการฝึกฝนนี้ ฉันคิดว่าอาจารย์ทำตามสไตล์ที่แสดงในเอกสารอย่างเป็นทางการของกรอบงาน
จาก

@COMEFROM ฉันจะยอมรับว่าไม่ได้อ่านบทช่วยสอนการสวิงทั้งหมดดังนั้นบางทีฉันอาจพลาดที่จะใช้สไตล์นี้ แต่หน้าเว็บที่ฉันดูมีแนวโน้มที่จะใช้คลาสฐานและไม่ขับคลาสย่อยเช่นdocs.oracle .com / javase / tutorial / uiswing / components / button.html
Jules

@Jules ขออภัยเกี่ยวกับความสับสนที่ฉันหมายถึงหลักสูตร / กระดาษอาจารย์สอนที่เรียกว่าปฏิบัติที่ดีที่สุดในชวา
Paras

1
จริงโดยทั่วไป แต่มีข้อยกเว้นที่สำคัญอื่น ๆ JPanel- มันมีประโยชน์มากกว่า subclass ที่มากกว่าJFrameเนื่องจาก panel เป็นเพียงคอนเทนเนอร์นามธรรม
2559

10

IMHO แนวทางปฏิบัติด้านการเขียนโปรแกรมที่ดีที่สุดใน Java ถูกกำหนดโดยหนังสือของ Joshua Bloch "Java ที่มีประสิทธิภาพ" เป็นเรื่องที่ดีมากที่ครูของคุณกำลังให้แบบฝึกหัด OOP และสิ่งสำคัญคือการเรียนรู้การอ่านและเขียนรูปแบบการเขียนโปรแกรมของคนอื่น แต่นอกเหนือจากหนังสือของ Josh Bloch ความคิดเห็นแตกต่างกันอย่างกว้างขวางเกี่ยวกับแนวปฏิบัติที่ดีที่สุด

หากคุณกำลังจะขยายชั้นเรียนนี้คุณอาจใช้ประโยชน์จากการสืบทอด สร้างคลาส MyButton เพื่อจัดการรหัสทั่วไปและ sub-class สำหรับชิ้นส่วนตัวแปร:

class MyButton extends JButton{
    protected final MainUI mui;
    public MyButton(MainUI mui, String text) {
        setSize(80,60);
        setText(text);
        this.mui = mui;
        mui.add(this);        
    }
}

class OkayButton extends MyButton{
    public OkayButton(MainUI mui) {
        super(mui, "Okay");
    }
}

class CancelButton extends MyButton{
    public CancelButton(MainUI mui) {
        super(mui, "Cancel");
    }
}

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

public void showPopUp(String text, JButton ok, JButton cancel)

ประเภทที่คุณเพิ่งสร้างขึ้นนั้นไม่ได้ทำอะไรดีเลย แต่:

public void showPopUp(String text, OkButton ok, CancelButton cancel)

ตอนนี้คุณได้สร้างสิ่งที่มีประโยชน์

  1. คอมไพเลอร์ตรวจสอบว่า showPopUp ใช้ OkButton และ CancelButton มีคนอ่านรหัสรู้วิธีใช้ฟังก์ชันนี้เนื่องจากเอกสารประเภทนี้จะทำให้เกิดข้อผิดพลาดในการคอมไพล์หากล้าสมัย นี่คือผลประโยชน์ที่สำคัญ การศึกษาเชิงประจักษ์ 1 หรือ 2 เกี่ยวกับประโยชน์ของความปลอดภัยของประเภทพบว่าการเข้าใจรหัสของมนุษย์เป็นประโยชน์เชิงปริมาณเพียงอย่างเดียว

  2. นอกจากนี้ยังป้องกันข้อผิดพลาดที่คุณย้อนกลับลำดับของปุ่มที่คุณส่งผ่านไปยังฟังก์ชั่น ข้อผิดพลาดเหล่านี้ยากที่จะสังเกตเห็น แต่ก็ค่อนข้างหายากดังนั้นนี่คือข้อดีเล็กน้อย สิ่งนี้เคยขายความปลอดภัยประเภท แต่ไม่ได้รับการพิสูจน์เชิงประจักษ์ว่าเป็นประโยชน์อย่างยิ่ง

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

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

ด้วยเหตุนี้คุณอาจต้องการเขียนโค้ด Clojure เพื่อดูว่าการเขียนฟังก์ชั่นของคุณนั้นยอดเยี่ยมแค่ไหน คุณสามารถเขียนโค้ดได้อย่างรวดเร็วโดยคิดเกี่ยวกับความซับซ้อนแบบอะซิมโทติคให้มากที่สุดและการสันนิษฐานของ Clojure เรื่องการไม่เปลี่ยนรูปจะช่วยป้องกันข้อผิดพลาดได้มากเท่ากับชนิดใน Java

ฉันยังคงเป็นแฟนของประเภทคงที่สำหรับโครงการขนาดใหญ่และตอนนี้ใช้ยูทิลิตีการทำงานบางอย่างที่อนุญาตให้ผมเขียนฟังก์ชั่นครั้งแรกและชื่อประเภทของฉันในภายหลังใน Java แค่ความคิด

ป.ล. ฉันทำตัวmuiชี้ครั้งสุดท้าย - ไม่จำเป็นต้องเปลี่ยนแปลง


6
จาก 3 คะแนนของคุณ 1 และ 3 สามารถจัดการได้ด้วย JButtons ทั่วไปและรูปแบบการตั้งชื่อพารามิเตอร์ที่เหมาะสม จุดที่ 2 เป็นข้อเสียจริง ๆ ที่ทำให้โค้ดของคุณแน่นมากขึ้น: ถ้าคุณต้องการให้ปุ่มกลับด้าน ถ้าหากแทนที่จะเป็นปุ่ม OK / Cancel คุณต้องการใช้ปุ่ม Yes / No? คุณจะสร้างเมธอด showPopup ใหม่ทั้งหมดที่ใช้ YesButton และ Nobutton หรือไม่? หากคุณเพิ่งใช้ JButtons เริ่มต้นคุณไม่จำเป็นต้องสร้างประเภทของคุณเป็นอันดับแรกเพราะมีอยู่แล้ว
Nzall

การขยายสิ่งที่ @NateKerkhofs กล่าวว่า IDE สมัยใหม่จะแสดงชื่ออาร์กิวเมนต์ที่เป็นทางการในขณะที่คุณพิมพ์ในนิพจน์จริง
โซโลมอนช้า

8

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

ที่ถูกกล่าวว่าในโลกแห่งความจริงที่เราโปรดปรานองค์ประกอบมรดก

กฎของ "หนึ่งควรขยายชั้นถ้ามีความสัมพันธ์ IS-A อยู่" ไม่สมบูรณ์ ควรลงท้ายด้วย"... และเราจำเป็นต้องเปลี่ยนพฤติกรรมเริ่มต้นของชั้นเรียน"ด้วยตัวอักษรขนาดใหญ่

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

ถ้าฉันเห็นชั้นเรียนextendsอีกชั้นหนึ่งฉันจะสงสัยว่าชั้นเรียนนั้นเปลี่ยนไปอย่างไร หากคุณไม่เปลี่ยนอะไรเลยอย่าทำให้ฉันมองผ่านชั้นเรียนเลย

กลับไปที่คำถามของคุณ: เมื่อใดที่คุณควรขยายคลาส Java เมื่อคุณมีเหตุผลจริงๆเหตุผลที่ดีจริงๆในการขยายชั้นเรียน

นี่คือตัวอย่างที่เฉพาะเจาะจง: วิธีหนึ่งในการวาดภาพที่กำหนดเอง (สำหรับเกมหรือภาพเคลื่อนไหวหรือเพียงแค่สำหรับองค์ประกอบที่กำหนดเอง) คือการขยายJPanelชั้นเรียน (ข้อมูลเพิ่มเติมเกี่ยวกับที่นี่ .) คุณขยายJPanelชั้นเรียนเพราะคุณจะต้องแทนที่paintComponent()ฟังก์ชั่น คุณเปลี่ยนพฤติกรรมของชั้นเรียนด้วยการทำเช่นนี้ คุณไม่สามารถวาดภาพที่กำหนดเองได้ด้วยการJPanelใช้งานเริ่มต้น

แต่อย่างที่ฉันพูดครูของคุณอาจใช้ตัวอย่างเหล่านี้เป็นข้อแก้ตัวเพื่อบังคับให้คุณฝึกฝนการขยายชั้นเรียน


1
โอ้เดี๋ยวก่อนคุณสามารถตั้งค่าBorderสีที่ตกแต่งเพิ่มเติม หรือสร้าง a JLabelและส่งการIconใช้งานแบบกำหนดเองไปยังมัน มันไม่จำเป็นที่จะต้อง subclass JPanelสำหรับการวาดภาพ (และทำไมJPanelแทนJComponent)
Holger

1
ฉันคิดว่าคุณมีความคิดที่ถูกต้องที่นี่ แต่อาจเลือกตัวอย่างที่ดีกว่า

1
แต่ผมอยากจะย้ำคำตอบของคุณถูกต้องและคุณทำจุดดี ฉันคิดว่าตัวอย่างที่เฉพาะเจาะจงและการสอนสนับสนุนมันอย่างอ่อน

1
@KevinWorkman ฉันไม่รู้เกี่ยวกับการพัฒนาเกม Swing แต่ฉันได้ทำ UIs แบบกำหนดเองบางอย่างใน Swing และ "ไม่ขยายคลาส Swing ดังนั้นจึงง่ายต่อการติดตั้งส่วนประกอบและ renderers ผ่าน config" ค่อนข้างเป็นมาตรฐานที่นั่น

1
"ฉันพนันได้เลยว่าพวกเขากำลังใช้สิ่งนี้เป็นตัวอย่างในการบังคับให้คุณ ... " หนึ่งในปัญหาใหญ่ของฉัน! ผู้สอนที่สอนวิธีการทำ X โดยใช้ตัวอย่างบ้า ๆ บอ ๆว่าทำไมต้องทำ X.
โซโลมอนช้า
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.