初识设计模式——原型模式(Prototype Pattern)

cuixiaogang

原型模式(Prototype Pattern)是一种创建型设计模式,它允许通过复制现有的对象来创建新对象,而不是从头开始创建。这种模式适用于创建对象的成本较高,或者创建过程复杂的场景。在原型模式中,有一个原型对象,其他对象可以通过克隆这个原型对象来创建。

原型模式的常用应用场景

  • 对象创建成本高:如数据库连接、网络连接等对象的创建,克隆已有对象比重新创建更高效。
  • 避免重复初始化:当对象的初始化过程复杂,且多个对象的初始状态相同,可以使用原型模式。
  • 动态配置对象:在运行时根据不同的配置创建对象。

代码示例

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
<?php

class Client {
private $name;

public function __construct($name) {
// 超级费时或者超级费资源
sleep(5);


$this->name = $name;
}

public function getName() {
return $this->name;
}

public function setName($name) {
$this->name = $name;
}
}

echo date("H:i:s") . "\n";
$clientA = new Client("A");
echo date("H:i:s") . $clientA->getName() . "\n";

// 未使用原型模式
$clientB = new Client("B");
echo date("H:i:s") . "------------------未使用原型模式\n";
echo $clientB->getName() . "\n";

// 使用了原型模式
$clientC = clone $clientA;
$clientC->setName("C");
echo date("H:i:s") . "------------------使用了原型模式\n";
echo date("H:i:s") . $clientC->getName() . "\n";

执行结果

原型模式的优缺点

优点

  • 性能提升:当创建对象的过程复杂且耗时,使用克隆操作可以显著提高性能。
  • 简化创建过程:避免了重复执行复杂的初始化代码,直接克隆已有对象即可。
  • 可扩展性:可以在运行时动态地添加或删除原型。

缺点

  • 克隆方法实现复杂:对于包含复杂引用类型的对象,实现克隆方法可能会很复杂。
  • 深克隆与浅克隆问题:需要根据需求正确处理深克隆和浅克隆,否则可能会导致数据不一致。

PHP中深克隆与浅克隆

浅克隆(Shallow Copy)

浅克隆只是复制对象的属性值。当对象的属性是值类型(像整数、字符串、布尔值等)时,会复制其值;而当属性是引用类型(如对象、数组)时,只会复制引用,也就是克隆对象和原对象的引用类型属性会指向同一个内存地址。下面举个详细的例子来说明

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
<?php
class Address {
public $street;

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

class Person {
public $name;
public $address;

public function __construct($name, Address $address) {
$this->name = $name;
$this->address = $address;
}
}

// 创建一个 Person 对象
$originalAddress = new Address("123 Main St");
$originalPerson = new Person("John", $originalAddress);

// 浅克隆
$clonedPerson = clone $originalPerson;

echo "Original Person's Street: ". $originalPerson->address->street. "\n";
echo "Cloned Person's Street: ". $clonedPerson->address->street. "\n";
echo "---------------------------------------------------". "\n";

// 修改克隆对象的地址属性
$clonedPerson->address->street = "456 Elm St";

echo "Original Person's Street: ". $originalPerson->address->street. "\n";
echo "Cloned Person's Street: ". $clonedPerson->address->street. "\n";

执行结果

对克隆对象的 address 属性的 street 进行修改,结果原对象的 address 属性的 street 也被改变了,这是因为浅克隆只是复制了引用。

深克隆(Deep Copy)

深克隆会递归地复制对象的所有属性,包含引用类型的属性。也就是说,克隆对象和原对象的所有属性都会有各自独立的内存空间,修改克隆对象的属性不会影响原对象。同样的例子,这次使用的事深克隆。

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
class Address {
public $street;

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

class Person {
public $name;
public $address;

public function __construct($name, Address $address) {
$this->name = $name;
$this->address = $address;
}

public function __clone() {
$this->address = clone $this->address;
}
}

// 创建一个 Person 对象
$originalAddress = new Address("123 Main St");
$originalPerson = new Person("John", $originalAddress);

// 深克隆
$clonedPerson = clone $originalPerson;

echo "Original Person's Street: ". $originalPerson->address->street. "\n";
echo "Cloned Person's Street: ". $clonedPerson->address->street. "\n";
echo "---------------------------------------------------". "\n";

// 修改克隆对象的地址属性
$clonedPerson->address->street = "456 Elm St";

echo "Original Person's Street: ". $originalPerson->address->street. "\n";
echo "Cloned Person's Street: ". $clonedPerson->address->street. "\n";

执行结果

  • 创建 $originalPerson 对象后,使用 clone 关键字进行克隆,由于重写了 __clone() 方法,所以实现了深克隆。
  • 修改克隆对象的 address 属性的 street,原对象的 address 属性的 street 不会受影响。

原型模式如何选择克隆方式

在原型模式中,需要特别注意深克隆和浅克隆的问题。下面从不同角度进行详细分析:

从数据一致性角度

  • 浅克隆风险:浅克隆仅复制对象属性的值,对于引用类型的属性,只是复制引用,而非对象本身。这意味着克隆对象和原对象的引用类型属性会指向同一内存地址。如果在后续操作中修改了克隆对象中引用类型属性的值,原对象中对应的属性值也会随之改变,可能破坏数据的独立性和一致性。
    • 例如,在一个图形绘制系统中,每个图形对象都有一个 Style 对象来存储其样式信息(如颜色、线条粗细等)。若使用浅克隆创建新的图形对象,当修改克隆图形的 Style 属性时,原图形的 Style 属性也会被修改,这可能导致绘制结果不符合预期。
  • 深克隆优势:深克隆会递归地复制对象的所有属性,包括引用类型的属性。这样,克隆对象和原对象的所有属性都有各自独立的内存空间,修改克隆对象的属性不会影响原对象,能更好地保证数据的一致性。

从业务需求角度

  • 业务逻辑依赖:不同的业务场景对克隆对象和原对象之间的关系有不同的要求。如果业务逻辑要求克隆对象和原对象完全独立,互不影响,那么就需要使用深克隆。
    • 例如,在一个文档编辑系统中,用户复制一份文档进行修改,修改后的文档不应影响原文档的内容,此时就需要对文档对象进行深克隆。
  • 数据共享需求:如果业务场景允许克隆对象和原对象共享部分数据,以节省资源和提高性能,那么浅克隆可能更合适。
    • 例如,在一个电商系统中,商品的分类信息对于同一类别的商品是相同的。当创建新的商品对象时,可以使用浅克隆共享分类信息对象,避免重复创建相同的分类信息。

总结

综上所述,在原型模式中,需要根据具体的业务需求、数据一致性要求以及性能和资源的考虑,谨慎选择使用深克隆还是浅克隆。