การเพิ่มประเภทส่งคืนไปยังวิธีการอัปเดตเป็นการละเมิด“ หลักการความรับผิดชอบเดี่ยว” หรือไม่?


37

ฉันมีวิธีที่อัปเดตข้อมูลพนักงานในฐานข้อมูล Employeeชั้นจะไม่เปลี่ยนรูปเพื่อ "ปรับปรุง" วัตถุหมายถึงจริงยกตัวอย่างวัตถุใหม่

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

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


5
โค้ดหลอกขนาดเล็กอาจไปไกลได้ที่นี่: จริง ๆ แล้วมีการสร้างเรคคอร์ด DB ใหม่หรือมีการปรับปรุงเรคคอร์ด DB แต่ในโค้ด "ไคลเอนต์" จะมีการสร้างออบเจ็กต์ใหม่เนื่องจากคลาสนั้นมีรูปแบบที่ไม่เปลี่ยนรูป
Martin Ba

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

1
@ ส่วนอื่น: หากเอนทิตีข้อมูลของคุณไม่เปลี่ยนรูปการส่งคืนอินสแตนซ์ที่มีข้อมูลที่อัปเดตนั้นเป็น SOP ค่อนข้างมากเพราะไม่เช่นนั้นคุณจะต้องทำอินสแตนซ์ของข้อมูลที่แก้ไขด้วยตัวคุณเอง
mikołak

21
Compare-and-swapและtest-and-setเป็นการดำเนินการขั้นพื้นฐานในทฤษฎีของการเขียนโปรแกรมแบบมัลติเธรด ทั้งสองวิธีนี้เป็นวิธีการอัพเดตที่มีประเภทส่งคืนและไม่สามารถทำงานได้ แนวคิดการหยุดพักเช่นการแยกคำสั่งการสืบค้นหรือหลักการความรับผิดชอบเดี่ยวหรือไม่ ใช่และที่เป็นจุดรวม SRP ไม่ใช่สิ่งที่ดีในระดับสากลและในความเป็นจริงอาจเป็นอันตรายได้
MSalters

3
@MSalters: แน่นอน การแยกคำสั่ง / แบบสอบถามบอกว่ามันเป็นไปได้ที่จะออกแบบสอบถามซึ่งสามารถจดจำได้ว่าเป็น idempotent และออกคำสั่งโดยไม่ต้องรอการตอบกลับ แต่การอ่านอะตอม - แก้ไข - เขียนควรเป็นประเภทที่สามของการดำเนินการ
supercat

คำตอบ:


16

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

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

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

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

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

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

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

แต่ฉันคิดว่าสถานการณ์ 1 มีแนวโน้มมากกว่าดังนั้นฉันจึงอาจพูดว่าไม่มีปัญหา


ขอบคุณสำหรับคำตอบทั้งหมด แต่อันนี้จริงๆช่วยฉันมากที่สุด
Sipo

หากคุณไม่ต้องการส่งโดยไม่มีการฟอร์แมตใหม่และไม่มีประโยชน์ในการฟอร์แมตนอกเหนือจากการส่งข้อมูลในภายหลังนั่นเป็นเรื่องหนึ่งไม่ใช่สอง
Joker_vD

public function sendDataInClientFormat() { formatDataForClient(); sendDataToClient(); } private function formatDataForClient() {...} private function sendDataToClient() {...}
CJ Dennis

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

67

แนวคิดของ SRP คือการหยุดโมดูลที่ทำ 2 สิ่งที่แตกต่างกันเมื่อทำมันเป็นเอกเทศทำให้การบำรุงรักษาดีขึ้นและสปาเก็ตตี้น้อยลงในเวลา ในฐานะที่เป็น SRP บอกว่า "เหตุผลหนึ่งที่จะเปลี่ยน"

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

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


7
อีกวิธีหนึ่งไม่ใช่ "เกี่ยวกับการพยายามลดการดำเนินการทั้งหมดเป็นการโทรครั้งเดียว" แต่เพื่อลดจำนวนกรณีที่แตกต่างกันที่คุณต้องพิจารณาเมื่อใช้รายการที่เป็นปัญหา
jpmc26

46

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

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

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

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


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

3
ฉันอาจเพิ่มว่าแอปพลิเคชัน SRP ที่กระตือรือร้นเกินไปนำไปสู่การเรียนเล็ก ๆ จำนวนมากซึ่งเป็นภาระในตัวเอง ภาระใหญ่ขึ้นอยู่กับสภาพแวดล้อมคอมไพเลอร์เครื่องมือ IDE / ตัวช่วยของคุณ ฯลฯ
Erik Alapää

8

มันเป็นการละเมิดหลักการความรับผิดชอบเดี่ยวหรือไม่?

ไม่จำเป็น. หากมีสิ่งใดสิ่งนี้เป็นการละเมิดหลักการแยกคำสั่งเคียวรี

ความรับผิดชอบคือ

อัปเดตข้อมูลพนักงาน

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

อีกครั้งนี่เป็นเรื่องของการศึกษาระดับปริญญาและการตัดสินส่วนตัว


แล้วการแยกคำสั่งเคียวรีล่ะ

มีหลักการนั้นอยู่แล้ว แต่การส่งคืนผลลัพธ์ของการอัพเดตนั้นเป็นเรื่องธรรมดามาก

(1) Java's Set#add(E)เพิ่มองค์ประกอบและส่งกลับการรวมก่อนหน้านี้ในชุด

if (visited.add(nodeToVisit)) {
    // perform operation once
}

สิ่งนี้มีประสิทธิภาพมากกว่าทางเลือกของ CQS ซึ่งอาจต้องทำการค้นหาสองครั้ง

if (!visited.contains(nodeToVisit)) {
    visited.add(nodeToVisit);
    // perform operation once
}

(2) Compare-and-swap , fetch-and-add , และการทดสอบและการตั้งค่าเป็นพื้นฐานทั่วไปที่ช่วยให้การเขียนโปรแกรมพร้อมกันอยู่ รูปแบบเหล่านี้ปรากฏบ่อยครั้งตั้งแต่คำสั่ง CPU ระดับต่ำไปจนถึงคอลเลกชันที่เกิดขึ้นพร้อมกันในระดับสูง


2

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

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

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

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

แต่วิธีการอัปเดตจะส่งคืนสิ่งที่เพิ่งได้อัปเดตนั้นเป็นเรื่องปกติ


2

Wrt กรณีเฉพาะ:

Employee Update(Employee, name, password, etc) (ใช้ตัวสร้างจริงเนื่องจากฉันมีพารามิเตอร์จำนวนมาก)

มันดูเหมือนว่าUpdateวิธีที่จะใช้เวลาที่มีอยู่Employeeเป็นพารามิเตอร์แรกที่ระบุ (?) ของพนักงานที่มีอยู่และการตั้งค่าของพารามิเตอร์การเปลี่ยนแปลงในการทำงานของพนักงานนี้

ผมไม่คิดว่านี่อาจจะทำความสะอาดเสร็จแล้ว ฉันเห็นสองกรณี:

(a) Employeeมีฐานข้อมูล / รหัสเฉพาะที่สามารถระบุได้ในฐานข้อมูล (นั่นคือคุณไม่จำเป็นต้องตั้งค่าระเบียนทั้งหมดเพื่อค้นหาในฐานข้อมูล

ในกรณีนี้ฉันต้องการvoid Update(Employee obj)วิธีที่เพิ่งพบระเบียนที่มีอยู่ตาม ID แล้วปรับปรุงเขตข้อมูลจากวัตถุที่ส่งผ่าน หรืออาจจะเป็นvoid Update(ID, EmployeeBuilder values)

ความหลากหลายของสิ่งนี้ที่ฉันพบว่ามีประโยชน์คือมีเพียงvoid Put(Employee obj)วิธีการที่แทรกหรืออัพเดตขึ้นอยู่กับว่าระเบียน (ตาม ID) มีอยู่หรือไม่

(ข) การvoid Update(Employee existing, Employee newData)บันทึกที่มีอยู่อย่างเต็มรูปแบบเป็นสิ่งจำเป็นสำหรับการค้นหาฐานข้อมูลในกรณีที่ว่ามันยังอาจทำให้รู้สึกมากขึ้นที่จะมี:

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

ข้อกำหนดที่เกิดขึ้นพร้อมกันดังกล่าวในคำตอบอื่น ๆ (ชุดอะตอมและการดึง / เปรียบเทียบ-swap ฯลฯ ) ไม่ได้เป็นปัญหาในรหัสฐานข้อมูลที่ฉันได้ทำงานมาจนถึงตอนนี้ เมื่อพูดคุยกับฐานข้อมูลฉันคิดว่าเรื่องนี้ควรได้รับการจัดการในระดับการทำธุรกรรมตามปกติไม่ใช่ในระดับใบแจ้งยอดรายบุคคล (นั่นไม่ได้บอกว่าอาจไม่มีการออกแบบที่ "อะตอมมิก" Employee[existing data] Update(ID, Employee newData)ไม่สมเหตุสมผล แต่ด้วยการเข้าถึงฐานข้อมูลไม่ใช่สิ่งที่ฉันเห็นตามปกติ)


1

จนถึงทุกคนที่นี่กำลังพูดถึงชั้นเรียน แต่ลองคิดดูจากมุมมองของอินเตอร์เฟส

หากวิธีการอินเทอร์เฟซประกาศชนิดส่งคืนและ / หรือคำสัญญาโดยปริยายเกี่ยวกับค่าส่งคืนการใช้งานทุกอย่างจะต้องส่งคืนวัตถุที่อัปเดต

ดังนั้นคุณสามารถถาม:

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

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

จากการพิจารณาเหล่านี้คุณสามารถตัดสินใจได้

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


0

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

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