ทำไมไม่สืบทอดจากรายการ <T>


1398

เมื่อวางแผนโปรแกรมของฉันฉันมักจะเริ่มต้นด้วยความคิดอย่าง:

ทีมฟุตบอลเป็นเพียงรายชื่อผู้เล่นฟุตบอล ดังนั้นฉันควรเป็นตัวแทนด้วย:

var football_team = new List<FootballPlayer>();

การเรียงลำดับของรายการนี้แสดงถึงลำดับที่ผู้เล่นแสดงไว้ในบัญชีรายชื่อ

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

ดังนั้นฉันคิดว่า:

โอเคทีมฟุตบอลก็เหมือนกับรายการของผู้เล่น แต่นอกจากนี้ยังมีชื่อ (a string) และคะแนนรวมทั้งหมด (an int) .NET ไม่มีคลาสสำหรับเก็บทีมฟุตบอลดังนั้นฉันจะสร้างคลาสของตัวเอง โครงสร้างที่มีอยู่ที่คล้ายกันและเกี่ยวข้องมากที่สุดคือList<FootballPlayer>ดังนั้นฉันจะสืบทอดจากมัน:

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

แต่มันกลับกลายเป็นว่าแนวทางในการพูดว่าคุณไม่ควรสืบทอดมาจาก List<T>ฉันสับสนอย่างละเอียดโดยแนวทางนี้สองประการ

ทำไมจะไม่ล่ะ?

เห็นได้ชัดว่ามีการเพิ่มประสิทธิภาพอย่างใดเพื่อให้ได้ประสิทธิภาพList งั้นเหรอ ฉันจะทำให้เกิดปัญหาประสิทธิภาพการทำงานถ้าฉันขยายList? จะเกิดอะไรขึ้น

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

ทำไมมันถึงสำคัญ รายการคือรายการ สิ่งที่อาจเปลี่ยนแปลงได้? ฉันอาจต้องการเปลี่ยนแปลงอะไร

และสุดท้ายถ้า Microsoft ไม่ต้องการให้ฉันรับช่วงต่อListทำไมพวกเขาถึงไม่ทำคลาสsealed?

ฉันควรจะใช้อะไรอีก

เห็นได้ชัดว่าสำหรับคอลเลกชันที่กำหนดเอง, ไมโครซอฟท์ได้จัดให้มีระดับที่ควรจะขยายแทนCollection Listแต่คลาสนี้เปลือยมากและไม่มีสิ่งที่มีประโยชน์มากมายเช่นAddRangeเช่น คำตอบของ jvitor83ให้เหตุผลด้านประสิทธิภาพสำหรับวิธีการเฉพาะนั้น แต่วิธีการนั้นช้าAddRangeไม่ดีกว่าไม่AddRange?

การสืบทอดจากCollectionเป็นวิธีการทำงานมากกว่าการสืบทอดจากListและฉันไม่เห็นประโยชน์ แน่นอนว่า Microsoft จะไม่บอกให้ฉันทำงานพิเศษโดยไม่มีเหตุผลดังนั้นฉันจึงอดไม่ได้ที่จะรู้สึกว่าฉันเข้าใจผิดบางอย่างและการรับมรดกCollectionไม่ใช่วิธีแก้ปัญหาที่เหมาะสมสำหรับปัญหาของฉัน

ผมเคยเห็นข้อเสนอแนะต่าง ๆ IListเช่นการดำเนินการ แค่ไม่. นี่คือโค้ดบรรทัดสำเร็จรูปหลายสิบบรรทัด

สุดท้ายบางคนแนะนำให้ห่อListในบางสิ่ง:

class FootballTeam 
{ 
    public List<FootballPlayer> Players; 
}

มีสองปัญหากับสิ่งนี้:

  1. มันทำให้รหัสของฉัน verbose โดยไม่จำเป็น ตอนนี้ผมต้องเรียกแทนเพียงmy_team.Players.Count my_team.Countโชคดีที่มี C # ฉันสามารถกำหนดตัวจัดทำดัชนีเพื่อให้การทำดัชนีโปร่งใสและส่งต่อวิธีการทั้งหมดของภายในList... แต่นั่นคือรหัสจำนวนมาก! ฉันจะได้อะไรจากการทำงานทั้งหมด

  2. แค่ธรรมดา ๆ ก็ไม่สมเหตุสมผล ทีมฟุตบอลไม่มีรายชื่อผู้เล่น "มี" มันเป็นรายการของผู้เล่น คุณไม่พูดว่า "John McFootballer ได้เข้าร่วมกับผู้เล่นของ SomeTeam" คุณพูดว่า "John ได้เข้าร่วม SomeTeam" คุณไม่ได้เพิ่มตัวอักษรใน "ตัวอักษรของสตริง" คุณเพิ่มตัวอักษรลงในสตริง คุณไม่เพิ่มหนังสือลงในหนังสือของห้องสมุด แต่เพิ่มหนังสือลงในห้องสมุด

ฉันรู้ว่าสิ่งที่เกิดขึ้น "ภายใต้ประทุน" อาจกล่าวได้ว่าเป็น "การเพิ่ม X เข้ากับรายการภายในของ Y" แต่นี่ดูเหมือนจะเป็นวิธีคิดที่ไม่โต้ตอบเกี่ยวกับโลก

คำถามของฉัน (สรุป)

C # วิธีที่ถูกต้องในการเป็นตัวแทนของโครงสร้างข้อมูลซึ่ง "ตรรกะ" (กล่าวคือ "เพื่อจิตใจมนุษย์") เป็นเพียงlistของที่thingsมีระฆังและนกหวีดไม่กี่?

สืบทอดมาจากที่List<T>ยอมรับไม่ได้เสมอ? ยอมรับได้เมื่อไหร่? ทำไม / ทำไมไม่ โปรแกรมเมอร์ต้องพิจารณาอะไรเมื่อตัดสินใจว่าจะสืบทอดจากList<T>หรือไม่


90
ดังนั้นคำถามของคุณคือเมื่อใดที่จะใช้การสืบทอด (Square is-a Rectangle เนื่องจากมีเหตุผลที่จะให้ Square เสมอเมื่อมีการร้องขอสี่เหลี่ยมผืนผ้าทั่วไป) และเมื่อใดที่จะใช้การจัดองค์ประกอบ (FootballTeam มีรายชื่อ FootballPlayers เพิ่มเติม คุณสมบัติอื่น ๆ ซึ่งเป็นเพียง "พื้นฐาน" ของมันเช่นชื่อ)? โปรแกรมเมอร์คนอื่นจะสับสนหรือไม่ถ้ามีที่อื่นในรหัสที่คุณส่งให้พวกเขาเป็น FootballTeam เมื่อพวกเขาคาดหวังรายการง่าย ๆ ?
เที่ยวบิน Odyssey

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

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

45
@FlightOdyssey: สมมติว่าคุณมีวิธี SquashRectangle ที่ใช้รูปสี่เหลี่ยมผืนผ้าทำให้ความสูงของมันลดลงครึ่งหนึ่งและเพิ่มความกว้างเป็นสองเท่า ตอนนี้ผ่านในรูปสี่เหลี่ยมผืนผ้าที่เกิดขึ้นเป็น 4x4 แต่เป็นรูปสี่เหลี่ยมผืนผ้าประเภทและสี่เหลี่ยมที่เป็น 4x4 อะไรคือขนาดของวัตถุหลังจาก SquashRectangle เรียกว่า? สำหรับสี่เหลี่ยมผืนผ้าอย่างชัดเจนมันคือ 2x8 หากสแควร์เป็น 2x8 แสดงว่ามันไม่ได้เป็นสี่เหลี่ยมอีกต่อไป ถ้าไม่ใช่ 2x8 การดำเนินการเดียวกันบนรูปร่างเดียวกันจะให้ผลลัพธ์ที่แตกต่าง ข้อสรุปคือสี่เหลี่ยมที่ไม่แน่นอนไม่ได้เป็นรูปสี่เหลี่ยมผืนผ้า
Eric Lippert

29
@Gangnus: คำพูดของคุณเป็นเท็จเพียง; คลาสย่อยจริงจำเป็นต้องมีฟังก์ชันการทำงานซึ่งเป็นชุดซูเปอร์คลาสของซูเปอร์คลาส stringจะต้องทำทุกอย่างobjectสามารถทำและอื่น ๆ
Eric Lippert

คำตอบ:


1565

มีคำตอบที่ดีอยู่ที่นี่ ฉันจะเพิ่มพวกเขาในประเด็นต่อไปนี้

C # วิธีที่ถูกต้องของการแสดงโครงสร้างข้อมูลซึ่ง "ตรรกะ" (กล่าวคือ "จิตใจมนุษย์") เป็นเพียงรายการของสิ่งที่มีระฆังและนกหวีดไม่กี่?

ถามผู้ที่ไม่ใช่นักเขียนโปรแกรมคอมพิวเตอร์สิบคนที่คุ้นเคยกับการมีอยู่ของฟุตบอลเพื่อเติมในช่องว่าง:

ทีมฟุตบอลเป็น _____ ชนิดใดชนิดหนึ่งโดยเฉพาะ

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

List<T>เป็นกลไก ทีมฟุตบอลเป็นวัตถุทางธุรกิจนั่นคือวัตถุที่แสดงถึงแนวคิดบางอย่างที่อยู่ในโดเมนธุรกิจของโปรแกรม อย่าผสมเหล่านั้น! ทีมฟุตบอลเป็นชนิดของทีม มันมีบัญชีรายชื่อของบัญชีรายชื่อเป็นรายชื่อของผู้เล่น บัญชีรายชื่อไม่ได้เป็นชนิดหนึ่งของรายชื่อผู้เล่น บัญชีรายชื่อคือรายชื่อผู้เล่น เพื่อให้คุณสมบัติที่เรียกว่านั่นคือRoster List<Player>และทำมันReadOnlyList<Player>ในขณะที่คุณกำลังทำอยู่เว้นแต่คุณจะเชื่อว่าทุกคนที่รู้เกี่ยวกับทีมฟุตบอลจะลบผู้เล่นออกจากบัญชีรายชื่อ

สืบทอดมาจากที่List<T>ยอมรับไม่ได้เสมอ?

ยอมรับไม่ได้กับใคร ผม? เลขที่

ยอมรับได้เมื่อไหร่?

เมื่อคุณกำลังสร้างกลไกที่ขยายList<T>กลไก

โปรแกรมเมอร์ต้องพิจารณาอะไรเมื่อตัดสินใจว่าจะสืบทอดจากList<T>หรือไม่

ฉันกำลังสร้างกลไกหรือวัตถุธุรกิจหรือไม่?

แต่นั่นเป็นรหัสจำนวนมาก! ฉันจะได้อะไรจากการทำงานทั้งหมด

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

UPDATE

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

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


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

128
@ Superbest: ดีใจที่ฉันสามารถช่วยได้! คุณมีสิทธิ์ที่จะรับฟังข้อสงสัยเหล่านั้น หากคุณไม่เข้าใจว่าทำไมคุณจึงเขียนโค้ดบางอย่างลองคิดดูหรือเขียนรหัสอื่นที่คุณเข้าใจ
Eric Lippert

48
@ Mehrdad แต่คุณลืมกฎทองของการเพิ่มประสิทธิภาพ: 1) อย่าทำมัน 2) ยังไม่ทำและ 3) อย่าทำโดยไม่ทำโปรไฟล์ประสิทธิภาพก่อนเพื่อแสดงสิ่งที่ต้องการเพิ่มประสิทธิภาพ
AJMansfield

107
@ Mehrdad: สุจริตถ้าใบสมัครของคุณต้องการให้คุณใส่ใจเกี่ยวกับภาระงานของพูดวิธีการเสมือนแล้วภาษาสมัยใหม่ใด ๆ (C #, Java, ฯลฯ ) ไม่ใช่ภาษาสำหรับคุณ ฉันมีแล็ปท็อปอยู่ที่นี่ซึ่งสามารถจำลองเกมอาร์เคดทุกเกมที่ฉันเล่นตอนเป็นเด็กพร้อมกันได้ สิ่งนี้ทำให้ฉันไม่ต้องกังวลกับระดับของการอ้อมที่นี่และที่นั่น
Eric Lippert

44
@Superbest: ตอนนี้เรามีความแน่นอนที่ "องค์ประกอบไม่ได้รับมรดก" สิ้นสเปกตรัม จรวดจะประกอบด้วยชิ้นส่วนจรวด จรวดไม่ใช่ "รายการชิ้นส่วนพิเศษ"! ฉันขอแนะนำให้คุณว่าคุณตัดมันลงไปอีก; ทำไมรายการเมื่อเทียบกับIEnumerable<T>- เป็นลำดับ ?
Eric Lippert

161

ว้าวโพสต์ของคุณมีคำถามและคะแนนมากมาย เหตุผลส่วนใหญ่ที่คุณได้รับจาก Microsoft นั้นตรงประเด็น เริ่มจากทุกอย่างเกี่ยวกับList<T>

  • List<T> จะเพิ่มประสิทธิภาพสูง การใช้งานหลักของมันคือการใช้เป็นสมาชิกส่วนตัวของวัตถุ
  • class MyList<T, TX> : List<CustomObject<T, Something<TX>> { ... }ไมโครซอฟท์ไม่ได้ปิดผนึกมันเพราะบางครั้งคุณอาจต้องการที่จะสร้างชั้นที่มีชื่อเป็นมิตรที่: var list = new MyList<int, string>();ตอนนี้มันเป็นเรื่องง่ายเหมือนการทำ
  • CA1002: อย่าเปิดเผยรายการทั่วไป : โดยทั่วไปแม้ว่าคุณวางแผนที่จะใช้แอพนี้เป็นผู้พัฒนา แต่เพียงผู้เดียวมันก็คุ้มค่าที่จะพัฒนาด้วยวิธีการเข้ารหัสที่ดี คุณยังคงได้รับอนุญาตให้เปิดเผยรายการIList<T>หากคุณต้องการให้ผู้บริโภครายใดมีรายการที่จัดทำดัชนี สิ่งนี้ให้คุณเปลี่ยนการใช้งานภายในชั้นเรียนในภายหลัง
  • Microsoft ทำCollection<T>เรื่องทั่วไปมากเพราะเป็นแนวคิดทั่วไป ... ชื่อกล่าวมันทั้งหมด มันเป็นเพียงของสะสม มีรุ่นที่แม่นยำมากขึ้นเช่นSortedCollection<T>, ObservableCollection<T>, ReadOnlyCollection<T>ฯลฯ ซึ่งแต่ละดำเนินการIList<T>แต่ไม่ได้ List<T>
  • Collection<T>อนุญาตให้สมาชิก (เช่นเพิ่มลบออก ฯลฯ ) ถูกเขียนทับเนื่องจากเป็นเสมือน List<T>ไม่.
  • ส่วนสุดท้ายของคำถามของคุณคือจุดที่ ทีมฟุตบอลเป็นมากกว่ารายชื่อผู้เล่นดังนั้นควรเป็นคลาสที่มีรายชื่อผู้เล่นนั้น คิดว่าองค์ประกอบ VS มรดก ทีมฟุตบอลมีรายชื่อผู้เล่น (รายชื่อ) มันไม่ใช่รายชื่อผู้เล่น

ถ้าฉันกำลังเขียนรหัสนี้คลาสอาจมีลักษณะเช่นนั้น:

public class FootballTeam
{
    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    // Yes. I used LINQ here. This is so I don't have to worry about
    // _roster.Length vs _roster.Count vs anything else.
    public int PlayerCount
    {
        get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}

6
" โพสต์ของคุณมีคำถามมากมาย " - ฉันก็บอกว่าฉันสับสนอย่างถี่ถ้วน ขอบคุณที่เชื่อมโยงไปยังคำถามของ @ Readonly ตอนนี้ฉันเห็นว่าส่วนใหญ่ของคำถามของฉันเป็นกรณีพิเศษของปัญหาองค์ประกอบทั่วไปกับการสืบทอด
Superbest

3
@Brian: ยกเว้นว่าคุณไม่สามารถสร้างชื่อสามัญโดยใช้นามแฝงได้ทุกประเภทต้องเป็นรูปธรรม ในตัวอย่างของฉันList<T>ขยายเป็นคลาสส่วนตัวภายในซึ่งใช้ชื่อสามัญเพื่อทำให้การใช้งานและการประกาศList<T>ง่ายขึ้น หากส่วนขยายเป็นเพียงเท่านั้นclass FootballRoster : List<FootballPlayer> { }ใช่ฉันสามารถใช้นามแฝงแทน ผมคิดว่ามันเป็นที่น่าสังเกตว่าคุณสามารถเพิ่มสมาชิกเพิ่มเติม / การทำงาน แต่จะมีข้อ จำกัด List<T>เนื่องจากคุณไม่สามารถแทนที่สมาชิกที่สำคัญของ
ของฉัน

6
หากนี่คือ Programmers.SE ฉันจะเห็นด้วยกับช่วงปัจจุบันของคำตอบการลงคะแนน แต่เนื่องจากเป็นโพสต์ SO คำตอบนี้อย่างน้อยก็พยายามตอบคำถามเฉพาะ C # (.NET) แม้ว่าจะมีความชัดเจนทั่วไป ปัญหาการออกแบบ
Mark Hurd

7
Collection<T> allows for members (i.e. Add, Remove, etc.) to be overriddenตอนนี้ว่าเป็นเหตุผลที่ดีที่จะไม่รับมรดกจากรายการ คำตอบอื่น ๆ มีปรัชญามากเกินไป
Navin

10
@my: ฉันสงสัยว่าคุณหมายถึง "ฆ่า" ของคำถามเนื่องจากนักสืบเป็นนักสืบ
batty

152
class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal;
}

โค้ดก่อนหน้าหมายถึง: กลุ่มคนจากการเล่นสตรีทฟุตบอลและพวกเขามีชื่อ สิ่งที่ต้องการ:

คนกำลังเล่นฟุตบอล

อย่างไรก็ตามรหัสนี้ (จากคำตอบของฉัน)

public class FootballTeam
{
    // A team's name
    public string TeamName; 

    // Football team rosters are generally 53 total players.
    private readonly List<T> _roster = new List<T>(53);

    public IList<T> Roster
    {
        get { return _roster; }
    }

    public int PlayerCount
    {
        get { return _roster.Count(); }
    }

    // Any additional members you want to expose/wrap.
}

หมายถึง: นี่คือทีมฟุตบอลที่มีการจัดการผู้เล่นผู้ดูแลระบบและอื่น ๆ สิ่งที่ชอบ:

ภาพแสดงสมาชิก (และคุณสมบัติอื่น ๆ ) ของทีมแมนเชสเตอร์ยูไนเต็ด

นี่คือเหตุผลที่คุณนำเสนอในรูปภาพ ...


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

3
เพิ่มประสิทธิภาพมากขึ้นprivate readonly List<T> _roster = new List<T>(44);
SiD

2
จำเป็นต้องนำเข้าSystem.Linqเนมสเปซ
Davide Cannizzo

1
สำหรับภาพ: +1
V.7

115

นี่คือตัวอย่างคลาสสิกของการแต่งเพลงและการสืบทอดมรดก

ในกรณีเฉพาะนี้:

เป็นทีมที่มีรายชื่อผู้เล่นที่มีพฤติกรรมเพิ่ม

หรือ

ทีมเป็นเป้าหมายของตัวเองหรือไม่ที่เกิดขึ้นกับรายชื่อผู้เล่น

โดยการขยายรายการคุณกำลัง จำกัด ตัวเองในหลายวิธี:

  1. คุณไม่สามารถ จำกัด การเข้าถึง (ตัวอย่างเช่นการหยุดคนเปลี่ยนบัญชีรายชื่อ) คุณได้รับรายการวิธีการทั้งหมดไม่ว่าคุณต้องการ / ต้องการทั้งหมดหรือไม่

  2. จะเกิดอะไรขึ้นถ้าคุณต้องการมีรายการสิ่งอื่นเช่นกัน ตัวอย่างเช่นทีมมีโค้ชผู้จัดการแฟนอุปกรณ์ ฯลฯ บางคนอาจมีรายชื่ออยู่ในสิทธิของตนเอง

  3. คุณ จำกัด ตัวเลือกของคุณสำหรับการสืบทอด ตัวอย่างเช่นคุณอาจต้องการสร้างวัตถุทีมทั่วไปแล้วมี BaseballTeam, FootballTeam และอื่น ๆ ที่สืบทอดมาจากสิ่งนั้น ในการรับช่วงต่อจากรายการคุณต้องทำการสืบทอดจากทีม แต่นั่นหมายความว่าทีมทุกประเภทจะต้องถูกบังคับให้มีการใช้บัญชีรายชื่อเดียวกัน

องค์ประกอบ - รวมถึงวัตถุที่ให้พฤติกรรมที่คุณต้องการภายในวัตถุของคุณ

การสืบทอด - วัตถุของคุณกลายเป็นตัวอย่างของวัตถุที่มีพฤติกรรมที่คุณต้องการ

ทั้งสองมีการใช้งานของพวกเขา แต่นี่เป็นกรณีที่ชัดเจนที่มีองค์ประกอบที่เป็นที่นิยม


1
ขยายตัวใน # 2 มันไม่สมเหตุสมผลสำหรับ List to -a List
Cruncher

อย่างแรกคำถามไม่เกี่ยวอะไรกับองค์ประกอบเทียบกับการสืบทอด คำตอบคือ OP ไม่ต้องการใช้รายการเฉพาะเจาะจงมากขึ้นดังนั้นเขาจึงไม่ควรขยาย List <> ฉันบิดเพราะคุณมีคะแนนสูงมากใน stackoverflow และควรรู้สิ่งนี้อย่างชัดเจนและผู้คนเชื่อในสิ่งที่คุณพูดดังนั้นตอนนี้มีคน 55 คนที่ upvoted และผู้ที่มีความคิดสับสนสับสนเชื่อว่าไม่ทางใดก็ทางหนึ่ง ระบบ แต่เห็นได้ชัดว่าไม่ใช่! # no-rage
Mauro Sampietro

6
@sam เขาต้องการพฤติกรรมของรายการ เขามีสองตัวเลือกเขาสามารถขยายรายการ (การสืบทอด) หรือเขาสามารถรวมรายการไว้ในวัตถุของเขา (องค์ประกอบ) บางทีคุณอาจเข้าใจผิดในส่วนของคำถามหรือคำตอบมากกว่า 55 คนผิดและใช่มั้ย :)
ทิม B

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

3
OP ไม่ได้ถามถึงองค์ประกอบ แต่กรณีการใช้งานเป็นตัวอย่างที่ชัดเจนของปัญหา X: Y ซึ่งหนึ่งใน 4 หลักการของการเขียนโปรแกรมเชิงวัตถุนั้นผิดใช้ผิดวิธีหรือไม่เข้าใจอย่างสมบูรณ์ คำตอบที่ชัดเจนยิ่งขึ้นคือการเขียนคอลเลกชันพิเศษเช่น Stack หรือ Queue (ซึ่งดูเหมือนจะไม่เหมาะกับกรณีการใช้งาน) หรือเพื่อให้เข้าใจองค์ประกอบ ทีมฟุตบอลไม่ใช่รายชื่อผู้เล่นฟุตบอล ไม่ว่า OP จะขออย่างชัดเจนหรือไม่ไม่เกี่ยวข้องโดยไม่เข้าใจ Abstraction, Encapsulation, Inheritance และ Polymorphism พวกเขาจะไม่เข้าใจคำตอบ ergo, X: Y amok
Julia McGuigan

67

ดังที่ทุกคนชี้ให้เห็นทีมของผู้เล่นไม่ใช่รายชื่อผู้เล่น ความผิดพลาดนี้เกิดขึ้นได้กับคนจำนวนมากในทุกที่บางทีอาจมีความเชี่ยวชาญในระดับต่างๆ บ่อยครั้งที่ปัญหามีความละเอียดอ่อนและบางครั้งก็แย่มากอย่างเช่นในกรณีนี้ การออกแบบดังกล่าวจะไม่ดีเพราะเหล่านี้ละเมิด Liskov ชดเชยหลักการ อินเทอร์เน็ตมีบทความที่ดีมากมายที่อธิบายแนวคิดนี้เช่นhttp://en.wikipedia.org/wiki/Liskov_substitution_principle

โดยสรุปมีกฎสองข้อที่ต้องรักษาไว้ในความสัมพันธ์หลัก / ลูกระหว่างคลาส:

  • เด็กไม่ควรมีลักษณะใดที่น้อยกว่าที่ผู้ปกครองกำหนดไว้อย่างสมบูรณ์
  • ผู้ปกครองไม่จำเป็นต้องมีลักษณะใดนอกเหนือไปจากสิ่งที่กำหนดเด็กโดยสมบูรณ์

กล่าวอีกนัยหนึ่งผู้ปกครองคือคำจำกัดความที่จำเป็นของเด็กและเด็กเป็นคำจำกัดความที่เพียงพอของผู้ปกครอง

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

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

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

ณ จุดนี้ฉันต้องการที่จะสังเกตเห็นว่าในชั้นเรียนของทีมควรจะไม่ได้ดำเนินการโดยใช้รายการ; ควรนำมาใช้โดยใช้โครงสร้างข้อมูลชุด (HashSet เป็นต้น) ในกรณีส่วนใหญ่


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

1
+1 สำหรับจุดที่ดีบางอย่าง แต่สิ่งสำคัญคือการถามว่า "โครงสร้างข้อมูลสามารถสนับสนุนทุกสิ่งที่มีประโยชน์อย่างสมเหตุสมผล (ในระยะสั้นและระยะยาว)" แทนที่จะเป็น "โครงสร้างข้อมูลมีประโยชน์มากกว่า"
Tony Delroy

1
@TonyD จุดที่ฉันเพิ่มไม่ใช่ว่าควรตรวจสอบ "ถ้าโครงสร้างข้อมูลทำมากกว่าสิ่งที่มีประโยชน์" มันคือการตรวจสอบ "ถ้าโครงสร้างข้อมูลผู้ปกครองทำสิ่งที่ไม่เกี่ยวข้องไม่มีความหมายหรือตอบโต้พฤติกรรมที่สิ่งที่คลาสเด็กอาจหมายถึง"
Satyan Raina

1
@TonyD มีปัญหากับการมีลักษณะที่ไม่เกี่ยวข้องที่มาจากผู้ปกครองเพราะมันจะล้มเหลวในการทดสอบเชิงลบในหลายกรณี โปรแกรมเมอร์สามารถขยายมนุษย์ {eat (); วิ่ง(); เขียน ();} จากลิงกอริลลา {eat (); วิ่ง(); swing ();} คิดว่าไม่มีอะไรผิดปกติกับมนุษย์ที่มีคุณสมบัติพิเศษที่สามารถแกว่งได้ และในโลกของเกมมนุษย์ของคุณก็เริ่มที่จะหลบหลีกสิ่งกีดขวางบนบกโดยการแกว่งไปบนต้นไม้ มนุษย์ที่ใช้งานได้จริงไม่ควรแกว่ง การออกแบบดังกล่าวทำให้ api เปิดกว้างสำหรับการใช้ในทางที่ผิดและสับสน
Satyan Raina

1
@TonyD ฉันไม่ได้แนะนำว่าคลาส Player ควรได้รับมาจาก HashSet เช่นกัน ฉันแนะนำให้ใช้คลาส Player ในกรณีส่วนใหญ่ควรใช้ HashSet ผ่านองค์ประกอบและนั่นคือรายละเอียดระดับการใช้งานทั้งหมดไม่ใช่ระดับการออกแบบหนึ่ง มันสามารถนำไปใช้งานได้เป็นอย่างดีโดยใช้รายการถ้ามีเหตุผลที่ถูกต้องสำหรับการนำไปใช้ ดังนั้นเพื่อตอบคำถามของคุณจำเป็นต้องมีการค้นหา O (1) โดยคีย์หรือไม่ ไม่ดังนั้นเราไม่ควรขยายผู้เล่นออกจาก HashSet ด้วย
Satyan Raina

50

จะเกิดอะไรขึ้นถ้าFootballTeamทีมสำรองพร้อมกับทีมหลัก?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
}

คุณจะสร้างแบบจำลองนั้นด้วยได้อย่างไร

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

ความสัมพันธ์อย่างชัดเจนมีและไม่ได้เป็นเป็น

หรือRetiredPlayers?

class FootballTeam
{
    List<FootballPlayer> Players { get; set; }
    List<FootballPlayer> ReservePlayers { get; set; }
    List<FootballPlayer> RetiredPlayers { get; set; }
}

ตามกฎทั่วไปหากคุณต้องการสืบทอดจากคอลเล็กชันให้ตั้งชื่อคลาส SomethingCollectionชื่อชั้น

SomethingCollectionความหมายของคุณสมเหตุสมผลหรือไม่? ทำเช่นนี้ต่อเมื่อประเภทของคุณเป็นชุดSomethingคอลเลกชันของ

ในกรณีที่FootballTeamมันไม่ถูกต้อง เป็นมากกว่าTeam CollectionTeamสามารถมีโค้ชผู้ฝึกสอน ฯลฯ เป็นคำตอบอื่น ๆ ได้ชี้ให้เห็น

FootballCollection ฟังดูเหมือนคอลเลคชันฟุตบอลหรือคอลเล็กชั่นชุดฟุตบอล TeamCollectionชุดของทีม

FootballPlayerCollection ดูเหมือนคอลเลกชันของผู้เล่นซึ่งจะเป็นชื่อที่ถูกต้องสำหรับคลาสที่สืบทอดมา List<FootballPlayer>หากคุณต้องการทำเช่นนั้น

List<FootballPlayer>เป็นประเภทที่ดีอย่างแท้จริงที่จะจัดการกับ อาจจะIList<FootballPlayer>ถ้าคุณคืนจากวิธีการ

สรุป

ถามตัวเอง

  1. คือ ? หรือมี ?XY XY

  2. ชื่อชั้นเรียนของฉันแปลว่าอะไร


ถ้าชนิดของผู้เล่นทุกคนจะแบ่งเป็นอยู่อย่างใดอย่างหนึ่งDefensivePlayers, OffensivePlayersหรือOtherPlayersมันอาจจะมีประโยชน์ถูกต้องตามกฎหมายที่จะมีชนิดที่สามารถนำมาใช้โดยรหัสซึ่งคาดว่าเป็นList<Player>แต่สมาชิกยังรวมถึงDefensivePlayers, OffsensivePlayersหรือSpecialPlayersประเภทIList<DefensivePlayer>, และIList<OffensivePlayer> IList<Player>หนึ่งสามารถใช้วัตถุแยกต่างหากเพื่อแคชรายการแยก แต่ encapsulating พวกเขาภายในวัตถุเดียวกันเป็นรายการหลักจะดูสะอาด [ใช้การตรวจสอบความถูกต้องของรายการแจงนับ ...
supercat

... เพื่อเป็นข้อเท็จจริงที่ว่ารายการหลักมีการเปลี่ยนแปลงและรายการย่อยจะต้องถูกสร้างใหม่เมื่อเข้าถึงครั้งต่อไป]
supercat

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

38

ออกแบบ> การนำไปใช้

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

วัตถุคือชุดของข้อมูลและพฤติกรรม

ดังนั้นคำถามแรกของคุณควรเป็น:

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

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

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

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

พิจารณารายละเอียดการออกแบบ

ดูรายการ <T> และ IList <T> บน MSDN หรือ Visual Studio ดูวิธีการและคุณสมบัติที่พวกเขาเปิดเผย วิธีการเหล่านี้ดูเหมือนกับทุกสิ่งที่คนต้องการจะทำกับทีมฟุตบอลในมุมมองของคุณหรือไม่?

footballTeam.Reverse () เหมาะสมกับคุณหรือไม่? footballTeam.ConvertAll <Tutput> () ดูเหมือนกับสิ่งที่คุณต้องการหรือไม่?

นี่ไม่ใช่คำถามที่หลอกลวง คำตอบอาจเป็น "ใช่" อย่างแท้จริง หากคุณใช้ / สืบทอดรายการ <Player> หรือ IList <Player> แสดงว่าคุณติดอยู่กับสิ่งนั้น ถ้ามันเหมาะสำหรับรุ่นของคุณให้ทำ

หากคุณตัดสินใจว่าใช่นั่นสมเหตุสมผลและคุณต้องการให้วัตถุของคุณสามารถรักษาได้ด้วยการรวบรวม / รายชื่อผู้เล่น (พฤติกรรม) ดังนั้นคุณจึงต้องการนำ ICollection หรือ IList ไปใช้ notionally:

class FootballTeam : ... ICollection<Player>
{
    ...
}

หากคุณต้องการให้วัตถุของคุณมีคอลเลกชัน / รายชื่อผู้เล่น (ข้อมูล) และคุณต้องการให้คอลเลกชันหรือรายการเป็นทรัพย์สินหรือสมาชิก notionally:

class FootballTeam ...
{
    public ICollection<Player> Players { get { ... } }
}

คุณอาจรู้สึกว่าคุณต้องการให้ผู้คนสามารถระบุชุดของผู้เล่นได้เท่านั้นนับจำนวนเพิ่มหรือลบออก IEnumerable <Player> เป็นตัวเลือกที่ถูกต้องสมบูรณ์ในการพิจารณา

คุณอาจรู้สึกว่าไม่มีส่วนต่อประสานเหล่านี้มีประโยชน์ในแบบจำลองของคุณเลย สิ่งนี้มีโอกาสน้อยกว่า (IENumerable <T> มีประโยชน์ในหลาย ๆ สถานการณ์) แต่ก็ยังเป็นไปได้

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

ไปที่การดำเนินการ

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

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

การทดลองทางความคิด

ตัวอย่างเทียม: ดังที่คนอื่น ๆ พูดถึงทีมไม่ได้ "เพียงแค่" กลุ่มผู้เล่น คุณเก็บคะแนนการแข่งขันให้กับทีมหรือไม่? ทีมสามารถแลกเปลี่ยนได้กับสโมสรในแบบของคุณหรือไม่? ถ้าเป็นเช่นนั้นและหากทีมของคุณเป็นกลุ่มผู้เล่นอาจเป็นกลุ่มของเจ้าหน้าที่และ / หรือกลุ่มคะแนน จากนั้นคุณก็จบลงด้วย:

class FootballTeam : ... ICollection<Player>, 
                         ICollection<StaffMember>,
                         ICollection<Score>
{
    ....
}

การออกแบบแม้ในจุดนี้ใน C # คุณจะไม่สามารถใช้สิ่งเหล่านี้ได้โดยการสืบทอดจาก List <T> ต่อไปเนื่องจาก C # "เท่านั้น" สนับสนุนการสืบทอดเดี่ยว (หากคุณเคยลองผิดลองถูกใน C ++ คุณอาจคิดว่านี่เป็นสิ่งที่ดี) การใช้คอลเลกชันหนึ่งผ่านการสืบทอดและอีกรายการหนึ่งผ่านการแต่งเพลงมีแนวโน้มที่จะรู้สึกสกปรก และคุณสมบัติเช่น Count ทำให้ผู้ใช้เกิดความสับสนเว้นแต่ว่าคุณใช้งาน ILIst <Player> .Count และ IList <StaffMember> .Count ฯลฯ อย่างชัดเจนแล้วพวกเขาก็เจ็บปวดมากกว่าที่จะสับสน คุณสามารถดูว่าสิ่งนี้จะไป; ความรู้สึกในขณะที่คิดถนนสายนี้อาจบอกคุณได้ว่ารู้สึกผิดที่จะมุ่งหน้าไปในทิศทางนี้ (และไม่ถูกต้องหรือผิดเพื่อนร่วมงานของคุณอาจจะถ้าคุณใช้วิธีนี้!)

คำตอบสั้น ๆ (ช้าไป)

คำแนะนำเกี่ยวกับการไม่สืบทอดจากคลาสคอลเล็กชันไม่เฉพาะ C # คุณจะพบได้ในหลายภาษาโปรแกรม จะได้รับปัญญาไม่ใช่กฎหมาย เหตุผลหนึ่งก็คือในการประกอบการปฏิบัติมักจะถูกมองว่าเป็นมรดกในแง่ของความเข้าใจความสามารถในการใช้งานและการบำรุงรักษา เป็นเรื่องปกติมากขึ้นกับโลกแห่งความเป็นจริง / โดเมนวัตถุในการค้นหาความสัมพันธ์ "Hasa" ที่เป็นประโยชน์และสอดคล้องกันกว่าความสัมพันธ์ "isa" ที่เป็นประโยชน์และสอดคล้องกันเว้นแต่ว่าคุณจะอยู่ลึกในนามธรรมโดยเฉพาะอย่างยิ่งเมื่อเวลาผ่านไป การเปลี่ยนแปลง สิ่งนี้ไม่ควรทำให้คุณต้องแยกออกจากคลาสการรวบรวม แต่มันอาจจะเป็นการชี้นำ


34

ก่อนอื่นมันเกี่ยวกับการใช้งาน ถ้าคุณใช้การสืบทอดTeamคลาสจะแสดงพฤติกรรม (เมธอด) ที่ออกแบบมาสำหรับการจัดการวัตถุ ตัวอย่างเช่นAsReadOnly()หรือCopyTo(obj)วิธีการที่เหมาะสมสำหรับวัตถุทีม แทนที่จะเป็นAddRange(items)วิธีที่คุณอาจต้องการAddPlayers(players)วิธีอธิบายเพิ่มเติม

หากคุณต้องการใช้ LINQ การใช้อินเทอร์เฟซทั่วไปเช่นICollection<T>หรือIEnumerable<T>จะทำให้รู้สึกมากกว่า

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


30

ทีมฟุตบอลไม่ใช่รายชื่อผู้เล่นฟุตบอล ทีมฟุตบอลประกอบด้วยรายชื่อผู้เล่นฟุตบอล!

นี่เป็นเหตุผลผิดปกติ:

class FootballTeam : List<FootballPlayer> 
{ 
    public string TeamName; 
    public int RunningTotal 
}

และนี่ถูกต้อง:

class FootballTeam 
{ 
    public List<FootballPlayer> players
    public string TeamName; 
    public int RunningTotal 
}

19
นี้ไม่ได้อธิบายว่าทำไม OP รู้ว่าโปรแกรมเมอร์คนอื่น ๆ ส่วนใหญ่รู้สึกแบบนี้เขาแค่ไม่เข้าใจว่าทำไมมันถึงสำคัญ นี่เป็นเพียงการให้ข้อมูลที่มีอยู่ในคำถามเท่านั้น
บริการ

2
นี่เป็นสิ่งแรกที่ฉันคิดเช่นกันว่าข้อมูลของเขาเป็นแบบอย่างที่ไม่ถูกต้อง ดังนั้นเหตุผลคือรูปแบบข้อมูลของคุณไม่ถูกต้อง
rball

3
ทำไมไม่สืบทอดจากรายการ เพราะเขาไม่ต้องการรายการที่เฉพาะเจาะจงมากขึ้น
Mauro Sampietro

2
ฉันไม่คิดว่าตัวอย่างที่สองนั้นถูกต้อง คุณกำลังเปิดเผยสถานะและกำลังทำลาย encapsulation รัฐควรถูกซ่อนไว้ / ภายในวัตถุและวิธีการเปลี่ยนแปลงสถานะนี้ควรเป็นวิธีสาธารณะ วิธีการใดเป็นสิ่งที่ต้องพิจารณาจากความต้องการทางธุรกิจอย่างแน่นอน แต่ควรเป็น "ต้องการสิ่งนี้ในตอนนี้" -basis ไม่ใช่ "อาจเป็นประโยชน์ในบางครั้งที่ฉันไม่รู้" -basis รักษา API ของคุณให้แน่นและคุณจะประหยัดเวลาและความพยายาม
sara

1
การห่อหุ้มไม่ใช่ประเด็นของคำถาม ฉันมุ่งเน้นที่จะไม่ขยายประเภทหากคุณไม่ต้องการให้ประเภทนั้นทำงานในลักษณะที่เฉพาะเจาะจงมากขึ้น ฟิลด์เหล่านั้นควรเป็น autoproperties ใน c # และรับ / กำหนดวิธีการในภาษาอื่น ๆ แต่ที่นี่ในบริบทนี้ซึ่งฟุ่มเฟือยโดยสิ้นเชิง
Mauro Sampietro

28

มันขึ้นอยู่กับบริบท

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

ความหมายที่ไม่ชัดเจน

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

ชั้นเรียนมีการขยาย

เมื่อคุณใช้คลาสที่มีสมาชิกเพียงคนเดียว (เช่นIList activePlayers) คุณสามารถใช้ชื่อของสมาชิก (และความคิดเห็นเพิ่มเติม) เพื่อทำให้บริบทชัดเจน เมื่อมีบริบทเพิ่มเติมคุณเพียงแค่เพิ่มสมาชิกเพิ่มเติม

ชั้นเรียนมีความซับซ้อนมากขึ้น

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

บทสรุป / ข้อควรพิจารณา

เมื่อคุณมีบริบทที่เฉพาะเจาะจงมันก็โอเคที่จะพิจารณาทีมเป็นรายชื่อผู้เล่น ตัวอย่างเช่นภายในเมธอดมันสมบูรณ์ที่จะเขียน:

IList<Player> footballTeam = ...

เมื่อใช้ F # คุณสามารถสร้างตัวย่อของประเภทได้:

type FootballTeam = IList<Player>

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


27

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

เมื่อฉันต้องการเป็นตัวแทนของทีมฟุตบอลฉันเข้าใจว่ามันเป็นชื่อ ไลค์: "The Eagles"

string team = new string();

หลังจากนั้นฉันก็ตระหนักว่าทีมมีผู้เล่นด้วย

เหตุใดฉันจึงไม่ขยายประเภทสตริงเพื่อให้มีรายชื่อผู้เล่นอยู่ด้วย

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

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


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

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

20

เพียงเพราะฉันคิดว่าคำตอบอื่น ๆ ค่อนข้างจะออกนอกลู่นอกทางว่าทีมฟุตบอล "is-a" List<FootballPlayer>หรือ "has-a" List<FootballPlayer>ซึ่งจริงๆแล้วไม่ได้ตอบคำถามนี้ตามที่เขียนไว้

OP ส่วนใหญ่ขอชี้แจงเกี่ยวกับแนวทางการสืบทอดจากList<T>:

แนวทางบอกว่าคุณไม่ควรรับช่วงList<T>ต่อ ทำไมจะไม่ล่ะ?

เพราะList<T>ไม่มีวิธีการเสมือน นี่เป็นปัญหาที่น้อยกว่าในรหัสของคุณเนื่องจากคุณสามารถเปลี่ยนการใช้งานด้วยความเจ็บปวดเล็กน้อย แต่อาจเป็นข้อตกลงที่ใหญ่กว่ามากใน API สาธารณะ

API สาธารณะคืออะไรและทำไมฉันจึงควรใส่ใจ

API สาธารณะเป็นส่วนต่อประสานที่คุณเปิดเผยแก่โปรแกรมเมอร์บุคคลที่สาม คิดว่ารหัสกรอบ และจำได้ว่าแนวทางที่อ้างอิงคือ ". NET Framework Design Guidelines" และไม่ใช่ ". NET Application Design Guidelines" มีความแตกต่างและ - โดยทั่วไปการพูด - การออกแบบ API สาธารณะนั้นเข้มงวดมากขึ้น

หากโครงการปัจจุบันของฉันไม่ได้และไม่น่าจะมี API สาธารณะนี้ฉันจะเพิกเฉยต่อแนวทางนี้ได้อย่างปลอดภัยหรือไม่? ถ้าฉันสืบทอดจาก List และปรากฎว่าฉันต้องการ API สาธารณะฉันจะมีปัญหาอะไรบ้าง

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

หากคุณเพิ่ม API สาธารณะในอนาคตคุณจะต้องสรุป API ของคุณจากการติดตั้ง (โดยไม่เปิดเผยList<T>โดยตรง) หรือละเมิดหลักเกณฑ์ด้วยความเจ็บปวดที่อาจเกิดขึ้นในอนาคต

ทำไมมันถึงสำคัญ รายการคือรายการ สิ่งที่อาจเปลี่ยนแปลงได้? ฉันอาจต้องการเปลี่ยนแปลงอะไร

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

 class FootballTeam : List<FootballPlayer> {
     override void Add(FootballPlayer player) {
        if (this.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
 }

อ่า ... แต่คุณทำไม่ได้override Addเพราะไม่ใช่virtual(ด้วยเหตุผลด้านประสิทธิภาพ)

หากคุณอยู่ในแอปพลิเคชัน (ซึ่งโดยทั่วไปหมายถึงว่าคุณและผู้โทรทั้งหมดของคุณถูกรวบรวมเข้าด้วยกัน) จากนั้นคุณสามารถเปลี่ยนเป็นการใช้IList<T>และแก้ไขข้อผิดพลาดในการคอมไพล์ได้:

 class FootballTeam : IList<FootballPlayer> {
     private List<FootballPlayer> Players { get; set; }

     override void Add(FootballPlayer player) {
        if (this.Players.Sum(p => p.Salary) + player.Salary > SALARY_CAP)) {
          throw new InvalidOperationException("Would exceed salary cap!");
        }
     }
     /* boiler plate for rest of IList */
 }

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

TL; DR - หลักเกณฑ์สำหรับ API สาธารณะ สำหรับ API ส่วนตัวให้ทำในสิ่งที่คุณต้องการ


18

อนุญาตให้คนพูด

myTeam.subList(3, 5);

ทำให้รู้สึกอะไรเลย? ถ้าไม่เช่นนั้นก็ไม่ควรเป็นรายการ


3
อาจเป็นได้ถ้าคุณเรียกมันว่าmyTeam.subTeam(3, 5);
Sam Leach

3
@ SamLeach สมมติว่าเป็นเรื่องจริงจากนั้นคุณจะยังคงต้องการองค์ประกอบไม่ใช่มรดก ในขณะที่subListจะไม่กลับมาTeamอีกต่อไป
Cruncher

1
สิ่งนี้ทำให้ฉันมั่นใจมากกว่าคำตอบอื่น ๆ +1
FireCubez

16

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

มันทำให้รหัสของฉัน verbose โดยไม่จำเป็น ตอนนี้ฉันต้องโทร my_team.Players.Count แทน my_team.Count โชคดีที่มี C # ฉันสามารถกำหนดตัวจัดทำดัชนีเพื่อให้การทำดัชนีโปร่งใสและส่งต่อวิธีการทั้งหมดของรายการภายใน ... แต่นั่นเป็นรหัสจำนวนมาก! ฉันจะได้อะไรจากการทำงานทั้งหมด

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

แค่ธรรมดา ๆ ก็ไม่สมเหตุสมผล ทีมฟุตบอลไม่มีรายชื่อผู้เล่น "มี" มันเป็นรายการของผู้เล่น คุณไม่พูดว่า "John McFootballer ได้เข้าร่วมกับผู้เล่นของ SomeTeam" คุณพูดว่า "John ได้เข้าร่วม SomeTeam" คุณไม่ได้เพิ่มตัวอักษรใน "ตัวอักษรของสตริง" คุณเพิ่มตัวอักษรลงในสตริง คุณไม่เพิ่มหนังสือลงในหนังสือของห้องสมุด แต่เพิ่มหนังสือลงในห้องสมุด

แน่นอน :) คุณจะพูด footballTeam.Add (john) ไม่ใช่ footballTeam.List.Add (john) รายการภายในจะไม่ปรากฏให้เห็น


1
OP ต้องการเข้าใจวิธี MODEL REALITY แต่จากนั้นกำหนดทีมฟุตบอลเป็นรายชื่อผู้เล่นฟุตบอล (ซึ่งเป็นแนวคิดที่ผิด) ปัญหานี้เป็นปัญหา. ข้อโต้แย้งอื่น ๆ ทำให้เขาเข้าใจผิดในความคิดของฉัน
Mauro Sampietro

3
ฉันไม่เห็นด้วยที่รายชื่อผู้เล่นผิดแนวคิด ไม่จำเป็น. อย่างที่คนอื่นเขียนไว้ในหน้านี้ "ทุกรุ่นผิด แต่มีประโยชน์"
xpmatteo

แบบจำลองที่ถูกต้องนั้นยากที่จะได้รับเมื่อทำเสร็จแล้วพวกมันจะเป็นประโยชน์ หากแบบจำลองสามารถนำไปใช้ในทางที่ผิดมันเป็นแนวคิดที่ผิด stackoverflow.com/a/21706411/711061
Mauro Sampietro

15

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

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

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

สมมติว่าคุณต้องการแยกความแตกต่างเมื่อผู้เล่นออกจากโดยรู้ว่าพวกเขาเกษียณลาออกหรือถูกไล่ออก คุณสามารถใช้RemovePlayerเมธอดที่รับอินพุต enum ที่เหมาะสม แต่ด้วยการสืบทอดจากListคุณจะไม่สามารถป้องกันการเข้าถึงโดยตรงไปRemove, และแม้กระทั่งRemoveAll Clearเป็นผลให้คุณได้จริงdisempoweredของคุณFootballTeamระดับ


ความคิดเพิ่มเติมเกี่ยวกับการห่อหุ้ม ... คุณแจ้งข้อกังวลดังต่อไปนี้:

มันทำให้รหัสของฉัน verbose โดยไม่จำเป็น ตอนนี้ฉันต้องโทร my_team.Players.Count แทน my_team.Count

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

คุณพูดต่อไปว่า:

แค่ธรรมดา ๆ ก็ไม่สมเหตุสมผล ทีมฟุตบอลไม่มีรายชื่อผู้เล่น "มี" มันเป็นรายการของผู้เล่น คุณไม่พูดว่า "John McFootballer ได้เข้าร่วมกับผู้เล่นของ SomeTeam" คุณพูดว่า "John ได้เข้าร่วม SomeTeam"

คุณผิดเกี่ยวกับบิตแรก: วางคำว่า 'รายการ' และเป็นที่ชัดเจนว่าทีมมีผู้เล่น
อย่างไรก็ตามคุณตีเล็บที่หัวด้วยที่สอง ateam.Players.Add(...)คุณไม่ต้องการให้ลูกค้าโทร ateam.AddPlayer(...)คุณไม่ต้องการให้พวกเขาเรียกร้อง และการดำเนินการของคุณจะเรียกPlayers.Add(...)ภายใน


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


12

วิธีC # ที่ถูกต้องในการแสดงโครงสร้างข้อมูลคืออะไร...

Remeber "ทุกรุ่นผิด แต่มีประโยชน์" - George EP Box

ไม่มี "วิธีที่ถูกต้อง" มีเพียงวิธีที่มีประโยชน์

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

- แก้ไขแล้ว

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

- ฉบับที่ 2

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

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

ขอบคุณ @kai ที่ช่วยฉันคิดอย่างแม่นยำมากขึ้นเกี่ยวกับคำตอบ


คำตอบที่ดีที่สุดของฉันคือ ... มันขึ้นอยู่กับ การสืบทอดจากรายการจะทำให้ลูกค้าของคลาสนี้มีวิธีการที่ไม่ควรเปิดเผยเนื่องจากขั้นต้นเนื่องจาก FootballTeam ดูเหมือนเป็นองค์กรธุรกิจ ฉันไม่รู้ว่าฉันควรแก้ไขคำตอบนี้หรือโพสต์อื่นความคิดใด?
ArturoTena

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

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

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

11

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

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


9

ความลับสกปรกของฉัน: ฉันไม่สนใจสิ่งที่ผู้คนพูดและฉันทำ .NET Framework แพร่กระจายด้วย "XxxxCollection" (UIElementCollection ด้านบนของตัวอย่างหัวของฉัน)

ดังนั้นสิ่งที่หยุดฉันพูดว่า:

team.Players.ByName("Nicolas")

เมื่อฉันพบว่าดีกว่า

team.ByName("Nicolas")

นอกจากนี้ PlayerCollection ของฉันอาจถูกใช้โดยคลาสอื่นเช่น "Club" โดยไม่มีการทำซ้ำรหัสใด ๆ

club.Players.ByName("Nicolas")

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

team.Players.ByName("Nicolas") 

หรือ

team.ByName("Nicolas")

จริงๆ. คุณมีข้อสงสัยหรือไม่? ตอนนี้คุณอาจต้องเล่นกับข้อ จำกัด ทางเทคนิคอื่น ๆ ที่ทำให้คุณไม่ต้องใช้ List <T> ในกรณีที่ใช้งานจริง แต่อย่าเพิ่มข้อ จำกัด ที่ไม่ควรมีอยู่ หาก Microsoft ไม่ได้จัดทำเอกสารเหตุผลว่าด้วยเหตุใดจึงเป็น "แนวปฏิบัติที่ดีที่สุด" มาจากที่ไหนเลยแน่นอน


9
ในขณะที่มีความกล้าหาญและมีความคิดริเริ่มที่จะท้าทายภูมิปัญญาที่ยอมรับได้ตามความเหมาะสมฉันคิดว่าควรเข้าใจก่อนว่าทำไมภูมิปัญญาที่ยอมรับจึงได้รับการยอมรับตั้งแต่แรกก่อนที่จะเริ่มดำเนินการท้าทาย -1 เพราะ "ไม่สนใจแนวทางนั้น!" ไม่ใช่คำตอบที่ดีสำหรับ "ทำไมหลักเกณฑ์นี้มีอยู่จริง" อนึ่งชุมชนไม่เพียง "ตำหนิฉัน" ในกรณีนี้ แต่ให้คำอธิบายที่น่าพอใจที่ประสบความสำเร็จในการโน้มน้าวฉัน
Superbest

3
เป็นจริงเมื่อไม่มีการอธิบายวิธีการเดียวของคุณคือผลักขอบเขตและทดสอบโดยไม่กลัวการละเมิด ตอนนี้ ถามตัวเองแม้ว่า List <T> นั้นไม่ใช่ความคิดที่ดี มันจะเจ็บปวดแค่ไหนในการเปลี่ยนการสืบทอดจาก List <T> เป็น Collection <T> คำตอบ: ใช้เวลาน้อยกว่าการโพสต์ในฟอรัมไม่ว่าความยาวของโค้ดของคุณจะเป็นอย่างไรด้วยเครื่องมือการรีแฟคเตอร์ ตอนนี้มันเป็นคำถามที่ดี แต่ไม่ใช่คำถามเชิงปฏิบัติ
Nicolas Dorier

เพื่อชี้แจง: แน่นอนฉันไม่ได้รอให้พรของ SO ที่จะสืบทอดจากสิ่งที่ฉันต้องการ แต่ฉันต้องการ แต่ประเด็นของคำถามของฉันคือการเข้าใจการพิจารณาที่ควรไปสู่การตัดสินใจครั้งนี้และทำไมโปรแกรมเมอร์ที่มีประสบการณ์จำนวนมากดูเหมือนจะมี ตัดสินใจตรงกันข้ามกับสิ่งที่ฉันทำ Collection<T>ไม่เหมือนกันList<T>ดังนั้นจึงอาจต้องใช้งานค่อนข้างมากในการตรวจสอบในโครงการขนาดใหญ่
Superbest

2
team.ByName("Nicolas")หมายความว่า"Nicolas"เป็นชื่อของทีม
Sam Leach

1
"อย่าเพิ่มข้อ จำกัด ที่ไม่ควรมีอยู่" Au contraire อย่าเปิดเผยสิ่งที่คุณไม่มีเหตุผลที่ดีที่จะเปิดเผย นี่ไม่ใช่ความพิถีพิถันหรือความกระตือรือร้นแบบตาบอดและไม่เป็นเทรนด์แฟชั่นการออกแบบล่าสุด มันคือการออกแบบเชิงวัตถุพื้นฐาน 101 ระดับเริ่มต้น ไม่ใช่ความลับว่าทำไม "กฎ" นี้มีอยู่มันเป็นผลมาจากประสบการณ์หลายสิบปี
sara

8

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


ในการหวนกลับหลังจากคำอธิบายของ @EricLippert และคนอื่น ๆ คุณได้รับคำตอบที่ยอดเยี่ยมสำหรับส่วน API ของคำถามของฉัน - นอกจากสิ่งที่คุณพูดถ้าฉันทำclass FootballTeam : List<FootballPlayers>ผู้ใช้ในชั้นเรียนของฉันจะสามารถบอกฉันได้ ' ได้รับมรดกมาจากList<T>โดยใช้การสะท้อนเห็นList<T>วิธีการที่ไม่ได้ทำให้ความรู้สึกของทีมฟุตบอลและความสามารถในการใช้FootballTeamเข้าList<T>ดังนั้นฉันจะเป็นรายละเอียดการดำเนินการเปิดเผยให้กับลูกค้า (ไม่จำเป็น)
Superbest

7

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

ฉันมักจะเห็นผู้คนจำนวนมากทำงานพิเศษมากมายเนื่องจากกฎ FxCop กล่าวเช่นนั้นหรือบล็อกของใครบางคนบอกว่ามันเป็นการปฏิบัติที่ "ไม่ดี" หลายครั้งนี้จะเปลี่ยนรหัสในการออกแบบรูปแบบแปลกประหลาด palooza ขณะที่มีจำนวนมากของแนวทางรักษามันเป็นแนวทางที่สามารถมีข้อยกเว้น


4

แม้ว่าฉันจะไม่มีการเปรียบเทียบที่ซับซ้อนเหมือนที่คำตอบส่วนใหญ่ทำ แต่ฉันต้องการแบ่งปันวิธีการของฉันในการจัดการสถานการณ์นี้ ด้วยการขยายIEnumerable<T>คุณสามารถอนุญาตให้Teamคลาสของคุณรองรับส่วนขยายการค้นหาของ Linq โดยไม่เปิดเผยวิธีและคุณสมบัติทั้งหมดของList<T>สาธารณะ

class Team : IEnumerable<Player>
{
    private readonly List<Player> playerList;

    public Team()
    {
        playerList = new List<Player>();
    }

    public Enumerator GetEnumerator()
    {
        return playerList.GetEnumerator();
    }

    ...
}

class Player
{
    ...
}

3

หากผู้ใช้คลาสของคุณต้องการวิธีการและคุณสมบัติทั้งหมด ** รายการมีคุณควรได้รับคลาสของคุณจากมัน หากพวกเขาไม่ต้องการพวกเขาให้แนบรายชื่อและทำ wrappers สำหรับวิธีที่ผู้ใช้ชั้นเรียนของคุณต้องการ

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

สำหรับแอพขนาดเล็กคุณอาจลองเลือกภาษาอื่นที่เข้มงวดน้อยกว่า Ruby, JavaScript - สิ่งใดก็ตามที่อนุญาตให้คุณเขียนโค้ดน้อยลง


3

ฉันแค่อยากจะเพิ่มว่าเบอร์ทรานด์เมเยอร์นักประดิษฐ์ของไอเฟลและการออกแบบตามสัญญาจะได้Teamรับมรดกมาจากที่List<Player>ไม่มีเปลือกตา

ในหนังสือของเขาการก่อสร้างซอฟต์แวร์เชิงวัตถุเขากล่าวถึงการใช้งานระบบ GUI ที่หน้าต่างสี่เหลี่ยมสามารถมีหน้าต่างลูก เขาเพียงได้Windowรับมรดกจากทั้งสองRectangleและTree<Window>เพื่อนำมาใช้ใหม่

อย่างไรก็ตาม C # ไม่ใช่ Eiffel หลังสนับสนุนมรดกหลายและเปลี่ยนชื่อของคุณสมบัติ ใน C # เมื่อคุณ subclass คุณรับทั้งอินเตอร์เฟสและการดำเนินการ คุณสามารถแทนที่การใช้งานได้ แต่อนุสัญญาการโทรจะถูกคัดลอกโดยตรงจากซุปเปอร์คลาส ในไอเฟล แต่คุณสามารถปรับเปลี่ยนชื่อของวิธีการสาธารณะเพื่อให้คุณสามารถเปลี่ยนชื่อAddและRemoveไปHireและในของคุณFire Teamหากอินสแตนซ์ของTeamupcast กลับไปList<Player>ที่ผู้โทรจะใช้AddและRemoveแก้ไข แต่วิธีการเสมือนของคุณHireและFireจะถูกเรียก


1
ที่นี่ปัญหาไม่ได้มีหลายมรดกมิกซ์อิน (ฯลฯ .. ) หรือวิธีการจัดการกับพวกเขา ในภาษาใด ๆ แม้ในภาษาของมนุษย์ทีมไม่ได้เป็นรายชื่อของคน แต่ประกอบด้วยรายชื่อของคน นี่เป็นแนวคิดที่เป็นนามธรรม หาก Bertrand Meyer หรือใครก็ตามบริหารทีมโดย List-Subclassing List ทำผิด คุณควรคลาสย่อยรายการถ้าคุณต้องการรายการที่เฉพาะเจาะจงมากขึ้น หวังว่าคุณจะเห็นด้วย
Mauro Sampietro

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

2

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

คุณสามารถพิจารณาคลาส PlayerCollection ที่ชอบ StringCollection แบบพิเศษมีสิ่งอำนวยความสะดวกอื่น ๆ เช่นการตรวจสอบความถูกต้องและการตรวจสอบก่อนที่วัตถุจะถูกเพิ่มหรือลบออกจากร้านค้าภายใน

บางทีแนวคิดของนักพนัน PlayerCollection นั้นเหมาะสมกับแนวทางที่คุณต้องการ?

public class PlayerCollection : Collection<Player> 
{ 
}

และจากนั้น FootballTeam สามารถมีลักษณะเช่นนี้:

public class FootballTeam 
{ 
    public string Name { get; set; }
    public string Location { get; set; }

    public ManagementCollection Management { get; protected set; } = new ManagementCollection();

    public CoachingCollection CoachingCrew { get; protected set; } = new CoachingCollection();

    public PlayerCollection Players { get; protected set; } = new PlayerCollection();
}

2

ชอบอินเทอร์เฟซมากกว่าคลาส

คลาสควรหลีกเลี่ยงจากคลาสและใช้อินเตอร์เฟสน้อยที่สุดแทน

การแบ่งมรดก Encapsulation

มาจากการแบ่งคลาสencapsulation :

  • เปิดเผยรายละเอียดภายในเกี่ยวกับวิธีการใช้คอลเลกชันของคุณ
  • ประกาศอินเตอร์เฟส (ชุดของฟังก์ชันสาธารณะและคุณสมบัติ) ที่อาจไม่เหมาะสม

เหนือสิ่งอื่นใดสิ่งนี้ทำให้การปรับโครงสร้างรหัสของคุณยากขึ้น

คลาสเป็นรายละเอียดการนำไปปฏิบัติ

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

แนวคิดข้อเท็จจริงที่ว่าSystem.Listชนิดข้อมูลเรียกว่า "รายการ" เป็นบิตของปลาเฮอริ่งแดง A System.List<T>เป็นคอลเล็กชันที่สั่งซื้อไม่แน่นอนที่รองรับการดำเนินงาน O (1) ที่ถูกตัดจำหน่ายสำหรับการเพิ่มการแทรกและการลบองค์ประกอบและการดำเนินการ O (1) สำหรับการดึงจำนวนองค์ประกอบหรือการรับและการตั้งค่าองค์ประกอบตามดัชนี

อินเทอร์เฟซที่เล็กลงยิ่งยืดหยุ่นรหัสมากขึ้น

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

วิธีการเลือกอินเทอร์เฟซ

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

บางคำถามที่สามารถช่วยแนะนำขั้นตอนนี้:

  • ฉันต้องมีการนับหรือไม่ หากไม่พิจารณาดำเนินการIEnumerable<T>
  • คอลเลกชันนี้จะเปลี่ยนไปหลังจากที่เริ่มต้นหรือไม่ IReadonlyList<T>ถ้าไม่ได้พิจารณา
  • เป็นสิ่งสำคัญที่ฉันสามารถเข้าถึงรายการตามดัชนีหรือไม่ พิจารณาICollection<T>
  • ลำดับที่ฉันเพิ่มรายการในคอลเลกชันมีความสำคัญหรือไม่ บางทีมันอาจจะเป็นISet<T>?
  • IList<T>หากคุณต้องการแน่นอนสิ่งเหล่านี้แล้วไปข้างหน้าและการดำเนินการ

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

ด้วยการใช้วิธีการนี้คุณจะพบว่ารหัสนั้นง่ายต่อการอ่านทำซ้ำและนำมาใช้ใหม่

หมายเหตุเกี่ยวกับการหลีกเลี่ยงหม้อไอน้ำ

การนำอินเตอร์เฟสไปใช้ใน IDE สมัยใหม่ควรเป็นเรื่องง่าย คลิกขวาและเลือก "Implement Interface" จากนั้นส่งต่อการนำไปใช้งานทั้งหมดไปยังคลาสสมาชิกหากคุณต้องการ

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

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


2

ปัญหาเกี่ยวกับการทำให้เป็นอันดับ

ด้านหนึ่งขาดหายไป ชั้นเรียนที่สืบทอดจากรายการ <> ไม่สามารถต่อเนื่องได้อย่างถูกต้องโดยใช้XmlSerializer ในกรณีนั้นจะต้องใช้DataContractSerializerแทนหรือจำเป็นต้องใช้การทำให้เป็นอนุกรมของตัวเอง

public class DemoList : List<Demo>
{
    // using XmlSerializer this properties won't be seralized:
    string AnyPropertyInDerivedFromList { get; set; }     
}

public class Demo
{
    // this properties will be seralized
    string AnyPropetyInDemo { get; set; }  
}

การอ่านเพิ่มเติม: เมื่อคลาสได้รับการสืบทอดจาก List <> XmlSerializer จะไม่ทำให้คุณสมบัติอื่น ๆ เป็นอนุกรม


0

ยอมรับได้เมื่อไหร่?

หากต้องการอ้างอิง Eric Lippert:

เมื่อคุณสร้างกลไกที่ขยายList<T>กลไก

ตัวอย่างเช่นคุณรู้สึกเบื่อกับการขาดAddRangeวิธีในIList<T>:

public interface IMoreConvenientListInterface<T> : IList<T>
{
    void AddRange(IEnumerable<T> collection);
}

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