ดั้งเดิมเทียบกับคลาสเพื่อแสดงวัตถุโดเมนอย่างง่าย


14

อะไรคือแนวทางทั่วไปหรือกฎง่ายๆสำหรับเมื่อใช้วัตถุโดเมน speciifc เทียบกับสตริงหรือหมายเลขธรรมดา?

ตัวอย่าง:

  • อายุกับจำนวนเต็ม?
  • ชั้น FirstName vs String?
  • UniqueID vs String
  • หมายเลขโทรศัพท์กับสตริงเทียบกับ Long หรือไม่
  • DomainName คลาส vs String?

ฉันคิดว่าผู้ปฏิบัติงาน OOP ส่วนใหญ่จะพูดคลาสที่เฉพาะเจาะจงสำหรับ PhoneNumber และ DomainName กฎเพิ่มเติมเกี่ยวกับสิ่งที่ทำให้พวกเขาถูกต้องและวิธีการเปรียบเทียบทำให้ชั้นเรียนง่าย ๆ ง่ายขึ้นและปลอดภัยยิ่งขึ้นที่จะจัดการกับ แต่สำหรับสามคนแรกนั้นมีการถกเถียงกันมากขึ้น

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

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

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

บางทีฉันอาจจะคิดมากเรื่องนี้ แต่ฉันอยากจะรู้ว่าถ้าใครมีกฎเกณฑ์ว่าเมื่อใดควรเลือกคลาสเทียบกับแบบดั้งเดิม


2
คลาสอายุไม่ใช่ความคิดที่ดีหากสามารถแทนที่ด้วยจำนวนเต็ม ถ้าใช้วันเดือนปีเกิดในตัวสร้างและมีเมธอด getCurrentAge () และ getAgeAt (Date date) คลาสจะมีความหมาย แต่ไม่สามารถแทนที่ด้วยจำนวนเต็ม
kiwiron

5
msgstr "บางครั้งสตริงอาจไม่ใช่สตริง มีการโพสต์ที่ดีโดย Mark Seemann เกี่ยวกับ 'ครอบงำจิตใจดั้งเดิม': blog.ploeh.dk/2015/01/19/…
Serhii Shushliapin

4
จริงๆแล้วคลาส Age จะทำให้รู้สึกบางอย่างในทางที่แปลก คำจำกัดความทางตะวันตกทั่วไปของอายุคือศูนย์ที่เกิดและเพิ่ม 1 วันเกิดของคุณต่อไป วิธีการของเอเชียตะวันออกคือคุณเกิดที่ 1 และเพิ่ม 1 ในวันปีใหม่ถัดไป ดังนั้นขึ้นอยู่กับสถานที่ของคุณอายุของคุณสามารถคำนวณได้แตกต่างกัน EG จากen.wikipedia.org/wiki/East_Asian_age_reckoning people may be one or two years older in Asian reckoning than in the western age system
Peter M

@PeterM นั่นเป็นข้อมูลที่ดี! วันที่ / เวลาและตอนนี้อายุ พวกเขาควรเป็นแนวคิดที่เรียบง่าย แต่สิ่งนี้พิสูจน์ได้ว่ามีความซับซ้อนในโลกมากกว่าที่เราเคยจัดการ
Machado

6
ฉันเห็นการเขียนจำนวนมากด้านล่างคำถามนี้ แต่คำตอบนั้นไม่ซับซ้อนจริงๆ คุณใช้Ageคลาสแทนจำนวนเต็มเมื่อคุณต้องการลักษณะการทำงานเพิ่มเติมที่คลาสนั้นจะมีให้
Robert Harvey

คำตอบ:


20

อะไรคือแนวทางทั่วไปหรือกฎง่ายๆสำหรับเมื่อใช้วัตถุโดเมน speciifc เทียบกับสตริงหรือหมายเลขธรรมดา?

หลักเกณฑ์ทั่วไปคือคุณต้องการสร้างแบบจำลองโดเมนของคุณในภาษาเฉพาะโดเมน

ลองพิจารณา: ทำไมเราถึงใช้จำนวนเต็ม? เราสามารถแสดงจำนวนเต็มทั้งหมดด้วยสตริงได้อย่างง่ายดาย หรือด้วยไบต์

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

สิ่งที่จริงลงไปคือลักษณะ "ดั้งเดิม" ของบางประเภทเป็นอุบัติเหตุของการเลือกภาษาสำหรับการใช้งานของเรา

โดยเฉพาะอย่างยิ่งตัวเลขมักต้องการบริบทเพิ่มเติม อายุไม่ใช่แค่ตัวเลข แต่มันมีมิติ (เวลา) หน่วย (ปี)กฎการปัดเศษ! การเพิ่มอายุเข้าด้วยกันทำให้รู้สึกถึงวิธีการเพิ่มอายุให้กับเงินไม่ได้

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

อุบัติเหตุของการแสดงค่าเหล่านี้ในหน่วยความจำเป็นหนึ่งในส่วนที่น่าสนใจน้อยที่สุด รูปแบบโดเมนไม่สนใจCustomerIdคือ int หรือ String หรือ UUID / GUID หรือโหนด JSON มันแค่ต้องการเงิน

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

Parnas ในปี 1972เขียน

เราเสนอว่าจะเริ่มด้วยรายการการตัดสินใจออกแบบที่ยากหรือการตัดสินใจออกแบบที่มีแนวโน้มที่จะเปลี่ยนแปลง จากนั้นแต่ละโมดูลจะถูกออกแบบมาเพื่อซ่อนการตัดสินใจดังกล่าวจากสิ่งอื่น

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

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

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

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


จุดที่ยอดเยี่ยมเกี่ยวกับการซ่อน (หรือสรุป) สิ่งเหล่านั้นที่ยากและมีแนวโน้มที่จะเปลี่ยนแปลง
949300

4

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

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

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

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

หากคุณมีชื่อเป็นคุณลักษณะหลายอย่าง (เช่นคำนำหน้า / ชื่อเรื่อง, ท้ายสุด, ท้ายคำต่อท้าย) ที่จะทำบุญในชั้นเรียน (เนื่องจากตอนนี้คุณมีพฤติกรรมบางอย่างเช่นได้รับชื่อเต็มเป็นสตริงเทียบกับการได้ชิ้นส่วนเล็ก ๆ ชื่อ ฯลฯ .)


1

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

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

สำหรับชื่อแรกที่ฉันไม่เห็นประโยชน์มาก, UniqueID ด้วย, เบอร์โทรศัพท์เล็ก ๆ น้อย ๆ , คุณสามารถขอให้คุณระบุประเทศและภูมิภาคได้เพราะโดยทั่วไป PhoneNumber หมายถึงประเทศและภูมิภาคผู้ให้บริการบางรายใช้ Suffixes เพื่อกำหนด Mobile หมายเลข Office ... ดังนั้นคุณสามารถเพิ่มวิธีการเหล่านี้ให้กับคลาสนั้นได้เช่นกันสำหรับ DomainName

สำหรับรายละเอียดเพิ่มเติมฉันแนะนำให้คุณดูที่ Value Objects ใน DDD (การออกแบบการขับเคลื่อนด้วยโดเมน)


1

สิ่งที่มันขึ้นอยู่กับว่าคุณมีพฤติกรรม (ที่คุณต้องรักษาและ / หรือเปลี่ยนแปลง) ที่เกี่ยวข้องกับข้อมูลนั้นหรือไม่

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

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


1

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

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

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

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

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

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


1

ในตอนท้ายของวันที่วัตถุเป็นเพียงพยานว่ากระบวนการบางจริงมีวิ่ง†

ดึกดำบรรพ์intหมายความว่ากระบวนการบางอย่างทำให้ศูนย์มีหน่วยความจำ 4 ไบต์แล้วพลิกบิตที่จำเป็นเพื่อแทนค่าจำนวนเต็มบางส่วนในช่วง -2,147,483,648 ถึง 2,147,483,647; ซึ่งไม่ได้เป็นคำสั่งที่แข็งแกร่งเกี่ยวกับแนวคิดของอายุ เช่นเดียวกัน a (ไม่เป็นโมฆะ) System.Stringหมายความว่ามีการจัดสรรไบต์บางส่วนและบิตถูกพลิกเพื่อแสดงลำดับของอักขระ Unicode ที่เกี่ยวกับการเข้ารหัสบางส่วน

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

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

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

//hypothetical syntax
class Age = |start:date - end:date|.Years

(* ML-like syntax *)
type Age(x, y) = datediff(year, x, y)

//C#-like syntax with primary constructors and expression-bodied classes
class Age(DateTime x, DateTime y) => implicit operator int (Age a) => Abs((y - x).Years);

ยกตัวอย่างเช่น F # จริง ๆ แล้วมีไวยากรณ์ที่ดีสำหรับสิ่งนี้ในรูปแบบของActive Patterns :

//Impossible to produce an `Age` without first computing the difference of two dates
//But, any pair (tuple) of dates is implicitly converted to an `Age` when needed.

let (|Age|) (x, y) =  (date_diff y x).Days / 365

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


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


0

คิดเกี่ยวกับสิ่งที่คุณได้รับจากการใช้คลาสกับแบบดั้งเดิม หากใช้คลาสสามารถรับคุณได้

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

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

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

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