跳到主要内容

เนื้อหาภาษาไทย / 泰语内容

系统指南 内容已审核

การใช้ Interfaces: เขียน Code ที่ยืดหยุ่น

บทความนี้อธิบายถึง Interfaces ใน PHP ว่าเป็นพิมพ์เขียวที่กำหนดสัญญาของเมธอดที่คลาสต้อง Implement เพื่อให้โค้ดมีความยืดหยุ่น ลดการพึ่งพากัน และรองรับ Polymorphis...

การใช้ Interfaces: เขียน Code ที่ยืดหยุ่น

ในบทที่แล้ว เราได้เรียนรู้เกี่ยวกับ Dependency Injection (DI) ซึ่งเป็นเทคนิคที่ช่วยลดการพึ่งพากัน (Coupling) ระหว่างคลาสต่างๆ ในระบบ ทำให้โค้ดของเรามีความยืดหยุ่นและทดสอบได้ง่ายขึ้น ในบทนี้ เราจะมาเรียนรู้เกี่ยวกับ Interfaces ซึ่งเป็นเครื่องมือสำคัญอีกชิ้นหนึ่งที่ช่วยให้เราเขียนโค้ดที่ยืดหยุ่นและง่ายต่อการเปลี่ยนแปลงได้มากยิ่งขึ้น

Interface คืออะไร?

Interface คือพิมพ์เขียว (Blueprint) ที่กำหนดว่าคลาสที่ Implement Interface นั้นจะต้องมีเมธอดอะไรบ้าง แต่ไม่ได้กำหนดวิธีการทำงานของเมธอดเหล่านั้น Interface เป็นเหมือนสัญญาที่บอกว่า "ถ้าคลาสนี้ Implement Interface นี้ คลาสนี้จะต้องมีเมธอดเหล่านี้แน่นอน" ดังนั้น Interface จึงเป็นวิธีที่ยอดเยี่ยมในการกำหนดพฤติกรรม (Behavior) ของวัตถุโดยไม่ต้องระบุรายละเอียดการทำงานภายใน

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

การ Implement Interface

ใน PHP เราใช้คีย์เวิร์ด interface เพื่อสร้าง Interface และใช้คีย์เวิร์ด implements เพื่อบอกว่าคลาสใด Implement Interface นั้น

<?php

interface LoggerInterface {
  public function log(string $message);
}

class FileLogger implements LoggerInterface {
  private $filename;

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

  public function log(string $message) {
    file_put_contents($this->filename, $message . PHP_EOL, FILE_APPEND);
  }
}

class DatabaseLogger implements LoggerInterface {
  private $dbConnection;

  public function __construct(PDO $dbConnection) {
    $this->dbConnection = $dbConnection;
  }

  public function log(string $message) {
    $stmt = $this->dbConnection->prepare("INSERT INTO logs (message) VALUES (?)");
    $stmt->execute([$message]);
  }
}

ในตัวอย่างนี้ เราได้สร้าง Interface ชื่อ LoggerInterface ซึ่งกำหนดว่าคลาสที่ Implement Interface นี้จะต้องมีเมธอด log() ที่รับพารามิเตอร์เป็น String (ข้อความที่จะบันทึก) จากนั้นเราได้สร้างคลาส FileLogger และ DatabaseLogger ซึ่ง Implement LoggerInterface ทั้งคู่

ข้อสังเกต:

  • คลาส FileLogger จะบันทึกข้อความลงในไฟล์
  • คลาส DatabaseLogger จะบันทึกข้อความลงในฐานข้อมูล

แม้ว่าทั้งสองคลาสจะมีวิธีการบันทึกข้อความที่แตกต่างกัน แต่ทั้งคู่ก็ปฏิบัติตามสัญญาที่ LoggerInterface กำหนดไว้ นั่นคือทั้งคู่มีเมธอด log() ที่รับพารามิเตอร์เป็น String

การใช้ Interface เพื่อ Polymorphism

Polymorphism คือความสามารถของวัตถุในการแสดงออกในรูปแบบที่แตกต่างกัน (Poly = หลาย, Morph = รูปร่าง) Interface ช่วยให้เราสามารถใช้ Polymorphism ได้อย่างมีประสิทธิภาพ

ลองพิจารณาโค้ดต่อไปนี้:

<?php

interface PaymentGatewayInterface {
  public function charge(float $amount): bool;
}

class StripePaymentGateway implements PaymentGatewayInterface {
  public function charge(float $amount): bool {
    // โค้ดสำหรับเรียก API ของ Stripe เพื่อชำระเงิน
    echo "Charging $" . $amount . " via Stripe.\n";
    return true; // สมมติว่าชำระเงินสำเร็จ
  }
}

class PayPalPaymentGateway implements PaymentGatewayInterface {
  public function charge(float $amount): bool {
    // โค้ดสำหรับเรียก API ของ PayPal เพื่อชำระเงิน
    echo "Charging $" . $amount . " via PayPal.\n";
    return true; // สมมติว่าชำระเงินสำเร็จ
  }
}

class PaymentProcessor {
  private $paymentGateway;

  public function __construct(PaymentGatewayInterface $paymentGateway) {
    $this->paymentGateway = $paymentGateway;
  }

  public function processPayment(float $amount) {
    $this->paymentGateway->charge($amount);
  }
}

// ใช้งาน
$stripeGateway = new StripePaymentGateway();
$paypalGateway = new PayPalPaymentGateway();

$processor = new PaymentProcessor($stripeGateway);
$processor->processPayment(100.00); // Output: Charging $100 via Stripe.

$processor = new PaymentProcessor($paypalGateway);
$processor->processPayment(50.00); // Output: Charging $50 via PayPal.

ในตัวอย่างนี้ เราได้สร้าง Interface ชื่อ PaymentGatewayInterface ซึ่งกำหนดว่าคลาสที่ Implement Interface นี้จะต้องมีเมธอด charge() ที่รับพารามิเตอร์เป็น Float (จำนวนเงิน) และคืนค่าเป็น Boolean (บอกว่าการชำระเงินสำเร็จหรือไม่)

จากนั้นเราได้สร้างคลาส StripePaymentGateway และ PayPalPaymentGateway ซึ่ง Implement PaymentGatewayInterface ทั้งคู่ คลาส PaymentProcessor รับ PaymentGatewayInterface เป็น Dependency ใน Constructor และใช้เมธอด charge() เพื่อประมวลผลการชำระเงิน

ข้อดีของการใช้ Interface ในกรณีนี้:

  • ความยืดหยุ่น: เราสามารถเปลี่ยน Payment Gateway ที่ใช้ได้ง่ายๆ โดยไม่ต้องแก้ไขโค้ดของ PaymentProcessor เพียงแค่ส่ง Instance ของ Payment Gateway ที่ต้องการให้กับ Constructor
  • การทดสอบ: เราสามารถสร้าง Mock Payment Gateway เพื่อทดสอบ PaymentProcessor ได้โดยไม่ต้องเชื่อมต่อกับ Payment Gateway จริงๆ
  • การขยาย: หากเราต้องการเพิ่ม Payment Gateway ใหม่ในอนาคต เราเพียงแค่สร้างคลาสใหม่ที่ Implement PaymentGatewayInterface และส่ง Instance ของคลาสนั้นให้กับ PaymentProcessor

Interface ช่วยให้ PaymentProcessor ทำงานกับ Payment Gateway ใดก็ได้ที่ Implement PaymentGatewayInterface นั่นคือ PaymentProcessor ไม่สนใจว่า Payment Gateway จะทำงานอย่างไร ขอเพียงแค่ Payment Gateway มีเมธอด charge() ที่รับพารามิเตอร์และคืนค่าตามที่ Interface กำหนดไว้

เหตุผลเชิงออกแบบ, Trade-offs และแนวปฏิบัติที่ดี

เหตุผลเชิงออกแบบ:

  • Decoupling: Interface ช่วยลดการพึ่งพากันระหว่างคลาส ทำให้โค้ดมีความยืดหยุ่นและง่ายต่อการเปลี่ยนแปลง
  • Abstraction: Interface ช่วยให้เราสามารถซ่อนรายละเอียดการทำงานภายในของคลาส และโฟกัสที่พฤติกรรมของคลาส
  • Testability: Interface ช่วยให้เราสามารถสร้าง Mock Objects เพื่อทดสอบโค้ดได้อย่างง่ายดาย
  • Extensibility: Interface ช่วยให้เราสามารถเพิ่ม Functionality ใหม่ๆ ให้กับระบบได้อย่างง่ายดาย โดยไม่ต้องแก้ไขโค้ดเดิม

Trade-offs:

  • Complexity: การใช้ Interface อาจทำให้โค้ดมีความซับซ้อนมากขึ้น โดยเฉพาะอย่างยิ่งสำหรับโปรเจกต์ขนาดเล็ก
  • Over-Engineering: การใช้ Interface มากเกินไป อาจทำให้โค้ดมีความซับซ้อนโดยไม่จำเป็น

แนวปฏิบัติที่ดี:

  • Principle of Least Astonishment: Interface ควรจะทำในสิ่งที่ผู้ใช้งานคาดหวัง
  • Single Responsibility Principle: Interface ควรจะมี Responsibility เดียว
  • Interface Segregation Principle: Interface ไม่ควรบังคับให้คลาส Implement เมธอดที่คลาสไม่ได้ใช้

สรุป

Interface เป็นเครื่องมือที่มีประโยชน์อย่างมากในการเขียนโค้ดที่ยืดหยุ่นและง่ายต่อการเปลี่ยนแปลง Interface ช่วยให้เราสามารถลด Coupling, เพิ่ม Abstraction, และทำให้โค้ดของเราสามารถทดสอบและขยายได้ง่ายขึ้น อย่างไรก็ตาม เราควรใช้ Interface อย่างระมัดระวัง และพิจารณา Trade-offs ที่เกี่ยวข้องก่อนที่จะตัดสินใจใช้ Interface ในโปรเจกต์ของเรา

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

Office.in.th Custom Service

想基于此结构开发或定制系统?

Office.in.th 承接企业级 Web Application、API Integration 以及政府/企业管理系统的设计与开发,并提供长期支持。

查看软件开发服务