Design Patterns - Prototype
Bài đăng này đã không được cập nhật trong 3 năm
Chào mọi người, hôm nay mình muốn giới thiệu một design pattern là Prototype. Nó nằm trong nhóm Creational Patterns - tức là những pattern giúp cho hệ thống độc lập với việc khởi tạo và trình diễn đối tượng. Trên viblo đã có bài viết về Builder pattern và Factory pattern nên mình không đề cập nữa. Giờ mình đi vào chi tiết.
Định nghĩa
Thôi thì cứ làm cho đúng thủ tục
The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects.
Mục đích của pattern này là tạo ra một đối tượng mới bằng cách sao chép một nguyên mẫu (nôm na là một đối tượng khác).
Thông thường khi tạo một đối tượng mới thì các thuộc tính chưa được khởi tạo (ngoại trừ giá trị default hoặc khởi tạo trong constructor). Tuy nhiên, có lúc thay vì tạo một đối tượng mới thì ta muốn sao chép một đối tượng khác đã tồn tại. Nếu đó là điều bạn muốn thì Prototype pattern là dành cho bạn. H3h3. Nôm na từ prototype là khuôn mẫu, tức là đối tượng mới sẽ có bộ khung giống với đối tượng được sao chép.
Vậy thì chúng ta thường sử dụng Prototype pattern khi nào? Mình sẽ điểm qua một số lý do như sau:
- muốn tạo đối tượng run-time
- chúng ta muốn truyền đối tượng vào một hàm nào đó để xử lý, thay vì truyền đối tượng gốc có thể ảnh hưởng dữ liệu thì ta có thể truyền đối tượng sao chép
- chúng ta thường sử dụng prototype pattern khi chi phí của việc tạo mới đối tượng (bằng cách sử dụng toán tử new) là quá lớn
Ví dụ
Thực ra đây là một pattern rất đơn giản, thay vì dùng new thì ta dùng clone, chỉ vậy thôi
abstract class AbstractBookPrototype {
protected $title;
protected $category;
abstract public function __clone();
}
class ITBookPrototype extends AbstractBookPrototype
{
protected $title;
protected $category = 'IT';
public function __construct()
{
}
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
public function __clone()
{
# code...
}
}
$itBook = new ITBookPrototype();
$itBook->setTitle('Learning PHP Design Pattern');
$itClone = clone $itBook;
$itClone->setTitle('PHP Master');
print("IT Book:\n");
print_r($itBook);
print("IT Book Clone:\n");
print_r($itClone);
Output:
IT Book:
FRDP\Prototype\ITBookPrototype Object
(
[title:protected] => Learning PHP Design Pattern
[category:protected] => IT Book
)
IT Clone:
FRDP\Prototype\ITBookPrototype Object
(
[title:protected] => PHP Master
[category:protected] => IT Book
)
Vậy là chúng ta cài đặt xong Prototype pattern, tất nhiên là đoạn code trên còn rất lởm, nhưng đại khái nó là thế =)) À một lưu ý là khi gọi hàm clone thì hàm khởi tạo không được gọi nữa, nó chỉ gọi đúng một lần khi dùng new thôi.
Shallow copy vs Deep copy
Object cloning có 2 kiểu: một là shallow copy, và hai là deep copy. Shallow copy có thể đạt được bằng việc sử dụng built-in function clone của PHP. Tức là khi một đối tượng được sao chép, PHP sẽ thực hiện shallow copy tất cả thuộc tính của đối tượng đó. Do đó nếu đối tượng mới thay đổi thuộc tính này thì đối tượng gốc cũng sẽ bị ảnh hưởng. Deep copy, hay còn gọi là de-referenced, tức là đối tượng sao chép và đối tượng gốc tham chiếu đến 2 đối tượng hoàn toàn khác nhau. Để đạt được điều này thì PHP tạo ra magic method __clone() Hàm này là hàm callback, được gọi sau khi một đối tượng được copy bằng hàm clone. Dưới đây là source code một class của Laravel 5.5:
Illuminate/Database/Eloquent/Builder.php
<?php
namespace Illuminate\Database\Eloquent;
use Closure;
use BadMethodCallException;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Pagination\Paginator;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Concerns\BuildsQueries;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Query\Builder as QueryBuilder;
class Builder
{
use BuildsQueries, Concerns\QueriesRelationships;
/**
* The base query builder instance.
*
* @var \Illuminate\Database\Query\Builder
*/
protected $query;
...
/**
* Create a new Eloquent query builder instance.
*
* @param \Illuminate\Database\Query\Builder $query
* @return void
*/
public function __construct(QueryBuilder $query)
{
$this->query = $query;
}
...
/**
* Force a clone of the underlying query builder when cloning.
*
* @return void
*/
public function __clone()
{
$this->query = clone $this->query;
}
}
Ở đây ta có thể thấy Laravel sử dụng deep copy bằng hàm __(clone). Đoạn code
$this->query = clone $this->query;
chính là để đảm bảo thuộc tính query của đối tượng mới và đối tượng gốc sẽ tham chiếu đến 2 object khác nhau. Đây là một bug mà trước đây Laravel chưa fix, có nhắc đến ở đây https://github.com/laravel/framework/issues/1336 Và mọi người cũng có thể thấy database chính là nơi mà hay sử dụng clone nhất, thường dùng để xử lý query.
Vậy là mình đã giới thiệu xong về Prototype pattern và cách clone object trong php. Bài viết còn khá sơ sài, source code chưa có test nên mình chưa up lên. Thank you for reading.
All rights reserved