ข้อดีของรูปแบบกลยุทธ์


15

ทำไมจึงเป็นประโยชน์ในการใช้รูปแบบกลยุทธ์หากคุณสามารถเขียนโค้ดของคุณใน if / then cases?

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

นอกจากนี้มันหมายความว่าอัลกอริทึมที่จะเปลี่ยนที่รันไทม์?


2
เป็นการบ้านหรือไม่? จะเป็นการดีกว่าถ้าระบุไว้ล่วงหน้า
Fuhrmanator

2
@Fuhrmanator ไม่เป็นไร
Armon Safai

คำตอบ:


20

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

ดังนั้นด้วยการซ้อนกันif/elseคุณจะพบกับการทดสอบมากมายในส่วนหนึ่งของรหัสของคุณในขณะที่ Strategy จะมีการทดสอบเล็กน้อยสำหรับกลยุทธ์ที่ง่ายกว่า ด้วยวิธีหลังมันง่ายกว่าที่จะมีการครอบคลุมที่ดีกว่าเพราะมันยากที่จะพลาดเส้นทางการประมวลผล

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

นี่คือรูปแบบกลยุทธ์:

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

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

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

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

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


1
if/elseบล็อกยังลดความสามารถในการอ่านรหัสได้อีกด้วย สำหรับรูปแบบกลยุทธ์หลักการเปิด / ปิดเป็นมูลค่าการกล่าวขวัญ IMO
Maciej Chałapuk

1
การทดสอบเป็นเหตุผลสำคัญ (ส่วนใหญ่) ทุกสาขาในรหัสของคุณควรได้รับการทดสอบ ยิ่งifคุณมีมากเท่าไหร่ก็ยิ่งมีเส้นทางที่เป็นไปได้มากขึ้นเท่านั้นการทดสอบที่คุณต้องเขียนและวิธีอื่น ๆ ที่ล้มเหลว ถ้าฉันจะพูดถึงโยคี Berra ปลาย: "ถ้าคุณมาทางแยกถนนเอามันไป" สิ่งนี้ใช้กับการทดสอบหน่วยอย่างชาญฉลาด นอกจากนี้ยังมีifข้อความมากมายที่หมายความว่าคุณมีแนวโน้มที่จะทำซ้ำตรรกะสำหรับเงื่อนไขเหล่านั้นเพิ่มการทดสอบโหลดของคุณและเพิ่มความเสี่ยงของการเกิดข้อบกพร่อง
Greg Burghardt

ขอบคุณสำหรับคำตอบ เหตุใดฉันจึงไม่สามารถใช้วิธีแยกต่างหากสำหรับอัลกอริทึมที่แตกต่างในคลาสเดียวกันได้
Armon Safai

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

1
คุณชัดเจนไหมว่าทำไมการทดสอบจึงง่ายกว่า ตัวอย่างสำหรับการปรับโครงสร้างคำสั่ง case (หรือถ้า / จากนั้น) เป็นวิธี polymorphic (พื้นฐานสำหรับกลยุทธ์) นั้นง่ายต่อการทดสอบ refactoring.com/catalog/replaceConditionalWithPolymorphism.htmlถ้าฉันรู้เงื่อนไขทั้งหมดที่จะทดสอบฉันจะเขียนข้อสอบสำหรับแต่ละข้อ ถ้าฉันมีกลยุทธ์ฉันต้องยกตัวอย่างและดำเนินการอย่างใดอย่างหนึ่งสำหรับแต่ละ วิธีการทดสอบกลยุทธ์ง่ายกว่ากันอย่างไร เราไม่ได้พูดถึง ifs ที่ซ้อนกันที่ซับซ้อนเมื่อคุณปรับโครงสร้างกลยุทธ์
Fuhrmanator

5

ทำไมจึงเป็นประโยชน์ในการใช้รูปแบบกลยุทธ์หากคุณสามารถเขียนโค้ดของคุณใน if / then cases?

บางครั้งคุณควรใช้ถ้า / แล้ว มันเป็นรหัสง่าย ๆ ที่อ่านง่าย

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

ปัญหาอื่น ๆ คือการมีเพศสัมพันธ์ ด้วยการใช้ if / then การใช้งานทั้งหมดจะเชื่อมโยงกับการใช้งานนั้นทำให้ยากต่อการเปลี่ยนแปลงในอนาคต โดยการใช้กลยุทธ์การมีเพศสัมพันธ์เท่านั้นคือการเชื่อมต่อของกลยุทธ์


เกิดอะไรขึ้นกับการแก้ไขโค้ดใน if / then code คุณจะไม่ต้องแก้ไขโค้ดในรูปแบบกลยุทธ์หรือไม่ถ้าคุณตัดสินใจที่จะเปลี่ยนวิธีการทำงานของอัลกอริทึม?
Armon Safai

@ armonsafai - หากคุณปรับเปลี่ยนกลยุทธ์คุณเพียงแค่ต้องทดสอบกลยุทธ์ ถ้าคุณปรับเปลี่ยนทุกขั้นตอนวิธีการที่คุณจะต้องทดสอบขั้นตอนวิธีการทั้งหมด ถ้าคุณเพิ่มกลยุทธ์ใหม่คุณต้องทดสอบกลยุทธ์ หากคุณเพิ่มเงื่อนไขใหม่คุณต้องทดสอบเงื่อนไขทั้งหมด
Telastyn

4

กลยุทธ์มีประโยชน์เมื่อif/thenเงื่อนไขเป็นไปตามประเภทดังที่อธิบายไว้ในhttp://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html

เงื่อนไขการตรวจสอบประเภทมักจะมีความซับซ้อนของวัฏจักรสูงดังนั้นฉันจะไม่พูดว่ากลยุทธ์ช่วยปรับปรุงสิ่งต่าง ๆ ที่จำเป็น

เหตุผลหลักสำหรับกลยุทธ์อธิบายไว้ในหนังสือ GoF หน้า 383 ที่นำเสนอรูปแบบ:

ใช้รูปแบบกลยุทธ์เมื่อ

...

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

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

ด้วยif/thenเงื่อนไขคุณจำเป็นต้องเปลี่ยนรหัสของคลาสที่มีตรรกะตามเงื่อนไข ดังที่กล่าวไว้ในคำตอบอื่น ๆ บางครั้งสิ่งนี้ก็ใช้ได้เมื่อคุณไม่ต้องการเพิ่มความซับซ้อนเพื่อรองรับการเพิ่มฟังก์ชั่นใหม่ (ปลั๊กอิน) โดยไม่ต้องรวบรวมใหม่


3

[... ] ถ้าคุณสามารถเขียนโค้ดของคุณใน if / then cases?

นั่นคือประโยชน์ที่ยิ่งใหญ่ที่สุดของรูปแบบกลยุทธ์ ไม่มีเงื่อนไข

คุณต้องการให้คลาส / วิธีการ / ฟังก์ชั่นของคุณง่ายและสั้นที่สุด รหัสย่อนั้นง่ายต่อการทดสอบและอ่านง่ายมาก

เงื่อนไข ( if/ elseif/ else) ทำให้ชั้นเรียนของคุณ / วิธีการ / ฟังก์ชั่นนานเพราะมักจะมีรหัสที่หนึ่งประเมินการตัดสินใจที่จะเป็นส่วนหนึ่งที่แตกต่างจากที่ประเมินการตัดสินใจที่จะtruefalse


ข้อดีอีกอย่างของรูปแบบกลยุทธ์คือสามารถใช้ซ้ำได้ตลอดทั้งโครงการของคุณ

เมื่อใช้รูปแบบการออกแบบกลยุทธ์คุณมีแนวโน้มที่จะมีคอนเทนเนอร์ IoC บางประเภทซึ่งคุณได้รับการนำไปใช้งานที่ต้องการของอินเทอร์เฟซบางทีโดยgetById(int id)วิธีการที่สมาชิกidอาจเป็นสมาชิกแจงนับ

ซึ่งหมายความว่าการสร้างการใช้งานจะอยู่ในที่เดียวในรหัสของคุณ

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

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


นอกจากนี้มันหมายความว่าอัลกอริทึมที่จะเปลี่ยนที่รันไทม์?

ในตัวอย่างของฉันidอาจเป็นตัวแปรที่บรรจุโดยอิงจากอินพุตของผู้ใช้ หากผู้ใช้คลิกที่ปุ่ม A, แล้วid = 2ถ้าเขาคลิกที่ปุ่ม B id = 8แล้ว

เนื่องจากidค่าที่แตกต่างกันการใช้งานอินเทอร์เฟซที่แตกต่างกันนั้นได้รับจากคอนเทนเนอร์ IoC และรหัสทำการดำเนินการที่แตกต่างกัน


ขอบคุณสำหรับคำตอบ เหตุใดฉันจึงไม่สามารถใช้วิธีแยกต่างหากสำหรับอัลกอริทึมที่แตกต่างในคลาสเดียวกันได้
Armon Safai

@ArmonSafai จะแยกวิธีการแก้ปัญหาอะไรจริง ๆ หรือไม่ ฉันไม่คิดอย่างนั้น คุณกำลังย้ายปัญหาจากที่หนึ่งไปยังอีกที่หนึ่งและการตัดสินใจว่าจะใช้วิธีใดในการโทรขึ้นอยู่กับผลลัพธ์ของเงื่อนไข อีกครั้งif/ elseif/ elseรัฐ เหมือนก่อนหน้านี้แค่อยู่คนละที่
Andy

ดังนั้นกรณี if / then จะเป็นหลักใช่ไหม? คุณจะไม่ต้องใช้ถ้า / หรือกรณีเป็นหลักสำหรับรูปแบบกลยุทธ์เช่นกัน?
Armon Safai

1
@ArmonSafai ไม่คุณจะไม่ คุณจะมีสวิตช์ในidตัวแปรในgetByIdวิธีการซึ่งจะส่งกลับการใช้งานที่เฉพาะเจาะจง ทุกครั้งที่คุณต้องมีการใช้อินเทอร์เฟซคุณจะขอให้คอนเทนเนอร์ IoC ส่งมอบให้คุณ
Andy

1
@ArmonSafai คุณอาจมีวิธีการgetSortByEnumType(SortEnum type)คืนค่าการใช้งานของSortอินเทอร์เฟซที่มีวิธีการgetSortTypeส่งกลับSortEnumตัวแปรและการเก็บรวบรวมเป็นพารามิเตอร์และgetSortByEnumTypeวิธีการอีกครั้งจะมีสวิตช์บนtypeพารามิเตอร์กลับคุณอัลกอริทึมการเรียงลำดับที่ถูกต้อง หากคุณต้องการเพิ่มอัลกอริทึมการเรียงลำดับใหม่คุณจะต้องแก้ไข enum และวิธีการเดียวเท่านั้น และคุณถูกตั้งค่า
Andy

2

ทำไมจึงเป็นประโยชน์ในการใช้รูปแบบกลยุทธ์หากคุณสามารถเขียนโค้ดของคุณใน if / then cases?

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

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

นอกจากนี้มันหมายความว่าอัลกอริทึมที่จะเปลี่ยนที่รันไทม์?

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


1

ไม่มีอะไรผิดปกติกับif/elsese ในหลายกรณีif/elseเป็นวิธีที่ง่ายและอ่านได้ง่ายที่สุดในการแสดงตรรกะ ดังนั้นวิธีที่คุณอธิบายนั้นใช้ได้อย่างสมบูรณ์ในหลายกรณี (นอกจากนี้ยังสามารถทดสอบได้อย่างสมบูรณ์แบบดังนั้นจึงไม่เป็นปัญหา)

แต่มีบางกรณีที่รูปแบบกลยุทธ์อาจปรับปรุงการบำรุงรักษาของรหัสโดยรวม ตัวอย่างเช่น:

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

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

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

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