Ra mắt hai series mới cực hot Trí tuệ nhân tạo A đến ZPython công cụ không thể thiếu khi nghiên cứu Data science, Machine learning.

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

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();
}

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;
    }
}

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";
    }
}

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; 
    }

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();
}

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();

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 (Xem Lập trình hướng đối tượng trong PHP để biết thêm về Trait).

<?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)));
  }
}

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ÁC BÀI VIẾT KHÁC

FirebirD

Đam mê Toán học, Lập trình. Sở thích chia sẻ kiến thức, Phim hài, Bóng đá, Cà phê sáng với bạn bè.

Lưu dữ liệu store Vuex trong Local Storage: Giảm tải đáng kể WebServer

Laravel Eloquent ORM phần 3: xử lý dữ liệu đầu ra

0 Bình luận trong "Null Object - Pattern đơn giản mà đâu cũng gặp"

Thêm bình luận