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 的原型模式提供了一种高效的方式来创建复杂对象的副本。通过合理使用原型模式,可以减少对象创建的开销,并提供灵活的对象复制机制。