Singleton Pattern

1. Giới Thiệu

1.1 Định Nghĩa

Singleton là một design pattern thuộc nhóm Creational Pattern, đảm bảo một class chỉ có một instance duy nhất và cung cấp một điểm truy cập toàn cục đến instance đó.

1.2 Mục Đích

  • Đảm bảo một class chỉ có một instance
  • Cung cấp một điểm truy cập toàn cục đến instance đó
  • Kiểm soát việc tạo đối tượng
  • Giới hạn số lượng instance

2. Cách Triển Khai

2.1 Eager Initialization

public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {}

    public static EagerSingleton getInstance() {
        return instance;
    }
}

2.2 Lazy Initialization

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {}

    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

2.3 Double-Checked Locking

public class DoubleCheckedSingleton {
    private static volatile DoubleCheckedSingleton instance;

    private DoubleCheckedSingleton() {}

    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}

2.4 Bill Pugh Singleton

public class BillPughSingleton {
    private BillPughSingleton() {}

    private static class SingletonHelper {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

2.5 Enum Singleton

public enum EnumSingleton {
    INSTANCE;

    private ConnectionPool pool;

    EnumSingleton() {
        pool = new ConnectionPool();
    }

    public void doSomething() {
        // Implementation
    }
}

3. Ví Dụ Thực Tế

3.1 Database Connection Pool

public class DatabaseConnectionPool {
    private static volatile DatabaseConnectionPool instance;
    private List<Connection> connections;

    private DatabaseConnectionPool() {
        connections = new ArrayList<>();
        // Initialize connections
    }

    public static DatabaseConnectionPool getInstance() {
        if (instance == null) {
            synchronized (DatabaseConnectionPool.class) {
                if (instance == null) {
                    instance = new DatabaseConnectionPool();
                }
            }
        }
        return instance;
    }

    public Connection getConnection() {
        // Get available connection
        return connections.remove(0);
    }

    public void releaseConnection(Connection connection) {
        // Return connection to pool
        connections.add(connection);
    }
}

3.2 Configuration Manager

public class ConfigurationManager {
    private static final ConfigurationManager INSTANCE = new ConfigurationManager();
    private Properties properties;

    private ConfigurationManager() {
        properties = new Properties();
        try {
            properties.load(new FileInputStream("config.properties"));
        } catch (IOException e) {
            throw new RuntimeException("Failed to load configuration", e);
        }
    }

    public static ConfigurationManager getInstance() {
        return INSTANCE;
    }

    public String getProperty(String key) {
        return properties.getProperty(key);
    }

    public void setProperty(String key, String value) {
        properties.setProperty(key, value);
    }
}

4. Lợi Ích và Nhược Điểm

4.1 Lợi Ích

  1. Đảm bảo chỉ có một instance duy nhất
  2. Truy cập toàn cục đến instance
  3. Khởi tạo lazy loading
  4. Thay thế biến global

4.2 Nhược Điểm

  1. Vi phạm Single Responsibility Principle
  2. Khó unit test
  3. Có thể gây ra tight coupling
  4. Khó mở rộng trong tương lai

5. Khi Nào Sử Dụng

5.1 Các Trường Hợp Nên Dùng

  • Quản lý shared resources (database connections, thread pools)
  • Quản lý configuration
  • Logging service
  • Caching service
  • Device drivers

5.2 Các Trường Hợp Không Nên Dùng

  • Khi cần nhiều instance cho scaling
  • Khi cần loose coupling
  • Khi cần unit test dễ dàng
  • Khi state có thể thay đổi

6. Best Practices

6.1 Thread Safety

@ThreadSafe
public class ThreadSafeSingleton {
    private static class InstanceHolder {
        private static final ThreadSafeSingleton INSTANCE = new ThreadSafeSingleton();
    }

    private ThreadSafeSingleton() {
        if (InstanceHolder.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static ThreadSafeSingleton getInstance() {
        return InstanceHolder.INSTANCE;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("Singleton cannot be cloned");
    }

    protected Object readResolve() {
        return getInstance();
    }
}

6.2 Dependency Injection

@Configuration
public class SingletonConfig {
    @Bean
    @Singleton
    public DatabaseConnectionPool databaseConnectionPool() {
        return DatabaseConnectionPool.getInstance();
    }
}

@Service
public class UserService {
    private final DatabaseConnectionPool connectionPool;

    @Autowired
    public UserService(DatabaseConnectionPool connectionPool) {
        this.connectionPool = connectionPool;
    }
}

7. Testing

7.1 Unit Testing

class SingletonTest {
    @Test
    void whenGetInstance_thenReturnSameInstance() {
        Singleton first = Singleton.getInstance();
        Singleton second = Singleton.getInstance();

        assertSame(first, second);
    }

    @Test
    void whenCloneSingleton_thenThrowException() {
        Singleton singleton = Singleton.getInstance();

        assertThrows(CloneNotSupportedException.class, () -> 
            singleton.clone());
    }
}

7.2 Mock Testing

@ExtendWith(MockitoExtension.class)
class ServiceTest {
    @Mock
    private DatabaseConnectionPool connectionPool;

    @InjectMocks
    private UserService userService;

    @Test
    void whenUseConnectionPool_thenSuccess() {
        // Given
        Connection connection = mock(Connection.class);
        when(connectionPool.getConnection())
            .thenReturn(connection);

        // When
        userService.doSomething();

        // Then
        verify(connectionPool).getConnection();
        verify(connectionPool).releaseConnection(connection);
    }
}

8. Common Issues và Solutions

8.1 Serialization

public class SerializableSingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final SerializableSingleton INSTANCE = new SerializableSingleton();

    private SerializableSingleton() {}

    public static SerializableSingleton getInstance() {
        return INSTANCE;
    }

    protected Object readResolve() {
        return getInstance();
    }
}

8.2 Reflection Attack Prevention

public class ReflectionSafeSingleton {
    private static final ReflectionSafeSingleton INSTANCE = new ReflectionSafeSingleton();
    private static boolean initialized = false;

    private ReflectionSafeSingleton() {
        if (initialized) {
            throw new IllegalStateException("Already initialized");
        }
        initialized = true;
    }

    public static ReflectionSafeSingleton getInstance() {
        return INSTANCE;
    }
}

9. References

9.1 Books

  • Design Patterns: Elements of Reusable Object-Oriented Software
  • Effective Java by Joshua Bloch
  • Head First Design Patterns

9.2 Online Resources