สองโครงสร้างที่มีสมาชิกเดียวกัน แต่ตั้งชื่อต่างกันมันเป็นความคิดที่ดีใช่ไหม


49

ฉันกำลังเขียนโปรแกรมที่เกี่ยวข้องกับการทำงานกับพิกัดเชิงขั้วและคาร์ทีเซียน

มันสมเหตุสมผลหรือไม่ที่จะสร้างสองโครงสร้างที่แตกต่างกันสำหรับแต่ละประเภทของคะแนนซึ่งเป็นหนึ่งเดียวกับXและYสมาชิกและอีกหนึ่งเป็นด้วยRและThetaสมาชิก

หรือมันมากเกินไปและดีกว่าถ้ามีโครงสร้างเดียวfirstและsecondเป็นสมาชิก

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

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


11
ฉันมักจะสร้าง struct / class ใหม่ต่อวัตถุประสงค์ ต้องการเวกเตอร์ 3 มิติสร้าง struct 3d_vector ด้วยสามลอย ต้องการการแทนค่า uvw สร้าง struct texture_coords ด้วยสามลอย ต้องการตำแหน่งในสภาพแวดล้อม 3 มิติสร้างตำแหน่งโครงสร้างด้วยสามลอย คุณได้รับคะแนน มันช่วยให้อ่านง่ายกว่าการใช้สิ่งเดียวกันทั่ว หากคุณกังวลกับการกำหนดสิ่งเดียวกันหลาย ๆ ครั้งให้ใช้ base 3-float-struct และกำหนดชื่อหลายชื่อให้เป็นโครงสร้างเดียวกัน
เควิน

หากพวกเขามีวิธีการทั่วไปบางอย่างก็อาจเป็นหนึ่ง คุณเคยต้องการเปรียบเทียบความเท่าเทียมกันของทั้งสองไหม?
paparazzo

8
@Sidney ถ้าคุณไม่ต้องการฟังก์ชั่นอย่างแน่นอน คุณต้องทำการดำเนินการ sin / arcsin เพื่อแปลงระหว่างสองแนวทาง นี่จะเป็นการแนะนำบิตบิตในบิตที่สำคัญน้อยที่สุดทุกครั้งที่คุณทำการแปลง ฉันเกือบจะแน่ใจแล้วว่าคุณต้องเจอกับความเจ็บปวดที่คล้ายกันกับสิ่งที่ฉันพยายามจัดการกับชั้นเรียนที่ให้ทั้งความถี่เหตุการณ์และช่วงเวลาระหว่างเหตุการณ์ (x และ 1 / x) แทนบางสิ่งบางอย่าง การติดตามการเป็นตัวแทนซึ่งเป็นปืนใหญ่ในชั้นเรียนและจัดการกับอาการปวดหัวที่ปัดเศษไม่ใช่สิ่งที่ฉันต้องการทำอีกครั้ง
Dan Neely

3
ตัวอย่างที่ดีของประเภทข้อมูลที่สามารถแสดงสิ่งต่าง ๆ ได้มากมายคือสตริง แต่ "stringy typed" เป็น antipattern ยกตัวอย่างของคุณ ลองใช้ผลิตภัณฑ์ dot สำหรับประเภทที่รองรับระบบพิกัดทั้งสอง
Nathan Cooper

1
คุณควรพยายามใช้ประเภทที่แตกต่างกันทุกครั้งที่มีการใช้เพื่อให้ได้ประโยชน์จากความปลอดภัยของประเภท - ซึ่งจะป้องกันคุณจากการส่งประเภทที่ไม่ถูกต้องไปยัง / จากฟังก์ชั่น (โดยใช้การตรวจสอบความถูกต้องเวลาคอมไพล์ มันจะทำให้การบำรุงรักษาง่ายขึ้นเพราะคุณสามารถค้นหาการใช้งานจริงบางประเภท (โดยไม่ได้รับการใช้งานที่ผิดพลาดเนื่องจากการใช้งานแบบแฟทต์)
Erik Eidt

คำตอบ:


17

ฉันได้เห็นวิธีแก้ปัญหาทั้งสองดังนั้นจึงขึ้นอยู่กับบริบทอย่างแน่นอน

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

วิธีการแก้ปัญหาที่รุนแรง (ซึ่งในที่สุดเราก็ให้ไว้) คือการมีคลาสฐาน templated CRTP กับฟังก์ชั่นรับ <0> () รับ <1> () และได้รับ <2> () เพื่อรับองค์ประกอบในลักษณะทั่วไป ฟังก์ชั่นเหล่านั้นจะถูกกำหนดในโครงสร้างคาร์ทีเซียนหรือขั้วโลกที่เกิดขึ้นจากคลาสฐานนี้ มันแก้ปัญหาทั้งหมด แต่มาในราคาที่ค่อนข้างโง่: ต้องเรียนรู้วิธีการเทมเพลต อย่างไรก็ตามถ้าเทมเพลตการเขียนโปรแกรมแบบแมปเป็นเกมที่ยุติธรรมสำหรับโครงการของคุณอาจเป็นการจับคู่ที่ดี


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

1
ฉันสามารถพิสูจน์ตัวอย่างของสิ่งที่ฉันทำ: ตัวกรอง FIR, คาร์ทีเซียนและเวกเตอร์ของมัน คณิตศาสตร์นั้นค่อนข้างคล้ายกันการห่อมุมซัน (อัน) รหัสนั้นมีบางส่วนที่ซ้ำกันอยู่แล้วด้วยเหตุผลด้านประสิทธิภาพและเราใช้แม่แบบที่มันเหมือนกัน ใช้ชื่อที่แตกต่างกันสำหรับทุกสิ่ง "วิธีการแก้ปัญหาที่รุนแรง" ของ Cort อาจช่วยให้เกิดการซ้ำซ้อนได้เล็กน้อย
Eugene Ryabtsev

1
ปฏิกิริยาแรกของฉันคือว่าสถานการณ์เช่นนี้จะได้รับการแก้ไขให้ดีขึ้นโดยการหล่อ แต่มันจะเปิดออกจะค่อนข้างมีความเสี่ยง
200_success

114

ใช่มันสมเหตุสมผลมาก

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

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


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

8
"ควรเขียนโปรแกรมเพื่อให้ผู้คนอ่านและบังเอิญให้เครื่องทำงาน" - จาก "โครงสร้างและการตีความโปรแกรมคอมพิวเตอร์" โดย Abelson และ Sussman
hlovdal

18

ใช่ในขณะที่ทั้งคาร์ทีเซียนและโพลาร์เป็น (ในสถานที่ของพวกเขา) รูปแบบการเป็นตัวแทนประสานงานอย่างมีเหตุผลอย่างเด่นชัดพวกเขาไม่ควรผสม (ถ้าคุณมีจุดคาร์ทีเซียน {1,1}) มันเป็นจุดที่แตกต่างจากขั้วโลก {1,1 })

ทั้งนี้ขึ้นอยู่กับความต้องการของคุณมันก็อาจจะมีมูลค่าการดำเนินการประสานงานติดต่อกับวิธีการเช่นX(), Y(), Displacement()และAngle()(หรืออาจจะRadius()และTheta()ขึ้นอยู่กับ)


มันสำคัญยิ่งกว่าถ้า OP กำลังสร้างคลาสจากโครงสร้างเหล่านั้นเนื่องจากการดำเนินการกับพิกัดคาร์ทีเซียนและโพลาร์นั้นแตกต่างกัน
Mindwin

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

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

8

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

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

นอกจากนี้เพียงเพราะพวกเขาทั้งหมดมีความสามารถในการเป็นตัวแทนdoubleไม่ได้หมายความว่าคุณสามารถใช้พวกเขาแทนกันได้ ตัวอย่างเช่นθเป็นมุมที่ไม่มีมิติในขณะที่yมีหน่วยความยาว เนื่องจากประเภทนั้นไม่สามารถทดแทนได้ในเชิงตรรกะจึงควรมีโครงสร้างที่เข้ากันไม่ได้สองรายการ

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


คำตอบที่ดีที่สุด IMHO
Dean Radcliffe

2

ประการแรกมีทั้งคำตอบที่ชัดเจนตาม @ Kilian-foth

อย่างไรก็ตามฉันต้องการเพิ่ม:

ถาม: คุณมีการดำเนินงานที่เป็นทั่วไปสำหรับทั้งคู่เมื่อพิจารณาว่าเป็นคู่doubleหรือไม่? โปรดทราบว่านี่ไม่เหมือนกับการบอกว่าคุณมีการดำเนินการที่ใช้กับทั้งสองอย่างในแง่ของตัวเอง ตัวอย่างเช่น 'plot (Coord)' ใส่ใจว่าCoordPolar หรือ Cartesian เป็นอย่างไร ในทางกลับกันการคงอยู่ของไฟล์จะถือว่าข้อมูลเหมือนเดิม หากคุณมีการดำเนินการทั่วไปให้พิจารณากำหนดคลาสพื้นฐานหรือกำหนดตัวแปลงเป็นstd::pair<double, double>หรือtupleหรืออะไรก็ตามที่คุณมีในภาษาของคุณ

นอกจากนี้วิธีการหนึ่งอาจใช้เพื่อประสานงานหนึ่งประเภทเป็นแบบพื้นฐานและอีกแบบหนึ่งเป็นเพียงการสนับสนุนผู้ใช้หรือการโต้ตอบภายนอก

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


1

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

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

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


ชื่อของสมาชิกในทั้งสองคลาสนั้นไม่เหมือนกัน อันที่จริงชื่อมีความแตกต่างเพียงอย่างเดียวระหว่างสองชั้น
Moha the อูฐยิ่งใหญ่

@ Mhd.Tahawi คุณสามารถใช้ getters และ setters ด้วยชื่อที่เหมาะสมเพื่อให้แน่ใจว่าคลาสที่คุณใช้เหมือนกัน แต่ให้ชื่อที่เหมาะสมสำหรับการดำเนินการที่คุณต้องการใช้ มันจะกลายเป็น verbose อีกเล็กน้อย แต่คุณจะต้องทำซ้ำรหัสน้อย
Svalorzen

0

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

ลองนึกภาพสถานการณ์: คุณมีคะแนนคาร์ทีเซียนสองสามจุด: pntA และ pntB จากนั้นคุณตัดสินใจด้วยเหตุผลบางอย่างว่าพวกเขาควรจะแสดงในพิกัดเชิงขั้วได้ดีขึ้นและเปลี่ยนการประกาศและตัวสร้าง

ตอนนี้หากการดำเนินการทั้งหมดของคุณเป็นเพียงวิธีการเรียกเช่น:

double distance = pntA.distanceFrom(pntB);

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

double leftMargin = abs(pntA.x - pntB.x);
double leftMargin = abs(pntA.first - pntB.first);

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

หากคุณเขียนในภาษาที่ไม่ใช่เชิงวัตถุแล้วมันจะง่ายยิ่งขึ้นในการส่ง struct ที่ผิดไปยังฟังก์ชัน สิ่งที่จะหยุดคุณจากการเขียนรหัสต่อไปนี้?

double distance = calculate_distance_polar(cartesianPointA, polarPointB);

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

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