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

Design Patterns: แนวทางการแก้ปัญหาที่พบบ่อย

บทความนี้แนะนำ Design Patterns ซึ่งเป็นแนวทางการแก้ปัญหาการออกแบบซอฟต์แวร์ที่พบบ่อยและได้รับการพิสูจน์แล้วว่ามีประสิทธิภาพ โดย Design Patterns ไม่ใช่โค้ดสำเร็จร...

Design Patterns: แนวทางการแก้ปัญหาที่พบบ่อย

หลังจากที่เราได้เรียนรู้หลักการออกแบบ OOP ที่ดีอย่าง SOLID Principles โดยเฉพาะ Dependency Inversion Principle (DIP) แล้ว เราจะพบว่ามีปัญหาบางอย่างที่เกิดขึ้นซ้ำๆ ในการพัฒนาซอฟต์แวร์ ซึ่งปัญหาเหล่านี้มักจะมีแนวทางการแก้ไขที่เป็นมาตรฐานและได้รับการพิสูจน์แล้วว่ามีประสิทธิภาพ นั่นคือ Design Patterns

Design Patterns คืออะไร?

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

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

เหตุผลที่ต้องใช้ Design Patterns:

  • Code Reusability: ช่วยให้เรานำแนวทางการแก้ปัญหาที่ได้รับการพิสูจน์แล้วกลับมาใช้ใหม่ได้ ลดการคิดค้นวิธีแก้ปัญหาเองตั้งแต่ต้น
  • Improved Communication: สร้างภาษาเดียวกันระหว่างนักพัฒนา ทำให้สื่อสารและเข้าใจโค้ดได้ง่ายขึ้น
  • Better Architecture: ช่วยให้เราออกแบบซอฟต์แวร์ที่มีความยืดหยุ่น บำรุงรักษาง่าย และขยายได้ในอนาคต
  • Proven Solutions: เป็นแนวทางการแก้ปัญหาที่ได้รับการทดสอบและใช้งานจริงมาแล้ว มั่นใจได้ในระดับหนึ่งว่ามีประสิทธิภาพ

ประเภทของ Design Patterns

Design Patterns สามารถแบ่งออกเป็น 3 ประเภทหลักๆ ตามจุดประสงค์ในการใช้งาน:

  1. Creational Patterns: เกี่ยวกับการสร้าง Object อย่างมีประสิทธิภาพ
  2. Structural Patterns: เกี่ยวกับการจัดโครงสร้าง Class และ Object เพื่อสร้างระบบที่ใหญ่และซับซ้อน
  3. Behavioral Patterns: เกี่ยวกับการกำหนดปฏิสัมพันธ์ระหว่าง Object และการจัดการ Algorithm

แต่ละประเภทก็จะมี Design Patterns ย่อยๆ อีกมากมาย แต่ในบทเรียนนี้เราจะมาดูตัวอย่าง Design Patterns ที่สำคัญและใช้งานบ่อยในแต่ละประเภท เพื่อให้เห็นภาพรวมและเข้าใจแนวคิดพื้นฐาน

ตัวอย่าง Design Patterns

ต่อไปนี้เป็นตัวอย่าง Design Patterns ที่พบได้บ่อย พร้อมคำอธิบายและตัวอย่างโค้ด (อย่างง่าย) ใน PHP:

1. Singleton Pattern (Creational)

ปัญหา: ต้องการให้มี Object เพียงตัวเดียวของ Class นั้นๆ และต้องการให้สามารถเข้าถึง Object นั้นได้จากทุกที่ในโปรแกรม

แนวทาง: สร้าง Class ที่มี Constructor เป็น `private` เพื่อป้องกันการสร้าง Object จากภายนอก และมี Static Method ที่ทำหน้าที่สร้างและคืนค่า Object ตัวเดียวของ Class นั้น

class Database {
    private static $instance;
    private $connection;

    private function __construct() {
        // Connect to database (example)
        $this->connection = new PDO("mysql:host=localhost;dbname=mydb", "user", "password");
    }

    public static function getInstance(): Database {
        if (self::$instance === null) {
            self::$instance = new Database();
        }

        return self::$instance;
    }

    public function getConnection(): PDO {
        return $this->connection;
    }
}

// Usage
$db1 = Database::getInstance();
$db2 = Database::getInstance();

// $db1 และ $db2 คือ Object ตัวเดียวกัน
var_dump($db1 === $db2); // Output: bool(true)

เหตุผลเชิงออกแบบ: Singleton Pattern เหมาะสำหรับกรณีที่ต้องการควบคุมการเข้าถึงทรัพยากรที่มีจำกัด เช่น Database Connection หรือ Configuration File

Trade-offs: อาจทำให้เกิดปัญหาในการทดสอบ (Unit Testing) และอาจละเมิด Single Responsibility Principle (SRP) หาก Class Singleton ทำหน้าที่หลายอย่างเกินไป

2. Factory Pattern (Creational)

ปัญหา: ต้องการสร้าง Object โดยไม่ต้องระบุ Class ที่จะสร้างโดยตรง

แนวทาง: สร้าง Factory Class ที่มีหน้าที่ในการสร้าง Object ตามเงื่อนไขที่กำหนด

interface Animal {
    public function makeSound(): string;
}

class Dog implements Animal {
    public function makeSound(): string {
        return "Woof!";
    }
}

class Cat implements Animal {
    public function makeSound(): string {
        return "Meow!";
    }
}

class AnimalFactory {
    public static function createAnimal(string $type): Animal {
        switch ($type) {
            case 'dog':
                return new Dog();
            case 'cat':
                return new Cat();
            default:
                throw new InvalidArgumentException("Invalid animal type: " . $type);
        }
    }
}

// Usage
$dog = AnimalFactory::createAnimal('dog');
echo $dog->makeSound(); // Output: Woof!

$cat = AnimalFactory::createAnimal('cat');
echo $cat->makeSound(); // Output: Meow!

เหตุผลเชิงออกแบบ: Factory Pattern ช่วยลดการพึ่งพา (Coupling) ระหว่าง Class ที่เรียกใช้ (Client) กับ Class ที่ถูกสร้าง (Concrete Classes) ทำให้ Client ไม่จำเป็นต้องรู้รายละเอียดเกี่ยวกับการสร้าง Object

Trade-offs: อาจเพิ่มความซับซ้อนให้กับโค้ดเล็กน้อย และต้องแก้ไข Factory Class ทุกครั้งที่เพิ่ม Class ใหม่

3. Strategy Pattern (Behavioral)

ปัญหา: ต้องการเลือก Algorithm หรือพฤติกรรมที่แตกต่างกันใน Runtime

แนวทาง: สร้าง Interface สำหรับ Algorithm และ Implement Algorithm แต่ละแบบใน Class ที่แตกต่างกัน จากนั้นให้ Client เลือก Algorithm ที่ต้องการใช้ผ่าน Interface

interface PaymentStrategy {
    public function pay(float $amount): string;
}

class CreditCardPayment implements PaymentStrategy {
    private $cardNumber;
    private $expiryDate;
    private $cvv;

    public function __construct(string $cardNumber, string $expiryDate, string $cvv) {
        $this->cardNumber = $cardNumber;
        $this->expiryDate = $expiryDate;
        $this->cvv = $cvv;
    }

    public function pay(float $amount): string {
        return "Paid $" . $amount . " using Credit Card: " . $this->cardNumber;
    }
}

class PayPalPayment implements PaymentStrategy {
    private $email;

    public function __construct(string $email) {
        $this->email = $email;
    }

    public function pay(float $amount): string {
        return "Paid $" . $amount . " using PayPal: " . $this->email;
    }
}

class ShoppingCart {
    private $paymentStrategy;

    public function setPaymentStrategy(PaymentStrategy $paymentStrategy) {
        $this->paymentStrategy = $paymentStrategy;
    }

    public function checkout(float $amount): string {
        return $this->paymentStrategy->pay($amount);
    }
}

// Usage
$cart = new ShoppingCart();

$creditCardPayment = new CreditCardPayment("1234-5678-9012-3456", "12/24", "123");
$cart->setPaymentStrategy($creditCardPayment);
echo $cart->checkout(100.00); // Output: Paid $100 using Credit Card: 1234-5678-9012-3456

$payPalPayment = new PayPalPayment("test@example.com");
$cart->setPaymentStrategy($payPalPayment);
echo $cart->checkout(50.00); // Output: Paid $50 using PayPal: test@example.com

เหตุผลเชิงออกแบบ: Strategy Pattern ช่วยให้เราแยก Algorithm ออกจาก Client ทำให้ Client สามารถเปลี่ยน Algorithm ได้โดยไม่ต้องแก้ไขโค้ด

Trade-offs: อาจเพิ่มจำนวน Class ในโปรเจกต์ และต้องระบุ Algorithm ที่ต้องการใช้ใน Client

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

ในบทความนี้เราได้เรียนรู้เกี่ยวกับ Design Patterns และตัวอย่างบางส่วนที่สำคัญ ในบทความถัดไป เราจะลงลึกในเรื่องของการทดสอบ Code OOP ด้วย Unit Testing เพื่อให้มั่นใจว่า Code ของเราทำงานได้อย่างถูกต้องและมีคุณภาพ

Office.in.th Custom Service

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

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

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