ข้ามไปยังเนื้อหาหลัก
คู่มือระบบ เนื้อหาผ่านการตรวจสอบแล้ว

Open/Closed Principle (OCP): เปิดรับการขยาย แต่ปิดรับการแก้ไข

บทความนี้กล่าวถึงหลักการ Open/Closed Principle (OCP) ซึ่งเน้นว่าส่วนประกอบของซอฟต์แวร์ควรเปิดรับการขยายความสามารถใหม่ๆ ได้โดยไม่ต้องแก้ไขโค้ดเดิม เพื่อลดความเสี...

Open/Closed Principle (OCP): เปิดรับการขยาย แต่ปิดรับการแก้ไข

หลังจากที่เราได้เรียนรู้เรื่อง Single Responsibility Principle (SRP) ที่เน้นให้ Class มีหน้าที่เดียวไปแล้ว ในบทนี้เราจะมาทำความเข้าใจหลักการ Open/Closed Principle (OCP) ซึ่งเป็นอีกหนึ่งหลักการที่สำคัญในการออกแบบ Object-Oriented Programming (OOP) ที่ดี

ความหมายของ OCP

Open/Closed Principle (OCP) กล่าวว่า "Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification." หรือแปลเป็นไทยได้ว่า "ส่วนประกอบของซอฟต์แวร์ (คลาส, โมดูล, ฟังก์ชัน, ฯลฯ) ควรเปิดรับการขยาย แต่ปิดรับการแก้ไข"

ฟังดูอาจจะขัดแย้งกันเล็กน้อย แต่ความหมายที่แท้จริงคือ เราควรออกแบบ Class ให้สามารถเพิ่มความสามารถใหม่ๆ ได้ (open for extension) โดยไม่ต้องแก้ไข Code เดิมที่มีอยู่ (closed for modification) การทำเช่นนี้จะช่วยลดความเสี่ยงในการเกิด Bug ใหม่จากการแก้ไข Code เดิม และทำให้ Code ของเรามีความยืดหยุ่นและง่ายต่อการบำรุงรักษามากขึ้น

ตัวอย่างการละเมิด OCP

ลองพิจารณา Class Order ที่ใช้คำนวณราคารวมของ Order โดยมีเงื่อนไขการคิดส่วนลดที่แตกต่างกันไปตามประเภทของลูกค้า:

class Order {
    private $customerType;
    private $totalAmount;

    public function __construct(string $customerType, float $totalAmount) {
        $this->customerType = $customerType;
        $this->totalAmount = $totalAmount;
    }

    public function calculateDiscount(): float {
        if ($this->customerType === 'regular') {
            return $this->totalAmount * 0.05; // ส่วนลด 5% สำหรับลูกค้าทั่วไป
        } elseif ($this->customerType === 'premium') {
            return $this->totalAmount * 0.10; // ส่วนลด 10% สำหรับลูกค้าพรีเมียม
        } elseif ($this->customerType === 'vip') {
            return $this->totalAmount * 0.15; // ส่วนลด 15% สำหรับลูกค้า VIP
        } else {
            return 0; // ไม่มีส่วนลด
        }
    }

    public function getTotalPrice(): float {
        return $this->totalAmount - $this->calculateDiscount();
    }
}

// ตัวอย่างการใช้งาน
$order = new Order('premium', 1000);
echo "ราคาสุทธิ: " . $order->getTotalPrice() . PHP_EOL; // Output: ราคาสุทธิ: 900

ใน Code นี้ หากเราต้องการเพิ่มประเภทลูกค้าใหม่ เช่น 'corporate' ที่มีส่วนลด 20% เราจะต้องแก้ไข Code ภายใน calculateDiscount() ซึ่งเป็นการละเมิด OCP เพราะเรากำลังแก้ไข Code เดิมที่มีอยู่

ปัญหาของการละเมิด OCP:

  • ความเสี่ยงในการเกิด Bug: การแก้ไข Code เดิมอาจทำให้เกิด Bug ใหม่ที่ไม่คาดคิด
  • ความยากในการทดสอบ: การเปลี่ยนแปลง Code เดิมทำให้ต้องทำการทดสอบซ้ำทั้งหมด
  • ความซับซ้อน: Code จะมีความซับซ้อนมากขึ้นเมื่อมีเงื่อนไขมากขึ้น
  • ความยากในการบำรุงรักษา: Code ที่ซับซ้อนทำให้ยากต่อการบำรุงรักษาและแก้ไขในระยะยาว

การแก้ไข OCP

เราสามารถแก้ไขปัญหาการละเมิด OCP ได้โดยการใช้หลักการ Abstraction และ Polymorphism โดยการสร้าง Interface หรือ Abstract Class ที่กำหนดสัญญาสำหรับการคำนวณส่วนลด และให้ Class ที่รับผิดชอบในการคำนวณส่วนลดของแต่ละประเภทลูกค้า Implement Interface หรือ Extend Abstract Class นั้น

ตัวอย่างการแก้ไขโดยใช้ Interface:

interface DiscountCalculator {
    public function calculateDiscount(float $totalAmount): float;
}

class RegularCustomerDiscount implements DiscountCalculator {
    public function calculateDiscount(float $totalAmount): float {
        return $totalAmount * 0.05;
    }
}

class PremiumCustomerDiscount implements DiscountCalculator {
    public function calculateDiscount(float $totalAmount): float {
        return $totalAmount * 0.10;
    }
}

class VIPCustomerDiscount implements DiscountCalculator {
    public function calculateDiscount(float $totalAmount): float {
        return $totalAmount * 0.15;
    }
}

class CorporateCustomerDiscount implements DiscountCalculator {
    public function calculateDiscount(float $totalAmount): float {
        return $totalAmount * 0.20;
    }
}

class Order {
    private $discountCalculator;
    private $totalAmount;

    public function __construct(DiscountCalculator $discountCalculator, float $totalAmount) {
        $this->discountCalculator = $discountCalculator;
        $this->totalAmount = $totalAmount;
    }

    public function getTotalPrice(): float {
        return $this->totalAmount - $this->discountCalculator->calculateDiscount($this->totalAmount);
    }
}

// ตัวอย่างการใช้งาน
$regularDiscount = new RegularCustomerDiscount();
$order = new Order($regularDiscount, 1000);
echo "ราคาสุทธิ: " . $order->getTotalPrice() . PHP_EOL; // Output: ราคาสุทธิ: 950

$premiumDiscount = new PremiumCustomerDiscount();
$order = new Order($premiumDiscount, 1000);
echo "ราคาสุทธิ: " . $order->getTotalPrice() . PHP_EOL; // Output: ราคาสุทธิ: 900

$corporateDiscount = new CorporateCustomerDiscount();
$order = new Order($corporateDiscount, 1000);
echo "ราคาสุทธิ: " . $order->getTotalPrice() . PHP_EOL; // Output: ราคาสุทธิ: 800

ใน Code ที่แก้ไขแล้ว เราได้สร้าง Interface DiscountCalculator ที่กำหนดสัญญาสำหรับการคำนวณส่วนลด และสร้าง Class ที่ Implement Interface นี้สำหรับแต่ละประเภทลูกค้า เมื่อเราต้องการเพิ่มประเภทลูกค้าใหม่ เราเพียงแค่สร้าง Class ใหม่ที่ Implement DiscountCalculator โดยไม่ต้องแก้ไข Code ใน Class Order

อธิบายเพิ่มเติม:

  • DiscountCalculator: เป็น Interface ที่กำหนดว่า Class ที่ต้องการคำนวณส่วนลดต้องมี Method calculateDiscount
  • RegularCustomerDiscount, PremiumCustomerDiscount, VIPCustomerDiscount, CorporateCustomerDiscount: เป็น Class ที่ Implement DiscountCalculator และให้ Logic การคำนวณส่วนลดที่แตกต่างกัน
  • Order: รับ DiscountCalculator ผ่าน Constructor (Dependency Injection ซึ่งเราจะเรียนในบทต่อๆ ไป) ทำให้ Order สามารถทำงานร่วมกับ Class ที่ Implement DiscountCalculator ได้ทุก Class

ข้อดีของการแก้ไข OCP:

  • ลดความเสี่ยงในการเกิด Bug: ไม่ต้องแก้ไข Code เดิม ทำให้ลดความเสี่ยงในการเกิด Bug ใหม่
  • ง่ายต่อการทดสอบ: สามารถทดสอบ Class ใหม่ได้โดยไม่ต้องทดสอบ Class เดิมซ้ำ
  • มีความยืดหยุ่น: สามารถเพิ่มความสามารถใหม่ๆ ได้ง่าย โดยไม่ต้องแก้ไข Code เดิม
  • ง่ายต่อการบำรุงรักษา: Code ที่มีความยืดหยุ่นทำให้ง่ายต่อการบำรุงรักษาและแก้ไขในระยะยาว

สรุป

Open/Closed Principle (OCP) เป็นหลักการที่สำคัญในการออกแบบ OOP ที่ดี โดยการออกแบบ Class ให้เปิดรับการขยาย แต่ปิดรับการแก้ไข จะช่วยลดความเสี่ยงในการเกิด Bug ใหม่ เพิ่มความยืดหยุ่น และทำให้ Code ของเราง่ายต่อการบำรุงรักษามากขึ้น การใช้ Abstraction และ Polymorphism เป็นเทคนิคที่สำคัญในการปฏิบัติตาม OCP

ในบทถัดไป เราจะมาเรียนรู้หลักการ Liskov Substitution Principle (LSP) ซึ่งเป็นอีกหนึ่งหลักการที่สำคัญในการออกแบบ OOP ที่ดี

Office.in.th Custom Service

ต้องการพัฒนาระบบหรือปรับแต่งจากโครงสร้างนี้?

Office.in.th รับพัฒนาและออกแบบ Enterprise Web Application, API Integration และระบบบริหารงานภาครัฐ/เอกชน พร้อมการสนับสนุนระยะยาว

ดูบริการรับพัฒนาซอฟต์แวร์