Laravel 13 原型模式深度解析
原型模式是一种创建型设计模式,它通过复制现有对象来创建新对象,而不是通过实例化。本文将深入探讨 Laravel 13 中原型模式的高级用法。
原型模式基础
什么是原型模式
原型模式允许通过克隆现有对象来创建新对象,避免了从头创建对象的开销。
1 2 3 4 5 6 7 8
| <?php
namespace App\Contracts;
interface PrototypeInterface { public function clone(): self; }
|
基础原型实现
文档原型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| <?php
namespace App\Prototypes;
use App\Contracts\PrototypeInterface;
class Document implements PrototypeInterface { protected string $title; protected string $content; protected string $author; protected array $metadata = []; protected \DateTime $createdAt;
public function __construct(string $title, string $content, string $author) { $this->title = $title; $this->content = $content; $this->author = $author; $this->createdAt = new \DateTime(); }
public function setTitle(string $title): self { $this->title = $title; return $this; }
public function setContent(string $content): self { $this->content = $content; return $this; }
public function setMetadata(string $key, mixed $value): self { $this->metadata[$key] = $value; return $this; }
public function getTitle(): string { return $this->title; }
public function getContent(): string { return $this->content; }
public function getAuthor(): string { return $this->author; }
public function getMetadata(): array { return $this->metadata; }
public function clone(): self { $clone = new self($this->title, $this->content, $this->author); $clone->metadata = $this->metadata; $clone->createdAt = new \DateTime();
return $clone; } }
|
产品原型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| <?php
namespace App\Prototypes;
use App\Contracts\PrototypeInterface;
class Product implements PrototypeInterface { protected string $name; protected string $description; protected float $price; protected string $category; protected array $attributes = []; protected array $images = [];
public function __construct( string $name, string $description, float $price, string $category ) { $this->name = $name; $this->description = $description; $this->price = $price; $this->category = $category; }
public function setName(string $name): self { $this->name = $name; return $this; }
public function setPrice(float $price): self { $this->price = $price; return $this; }
public function addAttribute(string $key, mixed $value): self { $this->attributes[$key] = $value; return $this; }
public function addImage(string $url): self { $this->images[] = $url; return $this; }
public function getName(): string { return $this->name; }
public function getPrice(): float { return $this->price; }
public function toArray(): array { return [ 'name' => $this->name, 'description' => $this->description, 'price' => $this->price, 'category' => $this->category, 'attributes' => $this->attributes, 'images' => $this->images, ]; }
public function clone(): self { $clone = new self( $this->name . ' (Copy)', $this->description, $this->price, $this->category );
$clone->attributes = $this->attributes; $clone->images = $this->images;
return $clone; } }
|
深克隆与浅克隆
深克隆实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| <?php
namespace App\Prototypes;
use App\Contracts\PrototypeInterface;
class Order implements PrototypeInterface { protected string $orderNumber; protected Customer $customer; protected array $items = []; protected Address $shippingAddress; protected Address $billingAddress;
public function __construct(string $orderNumber, Customer $customer) { $this->orderNumber = $orderNumber; $this->customer = $customer; }
public function addItem(OrderItem $item): self { $this->items[] = $item; return $this; }
public function setShippingAddress(Address $address): self { $this->shippingAddress = $address; return $this; }
public function setBillingAddress(Address $address): self { $this->billingAddress = $address; return $this; }
public function getOrderNumber(): string { return $this->orderNumber; }
public function getItems(): array { return $this->items; }
public function clone(): self { $clone = new self( $this->generateNewOrderNumber(), clone $this->customer );
foreach ($this->items as $item) { $clone->addItem(clone $item); }
if ($this->shippingAddress) { $clone->setShippingAddress(clone $this->shippingAddress); }
if ($this->billingAddress) { $clone->setBillingAddress(clone $this->billingAddress); }
return $clone; }
protected function generateNewOrderNumber(): string { return 'ORD-' . strtoupper(uniqid()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php
namespace App\Prototypes;
class Address { protected string $street; protected string $city; protected string $state; protected string $zipCode; protected string $country;
public function __construct(string $street, string $city, string $state, string $zipCode, string $country) { $this->street = $street; $this->city = $city; $this->state = $state; $this->zipCode = $zipCode; $this->country = $country; }
public function getFullAddress(): string { return "{$this->street}, {$this->city}, {$this->state} {$this->zipCode}, {$this->country}"; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
namespace App\Prototypes;
class Customer { protected string $name; protected string $email; protected string $phone;
public function __construct(string $name, string $email, string $phone) { $this->name = $name; $this->email = $email; $this->phone = $phone; }
public function getName(): string { return $this->name; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
namespace App\Prototypes;
class OrderItem { protected string $productName; protected int $quantity; protected float $unitPrice;
public function __construct(string $productName, int $quantity, float $unitPrice) { $this->productName = $productName; $this->quantity = $quantity; $this->unitPrice = $unitPrice; }
public function getTotal(): float { return $this->quantity * $this->unitPrice; } }
|
原型管理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <?php
namespace App\Services;
use App\Contracts\PrototypeInterface;
class PrototypeManager { protected array $prototypes = [];
public function register(string $name, PrototypeInterface $prototype): self { $this->prototypes[$name] = $prototype; return $this; }
public function unregister(string $name): self { unset($this->prototypes[$name]); return $this; }
public function getPrototype(string $name): ?PrototypeInterface { return $this->prototypes[$name] ?? null; }
public function clone(string $name): ?PrototypeInterface { $prototype = $this->getPrototype($name);
if ($prototype === null) { return null; }
return $prototype->clone(); }
public function has(string $name): bool { return isset($this->prototypes[$name]); }
public function getRegisteredNames(): array { return array_keys($this->prototypes); } }
|
Laravel 模型克隆
使用 PHP clone 关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model { protected $fillable = [ 'name', 'description', 'price', 'category_id', 'sku', 'is_active', ];
public function replicateWithRelations(): self { $clone = $this->replicate(); $clone->sku = $this->generateNewSku(); $clone->save();
foreach ($this->images as $image) { $clonedImage = $image->replicate(); $clonedImage->product_id = $clone->id; $clonedImage->save(); }
foreach ($this->variants as $variant) { $clonedVariant = $variant->replicate(); $clonedVariant->product_id = $clone->id; $clonedVariant->save(); }
return $clone; }
protected function generateNewSku(): string { return strtoupper(uniqid('SKU-')); }
public function images() { return $this->hasMany(ProductImage::class); }
public function variants() { return $this->hasMany(ProductVariant::class); } }
|
页面模板克隆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PageTemplate extends Model { protected $fillable = [ 'name', 'slug', 'layout', 'sections', 'settings', 'is_default', ];
protected $casts = [ 'sections' => 'array', 'settings' => 'array', ];
public function createFromTemplate(array $overrides = []): self { $clone = $this->replicate();
$clone->name = $overrides['name'] ?? $this->name . ' (Copy)'; $clone->slug = $overrides['slug'] ?? $this->generateSlug($clone->name); $clone->is_default = false;
$clone->save();
return $clone; }
protected function generateSlug(string $name): string { return \Str::slug($name) . '-' . substr(uniqid(), -6); } }
|
报表模板克隆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| <?php
namespace App\Prototypes\Reports;
use App\Contracts\PrototypeInterface;
class ReportTemplate implements PrototypeInterface { protected string $name; protected string $type; protected array $columns = []; protected array $filters = []; protected array $sorting = []; protected array $styling = []; protected array $charts = [];
public function __construct(string $name, string $type) { $this->name = $name; $this->type = $type; }
public function addColumn(string $name, string $label, array $options = []): self { $this->columns[] = array_merge([ 'name' => $name, 'label' => $label, ], $options);
return $this; }
public function addFilter(string $field, string $operator, mixed $value): self { $this->filters[] = [ 'field' => $field, 'operator' => $operator, 'value' => $value, ];
return $this; }
public function setSorting(string $field, string $direction = 'asc'): self { $this->sorting = [$field, $direction]; return $this; }
public function addChart(array $chartConfig): self { $this->charts[] = $chartConfig; return $this; }
public function getName(): string { return $this->name; }
public function getType(): string { return $this->type; }
public function getConfig(): array { return [ 'name' => $this->name, 'type' => $this->type, 'columns' => $this->columns, 'filters' => $this->filters, 'sorting' => $this->sorting, 'styling' => $this->styling, 'charts' => $this->charts, ]; }
public function clone(): self { $clone = new self($this->name . ' (Copy)', $this->type);
$clone->columns = $this->columns; $clone->filters = $this->filters; $clone->sorting = $this->sorting; $clone->styling = $this->styling; $clone->charts = $this->charts;
return $clone; } }
|
邮件模板克隆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| <?php
namespace App\Prototypes\Emails;
use App\Contracts\PrototypeInterface;
class EmailTemplate implements PrototypeInterface { protected string $name; protected string $subject; protected string $body; protected array $variables = []; protected array $attachments = []; protected string $fromEmail; protected string $fromName;
public function __construct(string $name, string $subject, string $body) { $this->name = $name; $this->subject = $subject; $this->body = $body; }
public function setFrom(string $email, string $name = ''): self { $this->fromEmail = $email; $this->fromName = $name; return $this; }
public function addVariable(string $name, string $description = ''): self { $this->variables[$name] = $description; return $this; }
public function addAttachment(string $path): self { $this->attachments[] = $path; return $this; }
public function render(array $data = []): string { $body = $this->body;
foreach ($data as $key => $value) { $body = str_replace("{{{$key}}}", $value, $body); }
return $body; }
public function getName(): string { return $this->name; }
public function getSubject(): string { return $this->subject; }
public function clone(): self { $clone = new self( $this->name . ' (Copy)', $this->subject, $this->body );
$clone->variables = $this->variables; $clone->attachments = $this->attachments; $clone->fromEmail = $this->fromEmail; $clone->fromName = $this->fromName;
return $clone; } }
|
工作流模板克隆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| <?php
namespace App\Prototypes\Workflow;
use App\Contracts\PrototypeInterface;
class WorkflowTemplate implements PrototypeInterface { protected string $name; protected string $description; protected array $steps = []; protected array $triggers = []; protected array $conditions = [];
public function __construct(string $name, string $description = '') { $this->name = $name; $this->description = $description; }
public function addStep(WorkflowStep $step): self { $this->steps[] = $step; return $this; }
public function addTrigger(string $event, array $config = []): self { $this->triggers[] = [ 'event' => $event, 'config' => $config, ];
return $this; }
public function addCondition(string $field, string $operator, mixed $value): self { $this->conditions[] = [ 'field' => $field, 'operator' => $operator, 'value' => $value, ];
return $this; }
public function getName(): string { return $this->name; }
public function getSteps(): array { return $this->steps; }
public function toArray(): array { return [ 'name' => $this->name, 'description' => $this->description, 'steps' => array_map(fn($step) => $step->toArray(), $this->steps), 'triggers' => $this->triggers, 'conditions' => $this->conditions, ]; }
public function clone(): self { $clone = new self( $this->name . ' (Copy)', $this->description );
foreach ($this->steps as $step) { $clone->addStep(clone $step); }
$clone->triggers = $this->triggers; $clone->conditions = $this->conditions;
return $clone; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <?php
namespace App\Prototypes\Workflow;
class WorkflowStep { protected string $name; protected string $type; protected array $config = []; protected int $order;
public function __construct(string $name, string $type, int $order = 0) { $this->name = $name; $this->type = $type; $this->order = $order; }
public function setConfig(array $config): self { $this->config = $config; return $this; }
public function getName(): string { return $this->name; }
public function toArray(): array { return [ 'name' => $this->name, 'type' => $this->type, 'config' => $this->config, 'order' => $this->order, ]; } }
|
测试原型模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <?php
namespace Tests\Unit\Prototypes;
use Tests\TestCase; use App\Prototypes\Document; use App\Prototypes\Product; use App\Prototypes\Order; use App\Services\PrototypeManager;
class PrototypeTest extends TestCase { public function test_document_clone(): void { $original = new Document('Original Title', 'Content', 'Author'); $original->setMetadata('key', 'value');
$clone = $original->clone();
$this->assertEquals('Original Title', $clone->getTitle()); $this->assertEquals('Content', $clone->getContent()); $this->assertEquals('Author', $clone->getAuthor()); $this->assertEquals(['key' => 'value'], $clone->getMetadata());
$clone->setTitle('Modified Title'); $this->assertEquals('Original Title', $original->getTitle()); }
public function test_product_clone(): void { $original = new Product('Product Name', 'Description', 99.99, 'Electronics'); $original->addAttribute('color', 'red'); $original->addImage('image1.jpg');
$clone = $original->clone();
$this->assertStringContainsString('(Copy)', $clone->getName()); $this->assertEquals(99.99, $clone->getPrice()); }
public function test_prototype_manager(): void { $manager = new PrototypeManager();
$template = new Document('Template', 'Template Content', 'System'); $manager->register('default_document', $template);
$this->assertTrue($manager->has('default_document'));
$clone = $manager->clone('default_document'); $this->assertInstanceOf(Document::class, $clone); $this->assertNotSame($template, $clone); } }
|
最佳实践
1. 区分深浅克隆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php
class DeepCloneExample implements PrototypeInterface { protected array $nestedData;
public function clone(): self { $clone = clone $this; $clone->nestedData = array_map(function ($item) { return is_object($item) ? clone $item : $item; }, $this->nestedData);
return $clone; } }
|
2. 使用 PHP __clone 魔术方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php
class MagicClone implements PrototypeInterface { protected \DateTime $createdAt; protected array $items = [];
public function __clone() { $this->createdAt = new \DateTime(); $this->items = array_map(fn($item) => clone $item, $this->items); }
public function clone(): self { return clone $this; } }
|
3. 原型注册表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php
class PrototypeRegistry { private static array $prototypes = [];
public static function register(string $key, PrototypeInterface $prototype): void { self::$prototypes[$key] = $prototype; }
public static function get(string $key): ?PrototypeInterface { return isset(self::$prototypes[$key]) ? self::$prototypes[$key]->clone() : null; } }
|
总结
Laravel 13 的原型模式提供了一种高效的方式来创建复杂对象的副本。通过合理使用原型模式,可以减少对象创建的开销,并提供灵活的对象复制机制。