如何用抽象类实现三类产品的多态CRUD操作(禁用条件判断)
Refactoring Product System with Abstract Class & Polymorphism in PHP
Step 1: Create Abstract Base Class Product
Convert your existing Product model into an abstract class that defines shared properties and enforces type-specific methods via abstract declarations. This ensures all product subclasses implement core operations consistently.
abstract class Product { protected $sku; protected $name; protected $price; protected $type; public function __construct(array $data) { $this->sku = $data['sku']; $this->name = $data['name']; $this->price = $data['price']; $this->type = $data['type']; $this->validateTypeSpecificAttributes($data); } // Common getters public function getSku(): string { return $this->sku; } public function getName(): string { return $this->name; } public function getPrice(): float { return $this->price; } public function getType(): string { return $this->type; } // Abstract methods to be implemented by subclasses abstract protected function validateTypeSpecificAttributes(array $data): void; abstract public function save(): bool; abstract public function delete(): bool; abstract public function display(): string; }
Step 2: Implement Product Subclasses
Create a dedicated subclass for each product type, handling type-specific validation, persistence, and display logic.
DVD Product
class DVDProduct extends Product { private $size; protected function validateTypeSpecificAttributes(array $data): void { if (!isset($data['size']) || !is_numeric($data['size']) || $data['size'] <= 0) { throw new InvalidArgumentException("DVD size must be a positive number."); } $this->size = (int)$data['size']; } public function save(): bool { // Database logic to insert DVD-specific data (sku, name, price, type, size) $sql = "INSERT INTO products (sku, name, price, type, size) VALUES (?, ?, ?, ?, ?)"; // Execute query with $this->sku, $this->name, $this->price, $this->type, $this->size return true; // Return actual success status from query } public function delete(): bool { $sql = "DELETE FROM products WHERE sku = ? AND type = ?"; // Execute query with $this->sku, $this->type return true; } public function display(): string { return sprintf( "<div class='product'> <p>SKU: %s</p> <p>Name: %s</p> <p>Price: $%.2f</p> <p>Type: DVD</p> <p>Size: %d MB</p> </div>", htmlspecialchars($this->sku), htmlspecialchars($this->name), $this->price, $this->size ); } }
Furniture Product
class FurnitureProduct extends Product { private $height; private $width; private $length; protected function validateTypeSpecificAttributes(array $data): void { foreach (['height', 'width', 'length'] as $attr) { if (!isset($data[$attr]) || !is_numeric($data[$attr]) || $data[$attr] <= 0) { throw new InvalidArgumentException("Furniture $attr must be a positive number."); } } $this->height = (float)$data['height']; $this->width = (float)$data['width']; $this->length = (float)$data['length']; } public function save(): bool { $sql = "INSERT INTO products (sku, name, price, type, height, width, length) VALUES (?, ?, ?, ?, ?, ?, ?)"; // Execute query with relevant attributes return true; } public function delete(): bool { $sql = "DELETE FROM products WHERE sku = ? AND type = ?"; // Execute query return true; } public function display(): string { return sprintf( "<div class='product'> <p>SKU: %s</p> <p>Name: %s</p> <p>Price: $%.2f</p> <p>Type: Furniture</p> <p>Dimensions: %.2fx%.2fx%.2f cm</p> </div>", htmlspecialchars($this->sku), htmlspecialchars($this->name), $this->price, $this->height, $this->width, $this->length ); } }
Book Product
class BookProduct extends Product { private $weight; protected function validateTypeSpecificAttributes(array $data): void { if (!isset($data['weight']) || !is_numeric($data['weight']) || $data['weight'] <= 0) { throw new InvalidArgumentException("Book weight must be a positive number."); } $this->weight = (float)$data['weight']; } public function save(): bool { $sql = "INSERT INTO products (sku, name, price, type, weight) VALUES (?, ?, ?, ?, ?)"; // Execute query return true; } public function delete(): bool { $sql = "DELETE FROM products WHERE sku = ? AND type = ?"; // Execute query return true; } public function display(): string { return sprintf( "<div class='product'> <p>SKU: %s</p> <p>Name: %s</p> <p>Price: $%.2f</p> <p>Type: Book</p> <p>Weight: %.2f Kg</p> </div>", htmlspecialchars($this->sku), htmlspecialchars($this->name), $this->price, $this->weight ); } }
Step 3: Refactor AddProduct Controller
Eliminate type conditionals by dynamically instantiating the correct subclass based on the submitted product type.
class AddProductController { public function handleRequest() { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { $this->renderForm(); return; } try { $data = $_POST; $type = strtolower($data['type']); $productClass = ucfirst($type) . 'Product'; // Validate valid product subclass exists if (!class_exists($productClass) || !is_subclass_of($productClass, Product::class)) { throw new InvalidArgumentException("Invalid product type."); } $product = new $productClass($data); if ($product->save()) { header("Location: /products"); exit; } else { throw new Exception("Failed to save product."); } } catch (InvalidArgumentException $e) { $this->renderForm(['error' => $e->getMessage()]); } catch (Exception $e) { $this->renderForm(['error' => 'An unexpected error occurred.']); } } private function renderForm(array $data = []) { // Render add product form with error messages (include type dropdown: dvd, furniture, book) } }
Step 4: Refactor Products Controller
Display products without type checks by instantiating the correct subclass for each record and calling its display() method.
class ProductsController { public function index() { $productsData = $this->getAllProductsFromDB(); $products = []; foreach ($productsData as $productData) { $type = strtolower($productData['type']); $productClass = ucfirst($type) . 'Product'; if (class_exists($productClass) && is_subclass_of($productClass, Product::class)) { $products[] = new $productClass($productData); } } $this->renderProducts($products); } private function getAllProductsFromDB(): array { // Fetch all products with their type-specific attributes from database $sql = "SELECT * FROM products"; // Execute query and return array of product data return []; // Replace with actual database results } private function renderProducts(array $products) { echo "<h1>Products</h1>"; foreach ($products as $product) { echo $product->display(); } echo "<a href='/add-product'>Add New Product</a>"; } }
Key Advantages
- No Type Conditionals: All type-specific logic lives in subclasses, keeping controllers clean and uncoupled from product types.
- Extensibility: Adding a new product type only requires creating a new
Productsubclass—no changes to existing controllers. - Maintainability: Validation, persistence, and display logic are centralized per product type, making updates easier.
内容的提问来源于stack exchange,提问作者pedram
相关产品推荐
相关产品推荐

