Null Object Pattern cung cấp một đối tượng "rỗng" triển khai cùng interface với đối tượng thực, thay vì trả về null. Giúp loại bỏ kiểm tra null lặp lại (null checks) và tránh NullReferenceException.
Ví dụ: Hình học (Shape)
// 1. Interface chung
public interface IShape
{
void Draw();
}
// 2. Real Objects
public class Square : IShape
{
public void Draw() => Console.WriteLine("Drawing Square");
}
public class Rectangle : IShape
{
public void Draw() => Console.WriteLine("Drawing Rectangle");
}
// 3. Null Object (Singleton)
public sealed class NullShape : IShape
{
private static readonly Lazy<NullShape> _instance = new(() => new NullShape());
public static NullShape Instance => _instance.Value;
private NullShape() { } // Private constructor
public void Draw() { /* Do nothing - safe no-op */ }
}
// 4. Factory trả về NullShape thay vì null
public static class ShapeFactory
{
public static IShape GetShape(string name) => name?.ToLower() switch
{
"square" => new Square(),
"rectangle" => new Rectangle(),
_ => NullShape.Instance // Thay vì return null
};
}
// 5. Client - KHÔNG CẦN KIỂM TRA NULL
class Program
{
static void Main()
{
IShape shape1 = ShapeFactory.GetShape("square");
IShape shape2 = ShapeFactory.GetShape("triangle"); // Không tồn tại
IShape shape3 = ShapeFactory.GetShape(null);
shape1.Draw(); // Drawing Square
shape2.Draw(); // (Im lặng, không crash)
shape3.Draw(); // (Im lặng)
Console.ReadLine();
}
}
Khi nào NÊN dùng
- Thay thế
nullcho các dependency tùy chọn (optional dependencies). - Khi gọi method trên null object là an toàn và không có side effect (logging, drawing, no-op).
- Giảm rải rác
if (obj != null) obj.Do()khắp codebase.
Khi nào KHÔNG nên dùng
- Khi hành vi "không làm gì" che giấu bug logic (nên ném exception rõ ràng).
- Khi cần phân biệt rõ "có dữ liệu" vs "không có dữ liệu" (dùng
Option<T>/Maybe<T>hoặc nullable reference types). - Khi null object có state phức tạp hoặc tốn tài nguyên.
So sánh với Nullable Reference Types (C# 8+)
// C# 8+ nullable context
#nullable enable
public IShape? GetShape(string name) // Có thể trả về null
{
return name switch { "square" => new Square(), _ => null };
}
// Caller BUỘC phải handle null
var shape = GetShape("circle");
shape?.Draw(); // Hoặc pattern matching
if (shape is not null) shape.Draw();
Kết luận: Null Object Pattern phù hợp cho các hành vi "safe no-op". Với dữ liệu nghiệp vụ, hãy dùng nullable reference types + pattern matching để ép compiler nhắc nhở handle null.