กลับสู่พื้นฐาน
ความลับของหลักการความรับผิดชอบเดียว
ค้นพบความลับเกี่ยวกับหลักการความรับผิดชอบเดี่ยว (SRP) ของหลักการ SOLID
นักพัฒนาเกือบทั้งหมดที่ทำงานกับภาษา Object Oriented Programming (OOP) รู้เกี่ยวกับหลักการ SOLID
หลักการที่มั่นคง
- Sหลักการความรับผิดชอบเดียว
- Oหลักการปิดปากกา
- หลักการทดแทน Liskov
- ฉันเชื่อมต่อหลักการแบ่งแยก
- Dหลักการผกผันแบบซ้ำซ้อน
ในบทความนี้ เราจะอธิบาย S ของ SOLID, หลักการความรับผิดชอบเดี่ยว อย่างไรก็ตาม คุณจะอ่านอะไรที่นี่ แตกต่าง…
หลักการความรับผิดชอบเดี่ยวคืออะไร
ตาม "วิกิพีเดีย":
คำนี้ถูกนำมาใช้โดย Robert C. Martin ในบทความของเขา "The Principles of OOD" ซึ่งเป็นส่วนหนึ่งของ Principles of Object Oriented Design ซึ่งได้รับความนิยมในหนังสือ Agile Software Development ของเขาในปี 2003 หลักการ รูปแบบ และแนวปฏิบัติ
Martin อธิบายว่าสิ่งนี้มีพื้นฐานอยู่บนหลักการของ "การทำงานร่วมกัน" ตามที่อธิบายโดย "Tom DeMarco" ในหนังสือของเขา Structured Analysis and System Specification และ Meilir Page-Jones ใน The Practical Guide to การออกแบบระบบโครงสร้าง
ในปี 2014 Martin เผยแพร่บล็อกโพสต์ชื่อ "หลักการความรับผิดชอบเดี่ยว" โดยมีเป้าหมายเพื่อชี้แจงความหมายของวลี "เหตุผลในการเปลี่ยนแปลง"
และสำหรับความหมายของมัน:
โรเบิร์ต ซี. มาร์ตินแสดงหลักการที่ว่า “ชั้นเรียนควรมีเหตุผลเดียวเท่านั้นที่จะเปลี่ยนแปลง” เนื่องจากความสับสนเกี่ยวกับคำว่า “เหตุผล” เขาจึงชี้แจงด้วยว่า “หลักการเป็นเรื่องเกี่ยวกับผู้คน” ในปาฐกถาบางเรื่อง เขายังแย้งว่าหลักการคือเกี่ยวกับบทบาทหรือนักแสดงโดยเฉพาะ
ตัวอย่างเช่น แม้ว่าพวกเขาจะเป็นคนคนเดียวกัน แต่บทบาทของนักบัญชีนั้นแตกต่างจากผู้ดูแลฐานข้อมูล ดังนั้นแต่ละโมดูลควรรับผิดชอบในแต่ละบทบาท
ตอนนี้พอกับการพูดคุยใน Wikipedia เรามาชี้แจงบางประเด็นกันดีกว่า
คำจำกัดความหลักของหลักการความรับผิดชอบเดียวก็คือ ชั้นเรียนควรมีเหตุผลเดียวเท่านั้นที่จะเปลี่ยนแปลง
ผู้คนอาจสับสนว่า เหตุผล หมายถึงอะไร และฉันเข้าใจดีว่าสิ่งนี้มาจากไหน
ให้ฉันอธิบายอย่างละเอียด…
โน๊ตสำคัญ
เพื่อผลักดันให้คุณมุ่งเน้นไปที่หัวข้อหลักที่เรากำลังพูดคุยกันที่นี่ แนวทางปฏิบัติที่ดีที่สุดบางประการเกี่ยวกับโค้ดจึงจะถูกละทิ้ง นี่หมายถึงอินเทอร์เฟซที่น้อยลง นามธรรม การนำคอนสตรัคเตอร์ไปใช้... เว้นแต่จำเป็นสำหรับการสาธิต
ความสับสน
Developer บางคนมักจะบอกว่าถ้าคลาสทำหลายสิ่งเกินไป จริงๆ แล้วคลาสนั้นละเมิด Single Responsibility Principle
ดี แต่ก็ยัง มากเกินไปหมายถึงอะไร? จะรู้ได้อย่างไรว่าชั้นเรียนทำอะไรมากเกินไปหรือไม่? ถ้าชั้นเรียนทำ 2 สิ่งนี้จะมากเกินไปหรือเปล่า? แล้ว 3 ล่ะ? แล้ว 4 ล่ะ? …
ในความเห็นอันต่ำต้อยของฉัน ฉันพบว่าคำจำกัดความนี้น่าสับสนและฉันไม่สามารถตำหนิคุณได้หากคุณรู้สึกแบบเดียวกัน คำว่า "มากเกินไป" ไม่สามารถวัดผลหรือเข้าใจได้
ตอนนี้ฉันได้ยินคุณพูดว่า:
แล้วเราตกลงกันไหมว่าไม่มีคำที่ดีที่จะอธิบายหลักการความรับผิดชอบเดี่ยว (Single Responsibility Principle)? เราควรทิ้งมันไปดีไหม?
จริงๆแล้วไม่มี ฉันมีบางอย่างที่สามารถทำให้มันชัดเจนมากขึ้น ดังนั้นให้ฉันอธิบายเพิ่มเติม
ในซอฟต์แวร์ วิธีการหรือฟังก์ชัน ไม่ว่าคุณจะเรียกมันว่าอะไรก็ตาม มีสองปัจจัยในการอธิบาย:
- ความรู้ จำเป็นต่อการทำงาน
- ความสำเร็จ เกิดขึ้นเมื่อเสร็จสิ้น
ปัจจัยทั้งสองนี้แตกต่างกันและไม่เหมือนกัน เราควรแยกความแตกต่างเนื่องจากมีผลกระทบมากมายขึ้นอยู่กับขนาดของแต่ละส่วน
ตอนนี้ให้ฉันอธิบายคุณทีละขั้นตอน
ความรู้วิธีการ
หมายถึงความรู้วิธีการที่จำเป็นเพื่อให้สามารถทำงานได้ กล่าวอีกนัยหนึ่งหมายถึงรายละเอียดภายในและขั้นตอนที่วิธีการจำเป็นต้องทราบเพื่อให้สามารถทำงานให้สำเร็จได้
ตัวอย่างเช่น มาดูวิธีนี้กัน:
public int Add(int a, int b) { return a + b; }
วิธี Add
ควรรู้วิธีบวกตัวเลขสองตัว ทำได้โดยใช้ตัวดำเนินการ +
ที่ได้รับจาก .NET Framework
ตอนนี้ หากนักพัฒนา .NET Framework ตัดสินใจที่จะเปลี่ยนการใช้งานภายในของตัวดำเนินการ +
หรือวิธีการแปลในระดับรหัสเครื่อง สิ่งนี้ไม่ควรส่งผลกระทบต่อวิธี Add
เนื่องจากไม่สนใจ มันเป็นเพียงการมอบหมายวิธีการเพิ่มเลขจำนวนเต็มสองตัวให้กับ .NET Framework
ตอนนี้ สมมติว่าข้อกำหนดของระบบที่เรากำลังพัฒนามีการเปลี่ยนแปลง ดังนั้นเราจึงต้องเพิ่มระยะขอบพิเศษให้กับวิธี Add
ดังนั้นโค้ดควรเป็นดังนี้:
private const int Margin = 10; public int Add(int a, int b) { return a + b + Margin; }
ตอนนี้มันแตกต่างออกไป
หลังจากการใช้เมธอด Add
ใหม่นี้ ความรู้ของเมธอดก็เปลี่ยนไป และความรู้ก็เปลี่ยนไป เนื่องจากตอนนี้จำเป็นต้องทราบเกี่ยวกับการเพิ่มระยะขอบพิเศษ ซึ่งไม่เคยมีมาก่อน
ความสำเร็จของวิธีการ
หมายถึง งาน ที่จะทำเมื่อวิธีการเสร็จสิ้น ไม่ว่าวิธีการนั้นจะมี ความรู้ความชำนาญอย่างแท้จริง ของแต่ละขั้นตอนของงานนี้หรือไม่ หรือไม่.
ตัวอย่างเช่น มาดูวิธีนี้กัน:
public class EmployeeManager { private readonly EmployeeRepository _mEmployeeRepository; private readonly TaxCalculator _mTaxCalculator; private readonly MailingGroupsManager _mMailingGroupsManager; private readonly HolidaysCalculator _mHolidaysCalculator; public async Task<Employee> HireEmployee(Employee employee) { // Add Employee entry with basic info in DB var updatedEmployee = await _mEmployeeRepository.Add(employee); // Add Emplyee to mailing groups updatedEmployee = await _mMailingGroupsManager.Add(updatedEmployee); // Calculate tax scheme decimal tax = await _mTaxCalculator.Calculate(updatedEmployee); // Update tax scheme in DB updatedEmployee = await _mEmployeeRepository.UpdateTax(updatedEmployee, tax); // Calculate holidays byte holidaysCount = await _mHolidaysCalculator.Calculate(updatedEmployee); // Update holidays in DB return await _mEmployeeRepository.UpdateHolidays(updatedEmployee, holidaysCount); } }
ดังที่เราสังเกตได้จากที่นี่ ปริมาณงานที่จะเกิดขึ้นหลังจากวิธีนี้เสร็จสิ้นจะมีขนาดใหญ่มาก อย่างไรก็ตาม เราสามารถพูดเช่นเดียวกันเกี่ยวกับความรู้ได้หรือไม่? ฉันไม่คิดอย่างนั้น...
ความรู้เดียวที่วิธีนี้มีคือความรู้เกี่ยวกับขั้นตอนและลำดับการดำเนินการที่จำเป็นเพื่อให้ได้งาน Employee
ไม่มีอะไรเพิ่มเติม
คุณอาจแย้งว่าวิธีการรู้มากกว่านั้น แต่จริงๆ แล้วไม่เลย ใช่ เมื่อเมธอด HireEmployee
เริ่มต้น พนักงานจะถูกเพิ่มลงในฐานข้อมูล แต่จริงๆ แล้ว พนักงานไม่รู้ว่าทำอย่างไร เมธอด Add
ของคลาส EmployeeRepository
คือเจ้าของความรู้นี้
เช่นเดียวกับวิธีอื่นๆ ทั้งหมด และทำให้วิธี HireEmployee
ง่ายกว่าที่เราคาดไว้ก่อนที่จะสังเกตเห็นสิ่งนี้ ใช่ไหม?
ถึงอย่างนั้น เรามาวิเคราะห์ว่าปัจจัยทั้งสองนี้อาจมีผลกระทบต่อคำจำกัดความของหลักการความรับผิดชอบเดี่ยวได้อย่างไร
ถึงตอนนี้ ควรจะชัดเจนว่าเมื่อเราประเมินว่าชั้นเรียนปฏิบัติตามหลักการความรับผิดชอบเดียวหรือไม่ เราควรให้ความสำคัญกับความรู้มากกว่าความสำเร็จ
เนื่องจากยิ่งชั้นเรียนมีความรู้มากเท่าไร ก็ยิ่งมีแนวโน้มที่จะละเมิดหลักความรับผิดชอบเดี่ยว (Single Responsibility Principle) มากขึ้นเท่านั้น
ให้ฉันอธิบายเพิ่มเติม…
ช่วงเวลาของความจริง
คุณจำคลาส EmployeeManager
ที่เราพูดถึงข้างต้นได้ไหม สมมติว่าเรานำไปใช้งานแตกต่างออกไป
สมมติว่ารหัสเป็นดังนี้:
public class EmployeeManager { public async Task<Employee> HireEmployee(Employee employee) { // Add Employee entry with basic info in DB // Assume that here we are actually doing the following: // 1. Open a connection to the database // 2. Build a SQL query to insert records in the database // 3. Execute the query // 4. Close connection // Add Emplyee to mailing groups // Assume that here we are actually doing the following: // 1. Open a connection to the mailing server // 3. Do whatever needed to add records to the mailing groups // 4. Close connection // Calculate tax scheme // Assume that here we are actually doing the following: // 1. Do the mathematical operations required // Update tax scheme in DB // Assume that here we are actually doing the following: // 1. Open a connection to the database // 2. Build a SQL query to insert records in the database // 3. Execute the query // 4. Close connection // Calculate holidays // Assume that here we are actually doing the following: // 1. Do the mathematical operations required // Update holidays in DB // Assume that here we are actually doing the following: // 1. Open a connection to the database // 2. Build a SQL query to insert records in the database // 3. Execute the query // 4. Close connection } }
ดังที่เราสังเกตได้จากที่นี่ ความรู้ที่จำเป็นนั้นมีมากมาย เมธอด HireEmployee
จำเป็นต้องรู้หลายสิ่งหลายอย่างเกี่ยวกับโครงสร้างฐานข้อมูล วิธีเปิด/ปิดการเชื่อมต่อ วิธีดำเนินการทางคณิตศาสตร์เพื่อคำนวณภาษีและวันหยุด วิธีเชื่อมต่อ/ยกเลิกการเชื่อมต่อไปยัง/จากเซิร์ฟเวอร์ส่งเมล วิธีเพิ่มลงใน กลุ่มส่งเมล์,...
ความรู้จำนวนมหาศาลประเภทนี้เป็นภาระที่วิธี HireEmployee
แบกรับอยู่
ถ้าจะใช้การเปลี่ยนแปลงใหม่กับตารางฐานข้อมูล Employee
หรือตารางใดๆ ที่เกี่ยวข้อง จะต้องอัปเดตเมธอด HireEmployee
และนั่นหมายความว่าคลาส EmployeeManager
จะได้รับการอัปเดตตามนั้น
หากจะต้องใช้การเปลี่ยนแปลงใหม่กับเซิร์ฟเวอร์การส่งเมล จะต้องอัปเดตเมธอด HireEmployee
และนั่นหมายความว่าคลาส EmployeeManager
จะได้รับการอัปเดตตามนั้น
และอื่นๆ...
ในที่สุด เราก็สรุปได้ว่าคลาส EmployeeManager
อาจมีเหตุผลมากมายในการเปลี่ยนแปลง ซึ่งเป็นข้อบ่งชี้ที่ชัดเจนว่าคลาส EmployeeManager
ละเมิดหลักการความรับผิดชอบเดี่ยว
ตอนนี้ฉันได้ยินคุณพูดว่า:
ตกลงตอนนี้ฉันเข้าใจแล้ว แต่ยังไม่รู้ว่าจะแก้ไขอย่างไร เป็นไปได้ไหม?
ใช่ แน่นอน มันเป็นไปได้ ให้ผมแสดง…
วิธีที่ดีกว่า
กลับไปที่คลาส EmployeeManager
อีกครั้ง หากเราต้องการนำไปใช้ในทางที่ถูกต้องเพื่อให้เป็นไปตามหลักการความรับผิดชอบเดี่ยว เราสามารถทำตามขั้นตอนบางอย่างตามที่ฉันจะแสดงให้คุณดู
ขั้นแรก เราต้องกลับไปสู่เวอร์ชันที่ดีกว่า - แต่ยังไม่ใช่เวอร์ชันที่ดีที่สุด:
public class EmployeeManager { private readonly EmployeeRepository _mEmployeeRepository; private readonly TaxCalculator _mTaxCalculator; private readonly MailingGroupsManager _mMailingGroupsManager; private readonly HolidaysCalculator _mHolidaysCalculator; public async Task<Employee> HireEmployee(Employee employee) { // Add Employee entry with basic info in DB var updatedEmployee = await _mEmployeeRepository.Add(employee); // Add Emplyee to mailing groups updatedEmployee = await _mMailingGroupsManager.Add(updatedEmployee); // Calculate tax scheme decimal tax = await _mTaxCalculator.Calculate(updatedEmployee); // Update tax scheme in DB updatedEmployee = await _mEmployeeRepository.UpdateTax(updatedEmployee, tax); // Calculate holidays byte holidaysCount = await _mHolidaysCalculator.Calculate(updatedEmployee); // Update holidays in DB return await _mEmployeeRepository.UpdateHolidays(updatedEmployee, holidaysCount); } }
ดังที่เราได้กล่าวไปแล้ว ปริมาณความรู้ที่จำเป็นที่นี่ไม่มากเท่าที่เห็นเมื่อมองแวบแรก
อย่างไรก็ตาม นี่เป็นสิ่งที่ดีที่สุดที่เราสามารถทำได้หรือไม่?
จริงๆ แล้วไม่ เราสามารถทำได้ดีกว่าดังนี้:
public class TaxManager { private readonly EmployeeRepository _mEmployeeRepository; private readonly TaxCalculator _mTaxCalculator; public async Task<Employee> UpdateTax(Employee) { // Calculate tax scheme decimal tax = await _mTaxCalculator.Calculate(updatedEmployee); // Update tax scheme in DB return await _mEmployeeRepository.UpdateTax(updatedEmployee, tax); } } public class HolidaysManager { private readonly EmployeeRepository _mEmployeeRepository; private readonly HolidaysCalculator _mHolidaysCalculator; public async Task<Employee> UpdateHolidays(Employee) { // Calculate holidays byte holidaysCount = await _mHolidaysCalculator.Calculate(updatedEmployee); // Update holidays in DB return await _mEmployeeRepository.UpdateHolidays(updatedEmployee, holidaysCount); } } public class EmployeeManager { private readonly EmployeeRepository _mEmployeeRepository; private readonly TaxManager _mTaxManager; private readonly MailingGroupsManager _mMailingGroupsManager; private readonly HolidaysManager _mHolidaysManager; public async Task<Employee> HireEmployee(Employee employee) { // Add Employee entry with basic info in DB var updatedEmployee = await _mEmployeeRepository.Add(employee); // Add Emplyee to mailing groups updatedEmployee = await _mMailingGroupsManager.Add(updatedEmployee); // Update tax scheme updatedEmployee = await _mTaxManager.UpdateTax(updatedEmployee); // Update holidays return await _mHolidaysManager.UpdateHolidays(updatedEmployee); } }
สิ่งที่เราทำที่นี่:
- บทคัดย่อความรู้ในการคำนวณและเพิ่มโครงการภาษีลงในคลาส
TaxManager
ที่แยกจากกัน - บทคัดย่อความรู้ในการคำนวณและเพิ่มวันหยุดลงในคลาส
HolidaysManager
แยกกัน
ซึ่งหมายความว่าคลาส EmployeeManager
รู้น้อยลงกว่าเดิม ตอนนี้ไม่ทราบว่าจะอัปเดตแผนภาษีได้ ต้องใช้คลาส TaxCalculator
ก่อน จากนั้นจึงใช้คลาส EmployeeRespository
เช่นเดียวกับการอัปเดตวันหยุด
ซึ่งจะทำให้คลาส EmployeeManager
มีความรู้น้อยลง และมีเหตุผลในการเปลี่ยนแปลงน้อยลง
ตอนนี้ฉันได้ยินคุณพูดว่า:
อย่างไรก็ตาม เมื่อมีการเปลี่ยนแปลงโดยบังคับให้เราต้องเพิ่มขั้นตอนใหม่ เช่น การเพิ่มพนักงานในโซเชียลเน็ตเวิร์กของบริษัท คลาส
EmployeeManager
ก็จะต้องเปลี่ยนแปลง
ใช่ ฉันเห็นด้วยอย่างยิ่งกับคุณแต่ฉันไม่เห็นว่ามันจะเป็นปัญหา
นี่คือสิ่งที่เราหลีกเลี่ยงไม่ได้เนื่องจากนี่คือบทบาทหลักของคลาส EmployeeManager
โดยจะจัดการขั้นตอนและขั้นตอนในการจ้างพนักงาน
สิ่งที่เราต้องจำไว้เสมอคือหากข้อกำหนดเปลี่ยนแปลง โค้ดบางส่วนก็จะเปลี่ยนไป สิ่งนี้เป็นสิ่งที่หลีกเลี่ยงไม่ได้
อย่างไรก็ตาม สิ่งที่เราพยายามทำคือลดจำนวนโค้ดที่อยู่รอบๆ ที่ต้องเปลี่ยนแปลงหรือแตะต้องให้เหลือน้อยที่สุด
ความคิดสุดท้าย
ในบทความนี้ เราได้กล่าวถึง หลักการความรับผิดชอบเดี่ยว - ซึ่งมีตัวย่อว่า SRP- ของ หลักการที่มั่นคง
เมื่อพูดถึงหลักการนี้ นักพัฒนาหลายคนอาจสับสนว่าอะไรมากหรือน้อย นั่นเป็นสาเหตุที่บทความนี้ทำให้ชัดเจนครั้งแล้วครั้งเล่า
คุ้มค่าที่จะกล่าวถึง คุณต้องจำไว้ว่าในโลกของซอฟต์แวร์และด้วยความเร็วของการเปลี่ยนแปลงที่เพิ่มขึ้น มักจะมีการเปลี่ยนแปลงบางอย่างที่ต้องเปลี่ยนโค้ดเช่นกัน นี่เป็นเรื่องปกติ เป็นสิ่งที่หลีกเลี่ยงไม่ได้ และคุณไม่ควรต่อสู้กับมัน คุณควรปรับตัวเข้ากับมัน
สุดท้ายนี้ ฉันหวังว่าคุณจะสนุกกับการอ่านบทความนี้ในขณะที่ฉันสนุกกับการเขียนมัน
หวังว่าเนื้อหานี้จะเป็นประโยชน์ หากคุณต้องการสนับสนุน:
▶ หากคุณยังไม่ได้เป็นสมาชิก Medium คุณสามารถใช้ ลิงก์การแนะนำของฉัน เพื่อที่ฉันจะได้รับค่าธรรมเนียมส่วนหนึ่งจาก Medium คุณไม่ต้องจ่ายเพิ่มใดๆ
▶ สมัครรับ จดหมายข่าวของฉัน เพื่อรับแนวทางปฏิบัติที่ดีที่สุด บทช่วยสอน คำแนะนำ เคล็ดลับ และสิ่งดีๆ อื่น ๆ อีกมากมายส่งตรงถึงกล่องจดหมายของคุณ
แหล่งข้อมูลอื่นๆ
นี่เป็นแหล่งข้อมูลอื่นๆ ที่คุณอาจพบว่ามีประโยชน์
ยกระดับการเข้ารหัส
ขอบคุณที่เป็นส่วนหนึ่งของชุมชนของเรา! ก่อนที่คุณจะไป:
- 👏 ปรบมือให้เรื่องและติดตามผู้เขียน 👉
- 📰 ดูเนื้อหาเพิ่มเติมใน "สิ่งพิมพ์ Level Up Coding"
- 🔔 ติดตามเรา: Twitter | LinkedIn | จดหมายข่าว
🚀 👉 เข้าร่วมกลุ่มผู้มีความสามารถ Level Up และหางานที่น่าทึ่ง