Strategy Pattern: เลือก Algorithm ใน Runtime
จากบทที่แล้วที่เราได้เรียนรู้เกี่ยวกับ Factory Pattern ที่ช่วยให้เราสร้าง Object ได้อย่างยืดหยุ่น โดยไม่ต้องระบุ Class ที่ต้องการสร้างโดยตรง ในบทนี้ เราจะมาทำความรู้จักกับอีกหนึ่ง Design Pattern ที่มีประโยชน์อย่างมาก นั่นคือ Strategy Pattern ที่จะช่วยให้เราสามารถเลือก Algorithm หรือพฤติกรรมที่ต้องการใช้งานได้ในขณะที่โปรแกรมกำลังทำงาน (Runtime) โดยไม่ต้องแก้ไขโค้ดหลัก
Strategy Pattern คืออะไร?
Strategy Pattern เป็น Design Pattern ประเภท Behavioral ที่ช่วยให้เรากำหนดกลุ่มของ Algorithm ที่สามารถสลับเปลี่ยนกันได้ และทำให้ Client สามารถเลือก Algorithm ที่ต้องการใช้งานได้ในขณะที่โปรแกรมกำลังทำงาน โดยไม่ต้องแก้ไขโค้ดของ Client เอง
หลักการทำงานของ Strategy Pattern:
- กำหนด Interface หรือ Abstract Class ที่เป็นตัวกลางสำหรับ Algorithm ทั้งหมด
- สร้าง Class ที่ Implement Interface หรือสืบทอด Abstract Class เพื่อเป็นตัวแทนของแต่ละ Algorithm
- สร้าง Context Class ที่เก็บ Algorithm ที่เลือกใช้งาน และมี Method สำหรับเรียกใช้งาน Algorithm นั้น
- Client สามารถเลือก Algorithm ที่ต้องการใช้งาน และกำหนดให้กับ Context Class
ทำไมต้องใช้ Strategy Pattern?
การใช้ Strategy Pattern มีข้อดีหลายประการ:
- ความยืดหยุ่น: สามารถสลับเปลี่ยน Algorithm ได้ง่าย โดยไม่ต้องแก้ไขโค้ดหลัก
- การแยกส่วน: Algorithm แต่ละตัวถูกแยกออกเป็น Class ทำให้โค้ดมีความชัดเจนและง่ายต่อการบำรุงรักษา
- การ Reuse: Algorithm สามารถนำกลับมาใช้ใหม่ได้ใน Context Class อื่นๆ
- ลดความซับซ้อน: ช่วยลดความซับซ้อนของโค้ด โดยการแยก Algorithm ออกจาก Context Class
การ Implement Strategy Pattern ใน PHP
เพื่อให้เข้าใจ Strategy Pattern มากยิ่งขึ้น เรามาดูตัวอย่างการ Implement ใน PHP กัน
ตัวอย่าง: ระบบการคำนวณภาษี
สมมติว่าเรามีระบบที่ต้องคำนวณภาษี โดยมี Algorithm การคำนวณภาษีหลายแบบ เช่น ภาษีมูลค่าเพิ่ม (VAT), ภาษีหัก ณ ที่จ่าย (Withholding Tax), และภาษีเงินได้บุคคลธรรมดา (Personal Income Tax) เราสามารถใช้ Strategy Pattern เพื่อให้ระบบของเราสามารถเลือก Algorithm การคำนวณภาษีที่เหมาะสมได้ใน Runtime
1. สร้าง Interface สำหรับ Strategy:
interface TaxStrategy {
public function calculateTax(float $amount): float;
}
Interface TaxStrategy กำหนด Method calculateTax() ที่รับจำนวนเงินเป็น Parameter และคืนค่าภาษีที่คำนวณได้
2. สร้าง Class สำหรับแต่ละ Strategy:
class VATStrategy implements TaxStrategy {
private float $vatRate;
public function __construct(float $vatRate) {
$this->vatRate = $vatRate;
}
public function calculateTax(float $amount): float {
return $amount * $this->vatRate;
}
}
class WithholdingTaxStrategy implements TaxStrategy {
private float $withholdingRate;
public function __construct(float $withholdingRate) {
$this->withholdingRate = $withholdingRate;
}
public function calculateTax(float $amount): float {
return $amount * $this->withholdingRate;
}
}
Class VATStrategy และ WithholdingTaxStrategy Implement Interface TaxStrategy และกำหนด Algorithm การคำนวณภาษีของตัวเอง
3. สร้าง Context Class:
class TaxCalculator {
private TaxStrategy $taxStrategy;
public function __construct(TaxStrategy $taxStrategy) {
$this->taxStrategy = $taxStrategy;
}
public function setTaxStrategy(TaxStrategy $taxStrategy): void {
$this->taxStrategy = $taxStrategy;
}
public function calculateTax(float $amount): float {
return $this->taxStrategy->calculateTax($amount);
}
}
Class TaxCalculator เป็น Context Class ที่เก็บ Instance ของ TaxStrategy และมี Method calculateTax() ที่เรียกใช้งาน Algorithm การคำนวณภาษีของ Strategy ที่เลือกใช้งาน
4. ใช้งาน Strategy Pattern:
// เลือก Strategy การคำนวณภาษี
$vatStrategy = new VATStrategy(0.07); // VAT 7%
$withholdingTaxStrategy = new WithholdingTaxStrategy(0.03); // Withholding Tax 3%
// สร้าง TaxCalculator และกำหนด Strategy
$taxCalculator = new TaxCalculator($vatStrategy);
// คำนวณภาษี
$amount = 1000;
$vatTax = $taxCalculator->calculateTax($amount);
echo "VAT Tax: " . $vatTax . "\n"; // Output: VAT Tax: 70
// เปลี่ยน Strategy
$taxCalculator->setTaxStrategy($withholdingTaxStrategy);
$withholdingTax = $taxCalculator->calculateTax($amount);
echo "Withholding Tax: " . $withholdingTax . "\n"; // Output: Withholding Tax: 30
จากตัวอย่าง เราสามารถสลับเปลี่ยน Algorithm การคำนวณภาษีได้ง่ายๆ โดยการกำหนด Strategy ใหม่ให้กับ TaxCalculator โดยไม่ต้องแก้ไขโค้ดของ TaxCalculator เอง
ข้อดีและข้อเสียของ Strategy Pattern
ข้อดี:
- Open/Closed Principle: สามารถเพิ่ม Algorithm ใหม่ได้ โดยไม่ต้องแก้ไขโค้ดที่มีอยู่
- Single Responsibility Principle: Algorithm แต่ละตัวถูกแยกออกเป็น Class ทำให้โค้ดมีความชัดเจน
- ความยืดหยุ่น: สามารถสลับเปลี่ยน Algorithm ได้ง่ายใน Runtime
ข้อเสีย:
- ความซับซ้อน: อาจทำให้โค้ดมีความซับซ้อนมากขึ้น หากมี Strategy จำนวนมาก
- Client ต้องรู้ Strategy ที่มี: Client ต้องทราบ Algorithm ที่มีอยู่ เพื่อที่จะเลือกใช้งานได้ถูกต้อง
เหตุผลเชิงออกแบบ, Trade-offs, และแนวปฏิบัติที่ดี
- การเลือก Strategy: การเลือก Strategy ที่เหมาะสมขึ้นอยู่กับความต้องการของระบบ อาจใช้ Factory Pattern ร่วมกับ Strategy Pattern เพื่อช่วยในการสร้าง Strategy Instance
- การจัดการ Strategy: หากมี Strategy จำนวนมาก อาจใช้ Service Locator หรือ Dependency Injection Container เพื่อช่วยในการจัดการ Strategy Instance
- การทดสอบ: ควรทดสอบ Algorithm แต่ละตัวอย่างละเอียด เพื่อให้มั่นใจว่าทำงานได้อย่างถูกต้อง
- Trade-offs: Strategy Pattern เพิ่มความยืดหยุ่น แต่ก็เพิ่มความซับซ้อนของโค้ดด้วย ควรพิจารณาความเหมาะสมกับขนาดและความซับซ้อนของระบบ
สรุป
Strategy Pattern เป็น Design Pattern ที่มีประโยชน์อย่างมากในการจัดการ Algorithm ที่สามารถสลับเปลี่ยนกันได้ ช่วยให้โค้ดมีความยืดหยุ่น ลดความซับซ้อน และง่ายต่อการบำรุงรักษา ในบทถัดไป เราจะมาเรียนรู้เกี่ยวกับ Observer Pattern ที่ช่วยให้เราสามารถแจ้งเตือน Object อื่นๆ เมื่อมีการเปลี่ยนแปลงเกิดขึ้น