การสร้างคลาสย่อยสำหรับอินสแตนซ์เฉพาะเป็นการปฏิบัติที่ไม่ถูกต้องหรือไม่?


37

พิจารณาการออกแบบดังต่อไปนี้

public class Person
{
    public virtual string Name { get; }

    public Person (string name)
    {
        this.Name = name;
    }
}

public class Karl : Person
{
    public override string Name
    {
        get
        {
            return "Karl";
        }
    }
}

public class John : Person
{
    public override string Name
    {
        get
        {
            return "John";
        }
    }
}

คุณคิดว่ามีบางอย่างผิดปกติที่นี่หรือไม่? สำหรับฉันแล้วชั้นเรียนของ Karl และ John ควรเป็นเพียงอินสแตนซ์แทนที่จะเป็นคลาสที่เหมือนกับ:

Person karl = new Person("Karl");
Person john = new Person("John");

เหตุใดฉันจึงต้องสร้างคลาสใหม่เมื่ออินสแตนซ์เพียงพอ คลาสไม่ได้เพิ่มอะไรลงไปในอินสแตนซ์


17
น่าเสียดายที่คุณจะพบสิ่งนี้ในรหัสการผลิตจำนวนมาก ทำความสะอาดเมื่อคุณสามารถ
Adam Zuckerman

14
นี่คือการออกแบบที่ยอดเยี่ยม - หากนักพัฒนาต้องการทำให้ตัวเองขาดไม่ได้สำหรับการเปลี่ยนแปลงข้อมูลธุรกิจและไม่มีปัญหาใด ๆ ที่จะสามารถใช้ได้ตลอด 24 ชั่วโมง ;-)
Doc Brown

1
นี่เป็นคำถามที่เกี่ยวข้องซึ่ง OP ดูเหมือนว่าจะเชื่อตรงกันข้ามกับภูมิปัญญาดั้งเดิม programmers.stackexchange.com/questions/253612/…
Dunk

11
ออกแบบคลาสของคุณขึ้นอยู่กับพฤติกรรมที่ไม่ใช่ข้อมูล สำหรับฉันแล้วคลาสย่อยไม่เพิ่มพฤติกรรมใหม่ให้กับลำดับชั้น
Songo

2
สิ่งนี้ถูกใช้อย่างกว้างขวางกับคลาสย่อยที่ไม่ระบุชื่อ (เช่นตัวจัดการเหตุการณ์) ใน Java ก่อนที่ lambdas จะมาถึง Java 8 (ซึ่งเป็นสิ่งเดียวกันกับไวยากรณ์ที่แตกต่างกัน)
marczellm

คำตอบ:


77

ไม่จำเป็นต้องมีคลาสย่อยเฉพาะสำหรับทุกคน

คุณพูดถูกแล้วนั่นควรจะเป็นกรณีแทน

เป้าหมายของคลาสย่อย: เพื่อขยายคลาสพาเรนต์

คลาสย่อยถูกใช้เพื่อขยายฟังก์ชันการทำงานที่จัดเตรียมโดยคลาสพาเรนต์ ตัวอย่างเช่นคุณอาจมี:

  • คลาสพาเรนต์Batteryซึ่งสามารถมีPower()บางสิ่งและสามารถมีVoltageคุณสมบัติได้

  • และคลาสย่อยRechargeableBatteryซึ่งสามารถสืบทอดPower()และVoltageแต่ยังสามารถเป็นRecharge()d

ขอให้สังเกตว่าคุณสามารถส่งอินสแตนซ์ของRechargeableBatteryคลาสเป็นพารามิเตอร์ไปยังวิธีการใด ๆ ที่ยอมรับBatteryเป็นอาร์กิวเมนต์ สิ่งนี้เรียกว่าหลักการทดแทนของ Liskovซึ่งเป็นหนึ่งในห้าหลักการที่เป็นของแข็ง ในทำนองเดียวกันในชีวิตจริงหากเครื่องเล่น MP3 ของฉันยอมรับแบตเตอรี่ AA สองก้อนฉันสามารถใช้แบตเตอรี่ AA แบบชาร์จซ้ำได้สองก้อนแทน

โปรดทราบว่าบางครั้งมันเป็นเรื่องยากที่จะตรวจสอบว่าคุณจำเป็นต้องใช้เขตข้อมูลหรือคลาสย่อยเพื่อแสดงความแตกต่างระหว่างบางสิ่งบางอย่าง ตัวอย่างเช่นหากคุณต้องจัดการกับแบตเตอรี่ AA, AAA และ 9-Volt คุณจะสร้างคลาสย่อยสามคลาสหรือใช้ enum หรือไม่ “ แทนที่คลาสย่อยด้วยฟิลด์” ในRefactoringโดย Martin Fowler หน้า 232 อาจให้แนวคิดและวิธีการย้ายจากที่หนึ่งไปยังอีกที่หนึ่ง

ในตัวอย่างของคุณKarlและJohnไม่ขยายสิ่งใดหรือไม่มีค่าเพิ่มเติมใด ๆ : คุณสามารถมีฟังก์ชันการทำงานเดียวกันโดยใช้Personคลาสโดยตรง มีรหัสบรรทัดเพิ่มเติมโดยไม่มีค่าเพิ่มเติมจะไม่ดี

ตัวอย่างของกรณีธุรกิจ

สิ่งที่อาจเป็นกรณีธุรกิจที่มันจะทำให้รู้สึกถึงการสร้างคลาสย่อยสำหรับบุคคลที่เฉพาะเจาะจง?

สมมติว่าเราสร้างแอปพลิเคชันที่จัดการบุคคลที่ทำงานใน บริษัท แอปพลิเคชันจัดการสิทธิ์เช่นกันดังนั้น Helen นักบัญชีไม่สามารถเข้าถึงที่เก็บ SVN ได้ แต่ Thomas และ Mary ซึ่งเป็นโปรแกรมเมอร์สองคนไม่สามารถเข้าถึงเอกสารที่เกี่ยวข้องกับบัญชีได้

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

โมเดลที่แย่ที่สุดสำหรับแอ็พพลิเคชันดังกล่าวคือมีคลาสเช่น:

          แผนภาพคลาสแสดงคลาส Helen, Thomas, Mary และ Jimmy และผู้ปกครองทั่วไป: คลาส Person นามธรรม

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

          แผนภาพคลาสแสดงบุคคลนามธรรมที่มีลูกสามคน: นักบัญชีนามธรรม, โปรแกรมเมอร์นามธรรมและจิมมี่  นักบัญชีมี Helen คลาสย่อยและโปรแกรมเมอร์มีสองคลาสย่อย: Thomas และ Mary

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

if (person is Programmer)
{
    this.AccessGranted = true;
}

ดังนั้นคุณจะต้องลบคลาสที่คุณไม่ได้ใช้:

          แผนภาพคลาสประกอบด้วยคลาสย่อยนักบัญชี, โปรแกรมเมอร์และจิมมี่ที่มีคลาสพาเรนต์ทั่วไป: นามธรรมบุคคล

“ แต่ฉันสามารถรักษาจิมมี่ไว้ได้เหมือนเดิมเพราะจะมีซีอีโอเพียงคนเดียวเจ้านายใหญ่คนหนึ่ง - จิมมี่” คุณคิด ยิ่งไปกว่านั้น Jimmy ยังใช้รหัสของคุณจำนวนมากซึ่งจริงๆแล้วมีลักษณะเช่นนี้และไม่เหมือนกับตัวอย่างก่อนหน้านี้:

if (person is Jimmy)
{
    this.GiveUnrestrictedAccess(); // Because Jimmy should do whatever he wants.
}
else if (person is Programmer)
{
    this.AccessGranted = true;
}

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


6
@DocBrown: ฉันมักจะแปลกใจที่เห็นว่าคำตอบสั้น ๆ ที่ไม่ผิด แต่ไม่ลึกพอที่จะมีคะแนนสูงกว่าคำตอบที่อธิบายในเชิงลึกเกี่ยวกับเรื่องนี้ อาจมีคนจำนวนมากที่ไม่ชอบอ่านมากคำตอบสั้น ๆ น่าดึงดูดสำหรับพวกเขา ถึงกระนั้นฉันได้แก้ไขคำตอบของฉันเพื่อให้คำอธิบายอย่างน้อย
Arseni Mourzenko

3
คำตอบสั้น ๆ มักจะโพสต์ก่อน โพสต์ก่อนหน้านี้ได้รับการโหวตมากขึ้น
ทิม B

1
@TimB: ฉันมักจะเริ่มต้นด้วยคำตอบขนาดกลางแล้วแก้ไขให้สมบูรณ์มาก (นี่คือตัวอย่างเนื่องจากอาจมีการแก้ไขประมาณสิบห้าครั้งแสดงเพียงสี่ครั้ง) ฉันสังเกตเห็นว่ายิ่งคำตอบนานขึ้นเรื่อย ๆ เวลายิ่งน้อยลงก็ยิ่งได้รับมากขึ้น คำถามที่คล้ายกันซึ่งยังคงสั้นดึงดูดดึงดูดมากขึ้น
Arseni Mourzenko

เห็นด้วยกับ @DocBrown ตัวอย่างเช่นคำตอบที่ดีจะพูดบางอย่างเช่น "subclass สำหรับพฤติกรรมไม่ใช่เนื้อหา" และอ้างถึงการอ้างอิงบางอย่างที่ฉันจะไม่ทำในความคิดเห็นนี้
user949300

2
ในกรณีที่ไม่ชัดเจนจากย่อหน้าสุดท้าย: แม้ว่าคุณจะมีเพียง 1 คนที่มีการเข้าถึงที่ไม่ จำกัด คุณก็ควรทำให้บุคคลนั้นเป็นอินสแตนซ์ของคลาสที่แยกต่างหากซึ่งใช้การเข้าถึงแบบไม่ จำกัด การตรวจสอบบุคคลนั้นส่งผลให้เกิดการโต้ตอบของรหัสข้อมูลและสามารถเปิดเวิร์มทั้งหมดได้ ตัวอย่างเช่นหากคณะกรรมการ บริษัท ตัดสินใจแต่งตั้งผู้มีอำนาจลงนามในฐานะประธานเจ้าหน้าที่บริหาร หากคุณไม่มีคลาส "ไม่ จำกัด " คุณไม่จำเป็นต้องอัปเดต CEO ที่มีอยู่เดิม แต่เพิ่มเช็คเพิ่มเติมอีก 2 รายการสำหรับสมาชิกคนอื่น ๆ หากคุณมีคุณก็ตั้งให้เป็น "ไม่ จำกัด "
Nzall

15

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

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

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


12

เหตุใดฉันจึงต้องสร้างคลาสใหม่เมื่ออินสแตนซ์เพียงพอ

ในกรณีส่วนใหญ่คุณจะไม่ ตัวอย่างของคุณเป็นกรณีที่ดีซึ่งพฤติกรรมนี้ไม่ได้เพิ่มมูลค่าที่แท้จริง

นอกจากนี้ยังละเมิดหลักการ Open Openเนื่องจากคลาสย่อยนั้นไม่ใช่ส่วนขยาย แต่ปรับเปลี่ยนการทำงานภายในของผู้ปกครอง ยิ่งไปกว่านั้นตัวสร้างสาธารณะของผู้ปกครองนั้นตอนนี้รำคาญในคลาสย่อยและ API จึงกลายเป็นเข้าใจยากน้อยลง

อย่างไรก็ตามบางครั้งหากคุณมีการกำหนดค่าพิเศษเพียงหนึ่งหรือสองครั้งที่มักใช้ตลอดรหัสบางครั้งก็สะดวกและเสียเวลาน้อยกว่าเพียงแค่ซับคลาสผู้ปกครองที่มีคอนสตรัคเตอร์ที่ซับซ้อน ในกรณีพิเศษเช่นนี้ฉันไม่เห็นอะไรผิดปกติกับวิธีการดังกล่าว ลองเรียกมันว่าconstructor currying j / k


ฉันไม่แน่ใจว่าฉันเห็นด้วยกับวรรค OCP หากคลาสเปิดเผยตัวตั้งค่าคุณสมบัติให้กับลูกหลานของมัน - โดยถือว่าเป็นไปตามหลักการออกแบบที่ดี - คุณสมบัติไม่ควรเป็นส่วนหนึ่งของผลงานภายใน
Ben Aaronson

@BenAaronson ตราบใดที่คุณสมบัติของตัวเองไม่ได้เปลี่ยนแปลงคุณก็ไม่ได้ละเมิด OCP แต่ก็ทำเช่นนี้ แน่นอนคุณสามารถใช้คุณสมบัตินั้นในการทำงานภายในของมัน แต่คุณไม่ควรเปลี่ยนพฤติกรรมที่มีอยู่! คุณควรขยายพฤติกรรมเท่านั้นไม่เปลี่ยนด้วยการสืบทอด แน่นอนว่ามีข้อยกเว้นสำหรับทุกกฎ
Falcon

1
ดังนั้นการใช้สมาชิกเสมือนเป็นการละเมิด OCP หรือไม่?
Ben Aaronson

3
@ Falcon ไม่จำเป็นต้องได้รับคลาสใหม่เพียงเพื่อหลีกเลี่ยงการเรียก constructor ที่ซับซ้อนซ้ำ เพียงแค่สร้างโรงงานสำหรับกรณีทั่วไปและกำหนดค่าความแปรปรวนเล็กน้อย
คม

@BenAaronson ฉันว่ามีสมาชิกเสมือนที่มีการใช้งานจริงเป็นการละเมิดดังกล่าว สมาชิกที่เป็นนามธรรมหรือพูดวิธีการที่ไม่ทำอะไรเลยจะเป็นจุดส่วนขยายที่ถูกต้อง โดยทั่วไปเมื่อคุณดูที่รหัสของคลาสพื้นฐานคุณจะรู้ว่ามันจะทำงานตามที่เป็นอยู่แทนที่จะเป็นวิธีการที่ไม่ใช่ขั้นสุดท้ายทุกครั้งที่เป็นผู้สมัครเพื่อแทนที่ด้วยสิ่งที่แตกต่างกันโดยสิ้นเชิง หรือเพื่อให้แตกต่าง: วิธีการที่คลาสสืบทอดสามารถส่งผลกระทบต่อพฤติกรรมของคลาสฐานจะชัดเจนและถูก จำกัด ให้เป็น "สารเติมแต่ง"
millimoose

8

ถ้านี่เป็นขอบเขตของการฝึกฉันก็เห็นด้วยว่านี่เป็นการฝึกฝนที่ไม่ดี

ถ้าจอห์นและคาร์ลมีพฤติกรรมแตกต่างกันสิ่งต่าง ๆ ก็เปลี่ยนไปเล็กน้อย อาจเป็นได้ว่าpersonมีวิธีการcleanRoom(Room room)ที่ปรากฏว่าจอห์นเป็นเพื่อนร่วมห้องที่ดีและทำความสะอาดอย่างมีประสิทธิภาพมาก แต่คาร์ลไม่ได้และไม่ทำความสะอาดห้องมากนัก

ในกรณีนี้มันจะสมเหตุสมผลที่จะให้มันเป็นคลาสย่อยของตัวเองพร้อมกับพฤติกรรมที่กำหนดไว้ มีวิธีที่ดีกว่าในการบรรลุสิ่งเดียวกัน (เช่นCleaningBehaviorชั้นเรียน) แต่อย่างน้อยวิธีนี้ไม่ใช่การละเมิดหลักการ OO อย่างร้ายแรง


ไม่มีพฤติกรรมที่แตกต่างกันเพียงแค่ข้อมูลที่แตกต่าง ขอบคุณ
Ignacio Soler Garcia

6

ในบางกรณีคุณรู้ว่าจะมีการตั้งค่าจำนวนครั้งและมันก็โอเค (แม้ว่าในความคิดของฉันน่าเกลียด) จะใช้วิธีนี้ มันจะดีกว่าถ้าใช้ enum ถ้าภาษาอนุญาต

ตัวอย่าง Java:

public enum Person
{
    KARL("Karl"),
    JOHN("John")

    private final String name;

    private Person(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return this.name;
    }
}

6

ผู้คนจำนวนมากตอบไปแล้ว คิดว่าฉันจะให้มุมมองส่วนตัวของฉันเอง


กาลครั้งหนึ่งนานมาแล้วที่ฉันทำงานในแอพ (และยังทำอยู่) ที่สร้างเพลง

แอปพลิเคมีนามธรรมScaleชั้นเรียนกับหลาย subclasses: CMajor, DMinorฯลฯScaleมองสิ่งที่ต้องการเพื่อ:

public abstract class Scale {
    protected Note[] notes;
    public Scale() {
        loadNotes();
    }
    // .. some other stuff ommited
    protected abstract void loadNotes(); /* subclasses put notes in the array
                                          in this method. */
}

เครื่องกำเนิดเพลงทำงานกับScaleอินสแตนซ์เฉพาะเพื่อสร้างเพลง ผู้ใช้จะเลือกสเกลจากรายการเพื่อสร้างเพลงจาก

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

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

บ่อยครั้งที่คิดได้ง่ายในแง่ของ 'ซูเปอร์คลาสและคลาสย่อย' เกือบทุกอย่างสามารถแสดงผ่านระบบนี้: ซูเปอร์PersonคลาสJohnและคลาสย่อยและMary; ซูเปอร์CarคลาสVolvoและคลาสย่อยและMazda; superclass Missileและ subclasses SpeedRocked, และLandMineTrippleExplodingThingy

เป็นเรื่องธรรมดามากที่จะคิดในลักษณะนี้โดยเฉพาะอย่างยิ่งสำหรับคนที่ค่อนข้างใหม่กับ OO

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

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

และนั่นคือเหตุผลที่ผมควรจะได้สร้างคอนกรีตScaleชั้นที่มีNote[]ข้อมูลและให้วัตถุกรอกข้อมูลลงในแม่แบบนี้ ; อาจผ่านตัวสร้างหรือบางสิ่งบางอย่าง และในที่สุดฉันก็ทำเช่นนั้น

ทุกครั้งที่คุณออกแบบเทมเพลตในคลาส (เช่นNote[]สมาชิกว่างที่ต้องเติมหรือString nameฟิลด์ที่ต้องกำหนดค่า) โปรดจำไว้ว่ามันเป็นงานของวัตถุของคลาสนี้เพื่อเติมเทมเพลต ( หรือผู้ที่สร้างวัตถุเหล่านี้) คลาสย่อยนั้นมีจุดประสงค์เพื่อเพิ่มการใช้งานไม่ใช่เพื่อเติมเทมเพลต


คุณอาจถูกล่อลวงให้สร้าง "superclass Person, subclasses JohnและMary" ชนิดของระบบเหมือนที่คุณทำเพราะคุณชอบความเป็นทางการที่จะทำให้คุณได้รับ

วิธีนี้คุณก็สามารถพูดแทนPerson p = new Mary() Person p = new Person("Mary", 57, Sex.FEMALE)มันทำให้สิ่งต่าง ๆ เป็นระเบียบมากขึ้นและมีโครงสร้างมากขึ้น แต่อย่างที่เราพูดการสร้างคลาสใหม่สำหรับการรวมกันของข้อมูลทุกอย่างนั้นไม่ใช่วิธีการที่ดีเพราะมันจะขยายโค้ดเพื่ออะไรและ จำกัด คุณในแง่ของความสามารถในการใช้งานจริง

ดังนั้นนี่คือวิธีการแก้ปัญหา: ใช้โรงงานพื้นฐานหรืออาจเป็นแบบคงที่ ชอบมาก

public final class PersonFactory {
    private PersonFactory() { }

    public static Person createJohn(){
        return new Person("John", 40, Sex.MALE);
    }

    public static Person createMary(){
        return new Person("Mary", 57, Sex.FEMALE);
    }

    // ...
}

ด้วยวิธีนี้คุณสามารถใช้ 'ที่ตั้งไว้ล่วงหน้า' 'มาพร้อมกับโปรแกรม' ได้อย่างง่ายดายเช่น: Person mary = PersonFactory.createMary()แต่คุณยังสงวนสิทธิ์ในการออกแบบบุคคลใหม่แบบไดนามิกตัวอย่างเช่นในกรณีที่คุณต้องการอนุญาตให้ผู้ใช้ทำเช่นนั้น . เช่น:

// .. requesting the user for input ..
String name = // user input
int age = // user input
Sex sex = // user input, interpreted
Person newPerson = new Person(name, age, sex);

หรือดียิ่งขึ้น: ทำอะไรเช่นนั้น:

public final class PersonFactory {
    private PersonFactory() { }

    private static Map<String, Person> persons = new HashMap<>();
    private static Map<String, PersonData> personBlueprints = new HashMap<>();

    public static void addPerson(Person person){
        persons.put(person.getName(), person);
    }

    public static Person getPerson(String name){
        return persons.get(name);
    }

    public static Person createPerson(String blueprintName){
        PersonData data = personBlueprints.get(blueprintName);
        return new Person(data.name, data.age, data.sex);
    }

    // .. or, alternative to the last method
    public static Person createPerson(String personName){
        Person blueprint = persons.get(personName);
        return new Person(blueprint.getName(), blueprint.getAge(), blueprint.getSex());
    }

}

public class PersonData {
    public String name;
    public int age;
    public Sex sex;

    public PersonData(String name, int age, Sex sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}

ฉันถูกพาไป ฉันคิดว่าคุณได้รับความคิด


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

คุณไม่ควรสร้างคลาสใหม่สำหรับชุดข้อมูลที่เป็นไปได้ทั้งหมด (เช่นเดียวกับที่ฉันไม่ควรสร้างScaleคลาสย่อยใหม่สำหรับทุกชุดที่เป็นไปได้Note)

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

หวังว่าจะช่วย


นี่เป็นตัวอย่างที่ยอดเยี่ยมขอบคุณมาก
Ignacio Soler Garcia

@SoMoS ดีใจที่ฉันสามารถช่วย
Aviv Cohn

2

นี่เป็นข้อยกเว้นสำหรับ 'ควรเป็นอินสแตนซ์' ที่นี่ - แม้ว่าจะสุดขั้วเล็กน้อย

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


2

มันถือเป็นทางปฏิบัติที่ดีที่จะมีรหัสที่ซ้ำกัน

นี้เป็นอย่างน้อยบางส่วนเพราะสายของรหัสและดังนั้นขนาดของความสัมพันธ์ codebase ค่าใช้จ่ายในการบำรุงรักษาและข้อบกพร่อง

นี่คือตรรกะสามัญสำนึกที่เกี่ยวข้องกับฉันในหัวข้อเฉพาะนี้:

1) ถ้าฉันต้องเปลี่ยนสิ่งนี้ฉันต้องไปหลายที่ น้อยกว่าดีกว่าที่นี่

2) มีเหตุผลไหมที่ทำแบบนี้? ในตัวอย่างของคุณ - ไม่สามารถทำได้ - แต่ในบางตัวอย่างอาจมีเหตุผลบางอย่างสำหรับการทำเช่นนี้ หนึ่งที่ฉันนึกได้ว่าส่วนบนของหัวอยู่ในกรอบบางอย่างเช่น Grails ยุคต้นซึ่งการสืบทอดไม่จำเป็นต้องเล่นกับปลั๊กอินบางอย่างสำหรับ GORM ไม่กี่และไกลระหว่าง

3) ทำความสะอาดและทำความเข้าใจวิธีนี้ได้ง่ายขึ้นหรือไม่? อีกครั้งมันไม่ได้อยู่ในตัวอย่างของคุณ - แต่มีรูปแบบการสืบทอดบางอย่างที่อาจยืดออกได้ซึ่งคลาสที่แยกออกจากกันนั้นง่ายต่อการดูแลรักษา (การใช้คำสั่งหรือรูปแบบกลยุทธ์บางอย่างเข้ามาในใจ)


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

ดูจุดที่ 2 แน่นอนมีเหตุผลว่าทำไมบางครั้ง นั่นเป็นเหตุผลที่คุณต้องถามก่อนควักรหัสของใครบางคน
dcgregorya

0

มีกรณีใช้งาน: การสร้างการอ้างอิงไปยังฟังก์ชั่นหรือวิธีการเป็นวัตถุปกติ

คุณสามารถเห็นสิ่งนี้ในโค้ด Java GUI มาก:

ActionListener a = new ActionListener() {
    public void actionPerformed(ActionEvent arg0) {
        /* ... */
    }
};

คอมไพเลอร์ช่วยคุณที่นี่ด้วยการสร้างคลาสย่อยที่ไม่ระบุชื่อใหม่

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