Null Object Pattern เป็น Behavioral Pattern ที่ให้ Object แทนค่า null เพื่อหลีกเลี่ยง Null Reference Checks ซ้ำซ้อน ทำให้โค้ดสะอาดขึ้น (Clean Code)
แทนที่จะ Return null และบังคับให้ Caller ตรวจสอบ if (obj != null) ทุกครั้ง ให้ Return Null Object ที่ Implement Interface เดียวกัน แต่ทำงานเป็น No-Op (No Operation)
โครงสร้าง
- Abstract/Interface: กำหนด Contract
- Real Object: Implementation จริง
- Null Object: Implementation ว่าง (Do Nothing) - มักเป็น Singleton
ตัวอย่าง: Shape Drawing
// 1. Interface
public interface IShape
{
void Draw();
}
// 2. Real Objects
public class Circle : IShape
{
public void Draw() => Console.WriteLine("Drawing a Circle");
}
public class Rectangle : IShape
{
public void Draw() => Console.WriteLine("Drawing a Rectangle");
}
// 3. Null Object (Singleton)
public class NullShape : IShape
{
private static readonly Lazy<NullShape> _instance = new Lazy<NullShape>(() => new NullShape());
private NullShape() { } // Private Constructor
public static NullShape Instance => _instance.Value;
public void Draw() { } // Do Nothing - No Op
}
// 4. Factory - คืน Null Object แทน null
public static class ShapeFactory
{
public static IShape GetShape(string shapeType)
{
return shapeType?.ToLower() switch
{
"circle" => new Circle(),
"rectangle" => new Rectangle(),
_ => NullShape.Instance // แทนที่จะ return null
};
}
}
// Client - ไม่ต้อง Check Null!
static void Main()
{
IShape shape1 = ShapeFactory.GetShape("Circle");
shape1.Draw(); // "Drawing a Circle"
IShape shape2 = ShapeFactory.GetShape("Triangle"); // ไม่มี
shape2.Draw(); // No Output, No Exception, No Null Check!
IShape shape3 = ShapeFactory.GetShape(null);
shape3.Draw(); // Safe!
}
ผลลัพธ์
Drawing a Circle
(ไม่มี Output สำหรับ Triangle และ null - แต่ไม่ Crash)
ข้อดี
- กำจัด Null Checks ทั่วโค้ด (Reduce Boilerplate)
- ป้องกัน
NullReferenceException - Client Code เรียบง่าย อ่านง่าย
- Default Behavior ที่ปลอดภัย (Safe Default)
ข้อจำกัด / ข้อควรระวัง
- ไม่เหมาะกับทุกสถานการณ์ - บางครั้ง
nullมีความหมายเฉพาะ (เช่น "ไม่พบข้อมูล" vs "ข้อมูลว่าง") - อาจซ่อน Bug ได้ หากควร Handle Missing Case แบบ Explicit
- ต้องระวัง Side Effects ใน Null Object (ควรเป็น Pure No-Op)
- Performance: สร้าง Object เพิ่ม (แต่ Singleton ช่วยลด)
ตัวเลือกใน C# Modern
Nullable Reference Types(C# 8.0+):#nullable enableช่วย Catch Null ที่ Compile Time?.(Null Conditional Operator),??(Null Coalescing)Option<T>/Maybe<T>(Functional Approach - LanguageExt, Optional)
เมื่อไหร่ควรใช้?
- Default Behavior ที่สมเหตุสมผล (Empty Collection, Disabled Logger, No-op Command)
- Strategy Pattern ที่มี "Do Nothing" Strategy
- Composite Pattern: Leaf Node ที่ว่าง