post-image

Null Object – Pattern đơn giản mà đâu cũng gặp

Tổng quan

1. Null Object Pattern là gì?

Trong lập trình chúng ta thường kiểm tra một đối tượng xem có bằng null, false hay một giá trị vô hướng nào đó trước khi thực hiện các phương thức của đối tượng để tránh lỗi null pointer exception. Null không phải là một đối tượng, nó là một giá trị, các kiểm tra trên so sánh một đối tượng với một giá trị, nó mất đi tính đối tượng trong lập trình hướng đối tượng. Null Object pattern không phải là một Gang of Four Design Pattern, nhưng nó xuất hiện nhiều đến nỗi các lập trình viên muốn đưa nó thành một design pattern. Việc áp dụng Null Object Pattern có một số lợi ích như sau: – Code trở nên đơn giản hơn

  • Giảm khả năng xảy ra lỗi null pointer exception
  • Giảm bớt các điều kiện sử dụng trong unit testingÁp dụng Null Object Pattern, các phương thức sẽ trả về một đối tượng hoặc một NullObject thay thế cho giá trị null.

2. Null Object Pattern UML

Null Object pattern UML

3. Ví dụ áp dụng Null Object pattern

Chúng ta cùng xem xét ví dụ về quản lý người dùng trong hệ thống sau đây, đầu tiên chúng ta có một UserInterface.

interface UserInterface { public function setId($id); public function getId(); public function setName($name); public function getName(); public function setEmail($email); public function getEmail(); }
Code language: PHP (php)

Khi đó class User sẽ implement UserInterface và định nghĩa các phương thức được nêu ra trong UserInterface:

class User implements UserInterface { private $id; private $name; private $email; public function __construct($name, $email) { $this->setName($name); $this->setEmail($email); } public function setId($id) { if ($this->id !== null) { throw new BadMethodCallException("ID của người dùng rỗng!"); } if (!is_int($id) || $id < 1) { throw new InvalidArgumentException("ID của người dùng sai định dạng."); } $this->id = $id; return $this; } public function getId() { return $this->id; } public function setName($name) { if (strlen($name) < 10 || strlen($name) > 30) { throw new InvalidArgumentException("Tên người dùng phải có độ dài lớn hơn 10 và nhỏ hơn 30."); } $this->name = $name; return $this; } public function getName() { return $this->name; } public function setEmail($email) { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException("Sai định dạng email."); } $this->email = $email; return $this; } public function getEmail() { return $this->email; } }
Code language: PHP (php)

Tiếp theo chúng ta có class NullUser cũng implement UserInterface nhưng các phương thức của nó là không làm gì.

class NullUser implements UserInterface { public function setId($id) { } public function getId() { return "ID của NullUser"; } public function setName($name) { } public function getName() { return "Name của NullUser"; } public function setEmail($email) { } public function getEmail() { return "Email của NullUser"; } }
Code language: PHP (php)

Như vậy chúng ta có thể trả về một NullUser khi tạo người dùng mà các thông số nhập vào là null.

private function createUser($row) { if (!$row) { return new NullUser; } $user = new User($row["name"], $row["email"]); $user->setId($row["id"]); return $user; }
Code language: PHP (php)

Nếu không sử dụng NullUser mỗi khi gọi các phương thức của User chúng ta cần kiểm tra xem có null hay không?

// Lấy người dùng có ID bằng 1 trong hệ thống $user = $userMapper->fetchById(1); if ($user !== null) { echo $user->getName() . " " . $user->getEmail(); }
Code language: PHP (php)

Sử dụng NullUser chúng ta không cần phải kiểm tra mỗi khi có các thao tác với class User:

$user = $userMapper->fetchById("ID phải là số, nhập chữ xem sao..."); echo $user->getName() . " " . $user->getEmail();
Code language: PHP (php)

4. Null Object pattern tiêu chuẩn

Trong ví dụ phần 3 chúng ta đã xây dựng một class NullUser theo như sơ đồ UML, tuy nhiên class này không thể sử dụng cho tất cả các class mà nó chỉ sử dụng riêng cho class User do có những thiết lập cứng bên trong. Chúng ta cần xây dựng một NullObject tiêu chuẩn để có thể sử dụng cho mọi class yêu cầu. NullObject sẽ không thể là một class, abstract class hoặc interface do các class khác chỉ cần một số tính năng của NullObject và Trait là lựa chọn.

<?php trait NullPattern { public $__value__; function __call($name, $arguments) { if( is_object($this->__value__) && method_exists($this->__value__, $name) ) return new NullObject(call_user_func_array(array($this->__value__, $name), $arguments)); else return new NullObject(); } function __get($name) { if( is_object($this->__value__) && property_exists($this->__value__, $name) ) return new NullObject($this->__value__->$name); else return new NullObject(); } function __set($name, $value) { if( is_object($this->__value__) ) $this->__value__->$name = $value; } function __isset($name) { return is_object($this->__value__) && property_exists($this->__value__, $name); } function __unset($name) { if( is_object($this->__value__) ) unset($this->__value__->$name); } function __toString() { if( is_array($this->__value__) ) return implode(', ', $this->__value__); else return (string) $this->__value__; } function present() { return !empty($this->__value__); } function or_default($default) { if( $this->present() ) { return $this->__value__; } else { return $default; } } function __invoke($key) { if( !is_array($this->__value__) ) return new NullObject(); if( func_num_args() > 1) $this->__value__[$key] = func_get_arg(1); if( array_key_exists($key, $this->__value__) ) return new NullObject($this->__value__[$key]); else return new NullObject(); } } class NullObject { use NullPattern; function __construct($value=null) { if( $value ) { if( is_assoc($value) ) $value = (object) $value; $this->__value__ = $value; } } } if( !function_exists('is_assoc') ) { function is_assoc($v) { return is_array($v) && array_diff_key($v,array_keys(array_keys($v))); } }
Code language: HTML, XML (xml)

Với NullObject tiêu chuẩn ở trên, mỗi khi một đối tượng cần xử lý null, chúng ta chỉ cần đơn giản là sử dụng lại Trait NullPattern. Thật là đơn giản!

5. Lời kết

Các pattern là thành quả của nhiều năm kinh nghiệm từ các lập trình viên đúc kết ra, việc hiểu và áp dụng các pattern sẽ giúp cho lập trình nhanh chóng, tránh được lỗi và unit testing cũng đơn giản hơn nhiều. Null Object pattern rất đơn giản nhưng các ứng dụng của chúng ta nên áp dụng do công việc xử lý đối tượng là cực nhiều trong lập trình hướng đối tượng.

Cảm ơn các bạn đã đọc.

Các bạn có thể tham khảo các bài viết hay về Laravel tại đây.


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.

Nguồn tham khảo: Allaravel

Trả lời

Email của bạn sẽ không được hiển thị công khai.