初识设计模式——原型模式(Prototype Pattern)
原型模式(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; } }
$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; } }
$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 属性也会被修改,这可能导致绘制结果不符合预期。
- 深克隆优势:深克隆会递归地复制对象的所有属性,包括引用类型的属性。这样,克隆对象和原对象的所有属性都有各自独立的内存空间,修改克隆对象的属性不会影响原对象,能更好地保证数据的一致性。
从业务需求角度
- 业务逻辑依赖:不同的业务场景对克隆对象和原对象之间的关系有不同的要求。如果业务逻辑要求克隆对象和原对象完全独立,互不影响,那么就需要使用深克隆。
- 例如,在一个文档编辑系统中,用户复制一份文档进行修改,修改后的文档不应影响原文档的内容,此时就需要对文档对象进行深克隆。
- 数据共享需求:如果业务场景允许克隆对象和原对象共享部分数据,以节省资源和提高性能,那么浅克隆可能更合适。
- 例如,在一个电商系统中,商品的分类信息对于同一类别的商品是相同的。当创建新的商品对象时,可以使用浅克隆共享分类信息对象,避免重复创建相同的分类信息。
总结
综上所述,在原型模式中,需要根据具体的业务需求、数据一致性要求以及性能和资源的考虑,谨慎选择使用深克隆还是浅克隆。