คลาสย่อยของตัวสร้างเท่านั้น: นี่เป็นรูปแบบการต่อต้านหรือไม่


37

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

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

ตัวอย่างเช่นลองพิจารณาชั้นเรียนชีวิตจริงนี้:

public class DataHelperBuilder
{
    public string DatabaseEngine { get; set; }
    public string ConnectionString { get; set; }

    public DataHelperBuilder(string databaseEngine, string connectionString)
    {
        DatabaseEngine = databaseEngine;
        ConnectionString = connectionString;
    }

    // Other optional "DataHelper" configuration settings omitted

    public DataHelper CreateDataHelper()
    {
        Type dataHelperType = DatabaseEngineTypeHelper.GetType(DatabaseEngine);
        DataHelper dh = (DataHelper)Activator.CreateInstance(dataHelperType);
        dh.SetConnectionString(ConnectionString);

        // Omitted some code that applies decorators to the returned object
        // based on omitted configuration settings

        return dh;
    }
}

การอ้างสิทธิ์ของเขาคือมันจะเหมาะสมอย่างยิ่งที่จะมีคลาสย่อยเช่นนี้:

public class SystemDataHelperBuilder
{
    public SystemDataHelperBuilder()
        : base(Configuration.GetSystemDatabaseEngine(),
               Configuration.GetSystemConnectionString())
    {
    }
 }

ดังนั้นคำถาม:

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

ฉันขอโทษถ้าสิ่งนี้กลายเป็นคำตอบที่ googleable ได้อย่างง่ายดาย การค้นหาของฉันบน google ส่วนใหญ่ส่งคืนข้อมูลเกี่ยวกับรูปแบบการต่อต้านการสร้างเหลื่อมและไม่ใช่สิ่งที่ฉันกำลังมองหา


2
ฉันคิดว่าคำว่า "ผู้ช่วย" ในชื่อเป็นปฏิปักษ์ มันเหมือนกับการอ่านเรียงความที่มีการอ้างอิงอย่างต่อเนื่องกับสิ่งของและ whatsit พูดในสิ่งที่มันเป็น มันเป็นโรงงานหรือไม่? คอนสตรัค? สำหรับฉันมันมีกระบวนการคิดที่ขี้เกียจและส่งเสริมการละเมิดหลักการความรับผิดชอบเพียงอย่างเดียวเนื่องจากชื่อความหมายที่เหมาะสมจะนำไปสู่ ฉันเห็นด้วยกับผู้ลงคะแนนคนอื่นและคุณควรยอมรับคำตอบของ @ lxrec การสร้างคลาสย่อยสำหรับวิธีการจากโรงงานนั้นไม่มีจุดหมายอย่างสมบูรณ์เว้นแต่คุณจะไม่สามารถเชื่อถือผู้ใช้ให้ส่งอาร์กิวเมนต์ที่ถูกต้องตามที่ระบุในเอกสาร ในกรณีนี้รับผู้ใช้ใหม่
Aaron Hall

ฉันเห็นด้วยอย่างยิ่งกับคำตอบของ @ Ixrec ท้ายที่สุดคำตอบของเขาบอกว่าฉันถูกต้องมาตลอดและเพื่อนร่วมงานของฉันก็ผิด ;-) แต่อย่างจริงจังนั่นคือสาเหตุที่ฉันรู้สึกแปลก ๆ ที่ยอมรับมัน; ฉันคิดว่าฉันถูกกล่าวหาว่ามีอคติ แต่ฉันยังใหม่กับโปรแกรมเมอร์ StackExchange; ฉันยินดีที่จะยอมรับถ้าฉันมั่นใจว่ามันจะไม่เป็นการละเมิดมารยาท
HardlyKnowEm

1
ไม่คาดว่าคุณจะยอมรับคำตอบที่คุณคิดว่ามีค่าที่สุด ชุมชนที่คิดว่ามีค่ามากที่สุดคือ Ixrec มากกว่า 3 ต่อ 1 ดังนั้นพวกเขาจึงคาดหวังให้คุณยอมรับ ในชุมชนนี้มีความขัดข้องน้อยมากที่ผู้ถามตอบยอมรับคำตอบที่ไม่ถูกต้อง โปรดทราบว่าไม่มีใครบ่นเกี่ยวกับคำตอบที่ยอมรับได้ที่นี่ แต่พวกเขาบ่นเกี่ยวกับการลงคะแนนซึ่งดูเหมือนว่าจะเป็นฝ่ายตัวเอง: stackoverflow.com/q/2052390/541136
Aaron Hall

ดีมากฉันจะยอมรับมัน ขอขอบคุณที่สละเวลาสอนฉันเกี่ยวกับมาตรฐานชุมชนที่นี่
HardlyKnowEm

คำตอบ:


54

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

เกี่ยวกับสัญชาตญาณทั่วไปทั้ง "เฉพาะเจาะจงมากขึ้น" และ "ช่วง จำกัด " เป็นวิธีที่อาจเป็นอันตรายของการคิดเกี่ยวกับคลาสย่อยเพราะทั้งคู่บอกเป็นนัยว่าการทำให้ Square เป็นคลาสย่อยของสี่เหลี่ยมผืนผ้าเป็นแนวคิดที่ดี โดยไม่ต้องพึ่งพาสิ่งที่เป็นทางการเช่น LSP ฉันจะบอกว่าสัญชาตญาณที่ดีกว่าคือ subclass ทั้งสองมีการใช้งานของอินเตอร์เฟซฐานหรือขยายอินเทอร์เฟซเพื่อเพิ่มฟังก์ชันใหม่บางอย่าง


2
วิธีการจากโรงงานจะไม่อนุญาตให้คุณบังคับใช้พฤติกรรมบางอย่าง (ควบคุมโดยข้อโต้แย้ง) ใน codebase ของคุณ และบางครั้งมันก็มีประโยชน์ที่จะอธิบายอย่างชัดเจนว่าคลาส / เมธอด / ฟิลด์นี้ใช้SystemDataHelperBuilderโดยเฉพาะ
Telastyn

3
อาอาร์กิวเมนต์ของสี่เหลี่ยมจัตุรัสกับสี่เหลี่ยมผืนผ้านั้นเป็นสิ่งที่ฉันกำลังมองหาอยู่ฉันคิดว่า ขอบคุณ!
HardlyKnowEm

7
แน่นอนว่าการมีสี่เหลี่ยมเป็นคลาสย่อยของสี่เหลี่ยมผืนผ้าอาจไม่เป็นไรหากพวกมันไม่เปลี่ยนรูป คุณต้องมองไปที่ LSP จริงๆ
David Conrad

10
สิ่งที่เกี่ยวกับ "ปัญหา" ของสี่เหลี่ยมจัตุรัส / สี่เหลี่ยมผืนผ้าคือว่ารูปทรงเรขาคณิตนั้นไม่เปลี่ยนรูป คุณสามารถใช้จตุรัสได้ทุกที่ที่สี่เหลี่ยมทำให้รู้สึกได้ แต่การปรับขนาดไม่ใช่สิ่งที่สี่เหลี่ยมหรือสี่เหลี่ยมทำ
Doval

3
@nha LSP = หลักการทดแทน Liskov
Ixrec

16

ข้อใดต่อไปนี้ถูกต้อง?

เพื่อนร่วมงานของคุณถูกต้อง (สมมติว่าเป็นระบบประเภทมาตรฐาน)

ลองคิดดูว่าคลาสจะเป็นค่าทางกฎหมายที่เป็นไปได้หรือไม่ หากคลาสAมีหนึ่งฟิลด์Fคุณอาจมีแนวโน้มที่จะคิดว่าAมีค่าตามกฎหมาย 256 ตัว แต่ปรีชานั้นไม่ถูกต้อง Aกำลัง จำกัด "การเปลี่ยนแปลงค่าทุกครั้งที่เคย" ถึง "ต้องมีฟิลด์Fเป็น 0-255"

ถ้าคุณขยายAด้วยBซึ่งมีข้อมูลไบต์อีกFFนั่นคือการเพิ่มข้อ จำกัด แทนที่จะเป็น "value where Fis byte" คุณมี "value Fat byte && FFเป็น byte" เมื่อคุณรักษากฎเก่าทุกอย่างที่ทำงานให้กับ basetype จะยังคงใช้ได้กับประเภทย่อยของคุณ แต่เนื่องจากคุณกำลังเพิ่มกฎคุณจึงมีความเชี่ยวชาญนอกเหนือจาก "อาจเป็นอะไรก็ได้"

หรือจะคิดว่ามันอีกทางหนึ่งสมมติว่าAมีพวงของเชื้อ: การB, และC DตัวแปรชนิดAอาจเป็นชนิดย่อยใด ๆ ก็ได้ แต่ตัวแปรชนิดBนั้นมีความเฉพาะเจาะจงมากกว่า มัน (มากที่สุดในระบบการพิมพ์) ไม่สามารถเป็นหรือCD

นี่เป็นรูปแบบการต่อต้านหรือไม่

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

ฉันจะไม่พิจารณารูปแบบการต่อต้านนี้เนื่องจากมันไม่ได้ผิดอย่างชัดเจนและไม่ดีในทุกสถานการณ์


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

6
@Telastyn เรากำลังจะปิดหัวข้อ แต่มันพิสูจน์ได้ว่า cardinality (หลวม "ขนาด") ของชุดจำนวนเต็มคู่นั้นเหมือนกับชุดของจำนวนเต็มทั้งหมดหรือแม้แต่จำนวนตรรกยะทั้งหมด มันเจ๋งจริงๆ ดูmath.grinnell.edu/~miletijo/museum/infinite.html
HardlyKnowEm

2
ทฤษฎีเซตค่อนข้างใช้ง่าย คุณสามารถใส่จำนวนเต็มและ evens ในการติดต่อแบบหนึ่งต่อหนึ่ง (เช่นใช้ฟังก์ชันn -> 2n) สิ่งนี้เป็นไปไม่ได้เสมอไป คุณไม่สามารถแมปจำนวนเต็มกับ reals ได้ดังนั้นชุดของ reals จึงเป็น "ใหญ่กว่า" มากกว่าจำนวนเต็ม แต่คุณสามารถแมปช่วงเวลา (จริง) [0, 1] กับชุดของ reals ทั้งหมดเพื่อที่จะทำให้พวกเขา "ขนาด" เดียวกัน เซ็ตย่อยถูกกำหนดเป็น "ทุกองค์ประกอบของAยังเป็นองค์ประกอบของB" ได้อย่างแม่นยำเพราะเมื่อชุดของคุณไม่มีที่สิ้นสุดก็อาจเป็นกรณีที่พวกเขามีความสำคัญเชิงเดียวกัน แต่มีองค์ประกอบที่แตกต่างกัน
Doval

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

1
@Doval: มีมากกว่าหนึ่งความหมายที่เป็นไปได้ของ "น้อยกว่า" - นั่นคือมากกว่าหนึ่งความสัมพันธ์ในการสั่งซื้อในชุด ความสัมพันธ์ "เป็นชุดย่อยของ" ให้การเรียงลำดับที่กำหนดไว้อย่างดี (บางส่วน) ยิ่งไปกว่านั้น "กฎเพิ่มเติม" สามารถนำมาใช้เพื่อหมายถึง "องค์ประกอบทั้งหมดของชุดตอบสนองข้อเสนอเพิ่มเติม (เช่น superset ของ) ข้อเสนอ (จากชุดที่แน่นอน)" ด้วยคำจำกัดความเหล่านี้ "กฎเพิ่มเติม" ย่อมหมายถึง "ค่าน้อยลง" นี่คือการพูดอย่างคร่าวๆแนวทางที่ใช้โดยแบบจำลองความหมายจำนวนมากของโปรแกรมคอมพิวเตอร์เช่นโดเมน Scott
psmears

12

ไม่ไม่ใช่รูปแบบการต่อต้าน ฉันสามารถนึกถึงกรณีการใช้งานจริงจำนวนหนึ่งสำหรับสิ่งนี้:

  1. หากคุณต้องการใช้การตรวจสอบเวลาการคอมไพล์เพื่อให้แน่ใจว่าการรวบรวมวัตถุเป็นไปตามคลาสย่อยหนึ่ง ๆ เท่านั้น ตัวอย่างเช่นถ้าคุณมีMySQLDaoและSqliteDaoในระบบของคุณ แต่ด้วยเหตุผลบางอย่างที่คุณต้องการตรวจสอบให้แน่ใจว่าการรวบรวมมีเพียงข้อมูลจากแหล่งเดียวหากคุณใช้คลาสย่อยตามที่คุณอธิบายคุณสามารถให้คอมไพเลอร์ตรวจสอบความถูกต้องนี้ ในทางตรงกันข้ามถ้าคุณเก็บแหล่งข้อมูลเป็นเขตข้อมูลสิ่งนี้จะกลายเป็นการตรวจสอบเวลาทำงาน
  2. โปรแกรมปัจจุบันของฉันมีหนึ่งถึงความสัมพันธ์แบบหนึ่งระหว่างAlgorithmConfiguration+ และProductClass AlgorithmInstanceในคำอื่น ๆ ถ้าคุณกำหนดค่าFooAlgorithmสำหรับProductClassในไฟล์คุณสมบัติคุณสามารถจะได้รับหนึ่งFooAlgorithmสำหรับการเรียนผลิตภัณฑ์ที่ วิธีเดียวที่จะได้รับสองFooAlgorithmสำหรับรับคือการสร้างประเภทรองProductClass FooBarAlgorithmไม่เป็นไรเพราะมันทำให้ไฟล์คุณสมบัติใช้งานง่าย (ไม่จำเป็นต้องใช้ไวยากรณ์สำหรับการกำหนดค่าต่างๆในส่วนหนึ่งของไฟล์คุณสมบัติ) และหายากมากที่จะมีอินสแตนซ์มากกว่าหนึ่งรายการสำหรับคลาสผลิตภัณฑ์ที่กำหนด
  3. คำตอบของ @ Telastyn เป็นอีกตัวอย่างที่ดี

บรรทัดล่าง: ไม่มีอันตรายใด ๆ ในการทำเช่นนี้ รูปแบบการต่อต้านถูกกำหนดให้เป็น:

anti-pattern (หรือ antipattern) เป็นการตอบสนองต่อปัญหาที่เกิดซ้ำซึ่งมักจะไม่ได้ผลและมีความเสี่ยงที่จะถูกต่อต้านอย่างมาก

ไม่มีความเสี่ยงในการต่อต้านอย่างมากที่นี่ดังนั้นจึงไม่ใช่รูปแบบการต่อต้าน


2
ใช่ฉันคิดว่าถ้าฉันต้องตอบคำถามอีกครั้งฉันจะพูดว่า "กลิ่นรหัส" มันไม่ดีพอที่จะรับประกันเตือนนักพัฒนารุ่นใหม่ที่จะหลีกเลี่ยงมัน แต่มันเป็นสิ่งที่ถ้าคุณเห็นมันอาจบ่งบอกว่ามีปัญหาในการออกแบบที่ครอบคลุม แต่ในที่สุดก็อาจถูกต้องด้วย
HardlyKnowEm

จุดที่ 1 ถูกต้องกับเป้าหมาย ฉันไม่เข้าใจจุดที่ 2 แต่ฉันแน่ใจว่ามันดีเหมือนกัน ... :)
Phil

9

หากบางอย่างเป็นรูปแบบหรือ antipattern ขึ้นอยู่กับภาษาและสภาพแวดล้อมที่คุณกำลังเขียนอย่างมีนัยสำคัญตัวอย่างเช่นforลูปเป็นรูปแบบในแอสเซมบลี C และภาษาที่คล้ายกันและ anti-pattern ใน lisp

ให้ฉันเล่าเรื่องของโค้ดที่ฉันเขียนมานานแล้ว ...

มันเป็นภาษาที่เรียกว่าLPCและใช้กรอบสำหรับการร่ายคาถาในเกม คุณมีคาถา superclass ซึ่งมี subclass ของคาถาต่อสู้ที่จัดการเช็คบางส่วนและจากนั้น subclass สำหรับคาถาสร้างความเสียหายโดยตรงเป้าหมายเดียวซึ่งถูก subclassed ไปยังคาถาแต่ละอย่าง - ขีปนาวุธสายฟ้าและอื่น ๆ

ลักษณะของการทำงานของ LPC คือคุณมีออบเจ็กต์หลักที่เป็น Singleton ก่อนที่คุณจะไป 'eww Singleton' - มันเป็นและมันก็ไม่ใช่ "eww" เลย ไม่มีใครทำสำเนาของคาถา แต่เรียกวิธีการคงที่ (มีประสิทธิภาพ) ในคาถา รหัสสำหรับขีปนาวุธเวทมนตร์ดูเหมือนว่า:

inherit "spells/direct";

void init() {
  ::init();
  damage = 10;
  cost = 3;
  delay = 1.0;
  caster_message = "You cast a magic missile at ${target}";
  target_message = "You are hit with a magic missile cast by ${caster}";
}

และคุณเห็นไหม มันเป็นคลาสคอนสตรัคเท่านั้น มันตั้งค่าบางอย่างที่อยู่ในระดับนามธรรมของผู้ปกครอง (ไม่ไม่ควรใช้ setters - มันไม่เปลี่ยนรูป) และนั่นก็คือ มันทำงานที่เหมาะสม มันง่ายต่อการเขียนโปรแกรมขยายและเข้าใจง่าย

การเรียงลำดับของรูปแบบนี้แปลสถานการณ์อื่น ๆ ที่คุณมีคลาสย่อยที่ขยายคลาสนามธรรมและตั้งค่าบางอย่างของตัวเอง แต่ฟังก์ชั่นทั้งหมดอยู่ในคลาสนามธรรมที่สืบทอด ลองดูที่StringBuilderใน Java สำหรับตัวอย่างนี้ - ไม่ใช่คอนสตรัคเตอร์เท่านั้น แต่ฉันกดเพื่อค้นหาตรรกะจำนวนมากในวิธีการด้วยตนเอง (ทุกอย่างอยู่ใน AbstractStringBuilder)

นี่ไม่ใช่สิ่งเลวร้ายและแน่นอนว่าไม่ใช่รูปแบบการต่อต้าน (และในบางภาษาอาจเป็นรูปแบบตัวเอง)


2

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

เราทำเช่นนี้เพราะมีข้อยกเว้นเกิดขึ้นจากการตรวจสอบประเภทของพวกเขา ดังนั้นเราจำเป็นต้องเข้ารหัสความเชี่ยวชาญในประเภทเพื่อให้ใครบางคนสามารถจับได้ReadErrorโดยไม่ต้องจับทั้งหมดIOErrorพวกเขาควรจะต้องการ

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

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

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

อย่างไรก็ตามการใช้งานประเภทสำหรับกรณีพิเศษอาจทำให้เกิดความสับสนเนื่องจากหากImmutableRectangle(1,1)และมีImmutableSquare(1)พฤติกรรมที่แตกต่างไปในทางใดทางหนึ่งในที่สุดก็มีคนสงสัยว่าทำไมและอาจไม่ต้องการ ซึ่งแตกต่างจากลำดับชั้นยกเว้นคุณตรวจสอบไม่ว่าจะเป็นรูปสี่เหลี่ยมผืนผ้าเป็นตารางโดยการตรวจสอบว่าไม่ได้กับheight == width instanceofในตัวอย่างของคุณมีความแตกต่างระหว่าง a SystemDataHelperBuilderและที่DataHelperBuilderสร้างขึ้นด้วยอาร์กิวเมนต์เดียวกันที่แน่นอนและที่อาจเกิดปัญหา ดังนั้นฟังก์ชั่นในการส่งคืนวัตถุที่สร้างขึ้นด้วยอาร์กิวเมนต์ "ด้านขวา" นั้นโดยปกติแล้วฉันจะทำสิ่งที่ถูกต้อง: คลาสย่อยส่งผลให้เกิดการแสดงสองแบบที่แตกต่างกันของสิ่งที่ "เหมือนกัน" และมันจะช่วยถ้าคุณทำอะไรบางอย่าง ตามปกติจะพยายามไม่ทำ

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


ฉันสามารถดูการใช้งานImmutableSquareMatrix:ImmutableMatrixได้หากมีวิธีที่มีประสิทธิภาพในการขอImmutableMatrixให้แสดงเนื้อหาของเนื้อหานั้นImmutableSquareMatrixถ้าเป็นไปได้ แม้ว่าImmutableSquareMatrixโดยทั่วไปแล้วคอนImmutableMatrixสตรัคเตอร์นั้นต้องการการจับคู่ความสูงและความกว้างและAsSquareMatrixวิธีการก็จะคืนค่าตัวเอง (แทนที่จะสร้างโครงสร้างImmutableSquareMatrixที่ล้อมรอบแบ็คอัพเดียวกัน) การออกแบบดังกล่าวจะช่วยให้การตรวจสอบพารามิเตอร์เวลาคอมไพล์ matrices
supercat

@supercat: จุดที่ดีและในตัวอย่างของผู้ถามหากมีฟังก์ชั่นที่จะต้องผ่านSystemDataHelperBuilderและไม่เพียง แต่DataHelperBuilderจะทำแล้วมันทำให้รู้สึกถึงการกำหนดประเภทสำหรับ (และอินเทอร์เฟซสมมติว่าคุณจัดการ DI ด้วยวิธีนั้น) . ในตัวอย่างของคุณมีการดำเนินการบางอย่างที่เมทริกซ์สแควร์อาจมีที่ไม่ใช่สแควร์จะไม่เช่นปัจจัย แต่จุดของคุณเป็นสิ่งที่ดีแม้ว่าระบบไม่ต้องการสิ่งที่คุณยังต้องการตรวจสอบตามประเภท .
Steve Jessop

... ดังนั้นฉันคิดว่ามันไม่เป็นความจริงทั้งหมดที่ฉันพูดว่า "คุณตรวจสอบว่าสี่เหลี่ยมเป็นสี่เหลี่ยมจัตุรัสโดยการตรวจสอบว่าheight == widthไม่ใช่กับinstanceof" คุณอาจต้องการสิ่งที่คุณสามารถยืนยันแบบคงที่
Steve Jessop

ฉันหวังว่าจะมีกลไกที่จะอนุญาตให้บางสิ่งเช่นไวยากรณ์ตัวสร้างเพื่อเรียกใช้วิธีการคงที่จากโรงงาน ประเภทของการแสดงออกfoo=new ImmutableMatrix(someReadableMatrix)มีความชัดเจนกว่าsomeReadableMatrix.AsImmutable()หรือแม้กระทั่งImmutableMatrix.Create(someReadableMatrix)แต่รูปแบบหลังมีความเป็นไปได้ความหมายที่เป็นประโยชน์ในอดีตขาด; ถ้าคอนสตรัคเตอร์สามารถบอกได้ว่าเมทริกซ์นั้นเป็นสแควร์ความสามารถในการสะท้อนชนิดของมันนั้นจะสะอาดกว่าการใช้โค้ดดาวน์สตรีมที่ต้องการเมทริกซ์สแควร์เพื่อสร้างอินสแตนซ์ของวัตถุใหม่
supercat

@supercat: สิ่งหนึ่งเล็ก ๆ เกี่ยวกับ Python ที่ฉันชอบคือไม่มีตัวnewดำเนินการ คลาสเป็นเพียง callable ซึ่งเมื่อเรียกว่าเกิดขึ้นเพื่อกลับ (ตามค่าเริ่มต้น) ตัวอย่างของตัวเอง ดังนั้นหากคุณต้องการรักษาความสอดคล้องของอินเทอร์เฟซจึงเป็นไปได้อย่างสมบูรณ์ที่จะเขียนฟังก์ชั่นจากโรงงานที่ชื่อขึ้นต้นด้วยตัวอักษรพิมพ์ใหญ่ดังนั้นจึงดูเหมือนกับการเรียกคอนสตรัคเตอร์ ไม่ว่าฉันจะทำสิ่งนี้เป็นประจำกับฟังก์ชั่นจากโรงงาน แต่มันก็อยู่ที่นั่นเมื่อคุณต้องการ
Steve Jessop

2

คำจำกัดความเชิงวัตถุที่เข้มงวดที่สุดของความสัมพันธ์คลาสย่อยเรียกว่า "is-a" การใช้ตัวอย่างดั้งเดิมของสี่เหลี่ยมและสี่เหลี่ยมสี่เหลี่ยม« is-a »สี่เหลี่ยม

โดยสิ่งนี้คุณควรใช้คลาสย่อยสำหรับสถานการณ์ใด ๆ ที่คุณรู้สึกว่า« is-a »เป็นความสัมพันธ์ที่มีความหมายสำหรับรหัสของคุณ

ในกรณีเฉพาะของคลาสย่อยที่ไม่มีอะไรนอกจากคอนสตรัคเตอร์คุณควรวิเคราะห์มันจากมุมมอง« is-a » เป็นที่ชัดเจนว่า SystemDataHelperBuilder « is-a » DataHelperBuilder ดังนั้นการส่งผ่านครั้งแรกจึงเป็นการใช้งานที่ถูกต้อง

คำถามที่คุณควรตอบคือรหัสอื่นใดที่ใช้ประโยชน์จากความสัมพันธ์« is-a »นี้ รหัสอื่นใดพยายามแยกแยะ SystemDataHelperBuilders จากประชากรทั่วไปของ DataHelperBuilders หรือไม่? ถ้าเป็นเช่นนั้นความสัมพันธ์ค่อนข้างสมเหตุสมผลและคุณควรเก็บคลาสย่อย

อย่างไรก็ตามหากไม่มีรหัสอื่นให้ทำสิ่งนี้สิ่งที่คุณมีคือ Relationshp ที่อาจเป็นความสัมพันธ์« is-a »หรือสามารถนำไปใช้กับโรงงานได้ ในกรณีนี้คุณสามารถไปทางใดทางหนึ่ง แต่ฉันอยากจะแนะนำโรงงานมากกว่าความสัมพันธ์« is-a » เมื่อวิเคราะห์รหัสเชิงวัตถุที่เขียนโดยบุคคลอื่นลำดับชั้นของชั้นเรียนเป็นแหล่งข้อมูลที่สำคัญ มันให้ความสัมพันธ์« is-a »ที่พวกเขาจะต้องทำความเข้าใจเกี่ยวกับโค้ด ดังนั้นการวางพฤติกรรมนี้ลงในคลาสย่อยส่งเสริมพฤติกรรมจาก "สิ่งที่บางคนจะพบถ้าพวกเขาขุดเข้าไปในวิธีการของซูเปอร์คลาส" เป็น "สิ่งที่ทุกคนจะมองผ่านก่อนที่พวกเขาจะเริ่มดำน้ำในรหัส" การอ้างอิง Guido van Rossum "รหัสถูกอ่านบ่อยกว่าที่เขียนไว้" ดังนั้นการลดความสามารถในการอ่านรหัสของคุณสำหรับนักพัฒนาซอฟต์แวร์ที่กำลังจะมาถึงคือราคาที่ต้องจ่าย ฉันจะเลือกที่จะไม่จ่ายเงินถ้าฉันสามารถใช้โรงงานแทน


1

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

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


1

ฉันคิดว่ากุญแจสำคัญในการตอบคำถามนี้คือการดูที่สถานการณ์การใช้งานเฉพาะนี้

การสืบทอดใช้เพื่อบังคับใช้ตัวเลือกการกำหนดค่าฐานข้อมูลเช่นสตริงการเชื่อมต่อ

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


1

ฉันใช้คลาสย่อยของนวกรรมิกคอนสตรัคเตอร์อย่างสม่ำเสมอเพื่อแสดงแนวคิดที่แตกต่าง

ตัวอย่างเช่นพิจารณาคลาสต่อไปนี้:

public class Outputter
{
    private readonly IOutputMethod outputMethod;
    private readonly IDataMassager dataMassager;

    public Outputter(IOutputMethod outputMethod, IDataMassager dataMassager)
    {
        this.outputMethod = outputMethod;
        this.dataMassager = dataMassager;
    }

    public MassageDataAndOutput(string data)
    {
        this.outputMethod.Output(this.dataMassager.Massage(data));
    }
}

จากนั้นเราสามารถสร้างคลาสย่อย:

public class CapitalizeAndPrintOutputter : Outputter
{
    public CapitalizeAndPrintOutputter()
        : base(new PrintOutputMethod(), new Capitalizer())
    {
    }
}

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

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


1

ให้ฉันทราบก่อนว่าเป็นรหัสที่ถูกเขียน, sub-class ไม่เฉพาะเจาะจงมากขึ้นที่ผู้ปกครองเป็น, ขณะที่ทั้งสองฟิลด์เริ่มต้นได้ทั้งคู่ตั้งโดยรหัสลูกค้า.

อินสแตนซ์ของคลาสย่อยสามารถแยกความแตกต่างได้โดยใช้ downcast

ตอนนี้ถ้าคุณมีการออกแบบที่subclass DatabaseEngineและConnectionStringคุณสมบัติถูกนำมาใช้ใหม่ซึ่งเข้าถึงได้Configurationคุณก็มีข้อ จำกัด ที่ทำให้รู้สึกว่ามี subclass มากขึ้น

ต้องบอกว่า "การต่อต้านลวดลาย" นั้นแรงเกินไปสำหรับรสนิยมของฉัน ไม่มีอะไรผิดปกติที่นี่


0

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

โดยทั่วไปถ้าลูกค้าของ datahelperbuilder คือการได้รับ systemdatahelperbuilder ไม่มีอะไรจะทำลาย ตัวเลือกอื่นจะต้องมี systemdatahelperbuilder เป็นอินสแตนซ์แบบคงที่ของคลาส datahelperbuilder

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