Trong lập trình hướng đối tượng, chúng ta thường xuyên kiểm tra một đối tượng xem có bằng null hay không 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 (NPE). Null không phải là một đối tượng, nó là một giá trị, các thao tác 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. Các đối tượng null này cần được kiểm tra để đảm bảo rằng chúng không rỗng trong khi truy cập bất kỳ thành viên nào hoặc gọi bất kỳ phương thức nào. Điều này là do các thành viên hoặc phương thức không thể được gọi trên các đối tượng null. Để tránh vấn đề này, chúng ta có thể sử dụng Null Object Pattern. Thay vì sử dụng giá trị null, chúng ta trả về một Null Object thể hiện hành vi mặc định của đối tượng.
Nội dung
Null Object Pattern là gì?
The intent of a Null Object is to encapsulate the absence of an object by providing a substitutable alternative that offers suitable default do nothing behavior. In short, a design where nothing will come of nothing.
Null Object Pattern là một trong những Pattern thuộc nhóm hành vi (Behavior Pattern). Null Object pattern không phải là một Gang of Four Design Pattern.
Tư tưởng của Null Object là sử dụng một đối tượng Null đặc biệt để gói gọn sự vắng mặt của một thể hiện bằng cách cung cấp một sự thay thế hành xử theo cách thụ động phù hợp. Về cơ bản, thay vì sử dụng giá trị null, hãy tạo một đối tượng không có tác động đến bất cứ thứ gì. Từ đó, người sử dụng không cần quan tâm đến việc reference đó có null hay không? Vì code implement bên trong đã xử lý mặc định rồi.
Nó là một đối tượng có hành vi trung lập hoặc không rõ ràng. Nó được gọi là Null Object bởi vì theo mặc định, nó không làm gì cả và nó giúp tránh các trường hợp Null Pointer Exception.
Cài đặt Null Object Pattern như thế nào?
Các thành phần tham gia Null Object Pattern:
- AbstractObject : định nghĩa các hành vi mà một đối tượng có thể có.
- RealObject : một triển khai thực sự của AbstractObject thực hiện một số hành động thực tế.
- NullObject : một triển khai không làm gì hoặc trả về giá trị mặc định của AbstractObject, để cung cấp một đối tượng không null cho Client.
- Client : nhận được một triển khai của AbstractObject và sử dụng nó. Nó không thực sự quan tâm đó là một NullObject hoặc RealObject vì cả hai đều được sử dụng theo cùng một cách.
Ví dụ Null Object Pattern với ứng dụng tính thuế (Tax)
Giả sử chúng ta cần tính giá tiền của sản phẩm sau khi cộng thêm thuế theo từng quốc gia. Chúng ta có một danh sách các quốc gia có áp dụng thuế, các quốc gia không nằm trong danh sách thì mặc định thuế sẽ là 0.
Chương trình của chúng ta như sau:
Tax.java
package com.gpcoder.patterns.behavioral.nullobject.tax; public interface Tax { String getCountry(); double apply(double price); }
RealTax.java
package com.gpcoder.patterns.behavioral.nullobject.tax; public class RealTax implements Tax { private String country; private double vat; public RealTax(String country, double vat) { this.country = country; this.vat = vat; } @Override public String getCountry() { return country; } @Override public double apply(double price) { return price * vat; } }
NullTax.java
package com.gpcoder.patterns.behavioral.nullobject.tax; public class NullTax implements Tax { private String country = "UNKNOWN_COUNTRY"; public NullTax(String country) { if (country != null) { this.country = country; } } @Override public String getCountry() { return country; } @Override public double apply(double price) { return price * 1; } }
TaxFactory.java
package com.gpcoder.patterns.behavioral.nullobject.tax; import java.util.HashMap; import java.util.Map; public class TaxFactory { private static final Map<String, Double> VATS = new HashMap<>(); static { VATS.put("Switzerland", 1.3); VATS.put("Germany", 1.45); VATS.put("Vietnam", 1.1); } public static Tax getTaxByCountry(String country) { Double vat = VATS.get(country); if (vat != null) { return new RealTax(country, vat); } return new NullTax(country); } }
NullObjectPatternExample.java
package com.gpcoder.patterns.behavioral.nullobject.tax; public class NullObjectPatternExample { public static void main(String[] args) { final double price = 1000; applyCountryTaxToPrice(price, "Switzerland"); applyCountryTaxToPrice(price, "Germany"); applyCountryTaxToPrice(price, "Vietnam"); applyCountryTaxToPrice(price, "Thailand"); applyCountryTaxToPrice(price, null); } public static void applyCountryTaxToPrice(double price, String country) { Tax tax = TaxFactory.getTaxByCountry(country); System.out.println(tax.getCountry() + ": " + tax.apply(price)); } }
Output của chương trình:
Switzerland: 1300.0 Germany: 1450.0 Vietnam: 1100.0 Thailand: 1000.0 UNKNOWN_COUNTRY: 1000.0
Như bạn thấy chương trình của chúng ta rất đơn giản, phía Client không cần quan tâm quốc gia đó có áp dụng Tax hay không (không kiểm tra null), có thể thực hiện tính toán ngay.
Ví dụ Null Object Pattern với ứng dụng Persistence
Các bạn tham khảo thêm ở link sau: https://github.com/gpcodervn/Design-Pattern-Tutorial/tree/master/DesignPatternTutorial/src/com/gpcoder/patterns/behavioral/nullobject/persistence
Lợi ích của Null Object Pattern là gì?
- Code trở nên đơn giản hơn, giảm bớt các điều kiện kiểm tra.
- Giảm khả năng xảy ra lỗi Null Pointer Exception.
Sử dụng Null Object Pattern khi nào?
- Đối phó với các đối tượng null.
- Thay vì kiểm tra đối tượng null, chúng ta xác định hành vi null hoặc hành vi được gọi nhưng không làm gì.
- Cung cấp hành vi, giá trị mặc định trong trường hợp dữ liệu không có sẵn.
- Tạo các đối tượng để thử nghiệm, trong trường hợp tài nguyên thật không có sẵn.
Lời kết:
Một trong những cách dễ nhất để quản lý null là không sử dụng nó. Tuy nhiên, các lập trình viên Java có thói quen khởi tạo dữ liệu (thuộc tính & biến) thành null. Điều này khiến chúng ta có nguy cơ sử dụng một phương thức trên đối tượng null, do đó gây ra ngoại lệ tại thời điểm run-time. Thông thường, nên sử dụng empty để khởi tạo dữ liệu, chẳng hạn empty collection, empty string , … Ngoài ra, trong một số trường hợp chúng ta nên áp dụng Null Object Pattern hay Optional trong Java 8 để giúp chương trình gọn ràng hơn, dễ đọc hơn và ít bug hơn.
Tài liệu tham khảo:
- https://sourcemaking.com/design_patterns/null_object
- https://refactoring.guru/introduce-null-object
- https://www.tutorialspoint.com/design_pattern/null_object_pattern.htm