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


38

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

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

อัปเดต : หนังสือและวิดีโอที่เผยแพร่ล่าสุดเมื่อไม่นานมานี้ในหัวเรื่อง


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

2
มีความเกี่ยวข้องอย่างยิ่งถ้าไม่ใช่ข้อมูลซ้ำซ้อน: เหตุใดรูปแบบโดเมนของโลหิตจางจึงถือว่าไม่ดีใน OOP แต่สำคัญมากใน FP
Robert Harvey

1
"คำว่าแบบจำลองโลหิตจาง" ยังมีอยู่ในกรณีนั้นหรือไม่ "คำตอบสั้น ๆ คำว่าแบบจำลองโลหิตจางได้รับการประกาศเกียรติคุณในบริบทของ OO การพูดถึงแบบจำลองโลหิตจางในบริบทของ FP นั้นไม่มีความหมายเลย อาจมีสิ่งเทียบเท่าในแง่ของการอธิบายว่าอะไรคือ idiomatic FP แต่มันไม่มีส่วนเกี่ยวข้องกับแบบจำลองโลหิตจาง
plalx

5
Eric Evans เคยถูกถามถึงสิ่งที่เขาพูดกับคนที่กล่าวหาเขาว่าสิ่งที่เขาอธิบายในหนังสือของเขาเป็นเพียงการออกแบบเชิงวัตถุที่ดีและเขาตอบว่าไม่ใช่ข้อกล่าวหามันเป็นความจริง DDD เป็นเพียง OOD ที่ดีเขาเพิ่งเขียน ลงสูตรและรูปแบบบางอย่างและตั้งชื่อเพื่อให้ง่ายต่อการติดตามและพูดคุยเกี่ยวกับพวกเขา ดังนั้นจึงไม่น่าแปลกใจที่ DDD เชื่อมโยงกับ OOD คำถามที่กว้างขึ้นจะเป็นอะไรจุดตัดและความแตกต่างระหว่าง OOD และ FPD แม้ว่าคุณจะต้องกำหนดสิ่งที่คุณหมายถึงโดย "การเขียนโปรแกรมการทำงาน" ก่อน
Jörg W Mittag

2
@ JörgWMittag: คุณหมายถึงนอกเหนือจากคำจำกัดความปกติหรือไม่ มีแพลตฟอร์มที่มีภาพประกอบมากมาย Haskell เป็นแพลตฟอร์มที่ชัดเจนที่สุด
Robert Harvey

คำตอบ:


22

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

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

การแก้ไขปัญหานี้มักเกิดขึ้นในไม่กี่ขั้นตอน การตัดครั้งแรกอย่างง่าย ๆ คือการพูดว่า "เอ่อเอUserIDแทนด้วยสตริง แต่มันเป็นประเภทที่แตกต่างกันและคุณไม่สามารถใช้งานได้ตามที่คุณคาดหวังไว้" Haskell (และภาษาที่ใช้งานได้บางประเภท) มีคุณสมบัตินี้ผ่านnewtype:

newtype UserID = UserID String

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

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

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

data Digit = Zero | One | Two | Three | Four | Five | Six | Seven | Eight | Nine
data UserID = UserID [Digit]

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


2
และสิ่งที่เกี่ยวกับมวลรวมและมวลรวมที่ป้องกันการแปรปรวนเป็นสิ่งสุดท้ายที่สามารถแสดงและเข้าใจได้ง่ายโดยนักพัฒนาในภายหลัง สำหรับฉันส่วนที่มีค่าที่สุดของ DDD คือการแม็พโมเดลธุรกิจโดยตรงกับโค้ด และคุณตอบว่าเกี่ยวกับที่
Pavel Voronin

2
คำพูดดี แต่ไม่มีคำตอบสำหรับคำถาม OP
SerG

10

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

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

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


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

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

7

เมื่อใช้ DDD ใน OOP หนึ่งในเหตุผลหลักที่ทำให้ตรรกะทางธุรกิจในวัตถุโดเมนนั้นคือตรรกะทางธุรกิจที่มักจะนำมาใช้โดยการกลายพันธุ์สถานะของวัตถุ สิ่งนี้เกี่ยวข้องกับการห่อหุ้ม: Employee.RaiseSalaryอาจกลายพันธุ์salaryฟิลด์ของEmployeeอินสแตนซ์ซึ่งไม่ควรตั้งค่าต่อสาธารณะ

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

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

โปรดสังเกตว่าเมื่อคุณจับคู่ข้อมูลและพฤติกรรมเช่นเดียวกับใน DDD คุณมักจะละเมิดหลักการความรับผิดชอบเดี่ยว (SRP): Employeeอาจจำเป็นต้องเปลี่ยนแปลงหากกฎสำหรับการเปลี่ยนแปลงเงินเดือนเปลี่ยนแปลง แต่มันอาจจำเป็นต้องเปลี่ยนแปลงหากกฎสำหรับการคำนวณการเปลี่ยนแปลงโบนัส EOY ด้วยวิธีการแยกสองส่วนนี้ไม่ใช่กรณีเนื่องจากคุณสามารถมีหลายโมดูลแต่ละคนมีความรับผิดชอบ

ดังนั้นตามปกติวิธีการของ FP จะให้ความสามารถในการแยกส่วน / เรียงความที่มากขึ้น


-1

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

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


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

-3

ฉันต้องการทราบว่า DDD เหมาะสมกับกระบวนทัศน์ของ FP อย่างไร

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

และคำว่า 'แบบจำลองโลหิตจาง' ยังคงมีอยู่ในกรณีนั้นหรือไม่

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

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

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


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

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

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