Əsas məzmuna keçin

Singleton Design Pattern

Bir class-dan yalnız bir obyektin yaradılmasını təmin edir. Bu pattern həmçinin həmin obyektə qlobal çıxış nöqtəsi təqdim edir. Singleton pattern, real həyatda bir çox nümunələrə bənzəyir. Məsələn, bir şirkətdə yalnız bir prezident ola bilər, və ya kompüterdə yalnız bir print spooler xidməti işləyə bilər.

Singleton Pattern-nin Əsas Xüsusiyyətləri

  • Single Instance: Yalnız bir obyekt nümunəsi yaradır
  • Global Access: Bütün aplikasiyada həmin obyektə çıxış imkanı
  • Lazy Initialization: Lazım olduqda obyekt yaradır
  • Thread Safety: Çox thread mühitdə təhlükəsiz işləyə bilir

Singleton Pattern-nin Strukturu

  1. Singleton Class: Özündən yalnız bir nümunə yaratmağa imkan verən class
  2. Private Constructor: Class-dan kənardan obyekt yaradılmasını məhdudlaşdıran konstruktor
  3. Static Instance: Class daxilində saxlanılan static obyekt
  4. Static Method: Obyektə çıxış üçün static metod

Java-da Singleton Pattern İmplementasiyası

1. Eager Initialization

Koda bax
// Eager Initialization Singleton
class EagerSingleton {
// Instance is created at class loading time
private static final EagerSingleton INSTANCE = new EagerSingleton();

// Private constructor to prevent instantiation
private EagerSingleton() {
System.out.println("EagerSingleton instance created");
}

// Public method to provide access to the instance
public static EagerSingleton getInstance() {
return INSTANCE;
}

public void doSomething() {
System.out.println("EagerSingleton doing something...");
}
}

// Client code
public class EagerSingletonDemo {
public static void main(String[] args) {
System.out.println("Getting first instance...");
EagerSingleton singleton1 = EagerSingleton.getInstance();
singleton1.doSomething();

System.out.println("Getting second instance...");
EagerSingleton singleton2 = EagerSingleton.getInstance();
singleton2.doSomething();

// Check if both references point to the same object
System.out.println("Are both instances the same? " + (singleton1 == singleton2));
System.out.println("Instance 1 hashcode: " + singleton1.hashCode());
System.out.println("Instance 2 hashcode: " + singleton2.hashCode());
}
}

2. Lazy Initialization

Koda bax
// Lazy Initialization Singleton (not thread-safe)
class LazySingleton {
private static LazySingleton instance;

private LazySingleton() {
System.out.println("LazySingleton instance created");
}

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

public void doSomething() {
System.out.println("LazySingleton doing something...");
}
}

// Thread-Safe Lazy Initialization
class ThreadSafeLazySingleton {
private static ThreadSafeLazySingleton instance;

private ThreadSafeLazySingleton() {
System.out.println("ThreadSafeLazySingleton instance created");
}

// Synchronized method to make it thread-safe
public static synchronized ThreadSafeLazySingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeLazySingleton();
}
return instance;
}

public void doSomething() {
System.out.println("ThreadSafeLazySingleton doing something...");
}
}

3. Double-Checked Locking

Koda bax
// Double-Checked Locking Singleton
class DoubleCheckedLockingSingleton {
// volatile keyword ensures that multiple threads handle the instance variable correctly
private static volatile DoubleCheckedLockingSingleton instance;

private DoubleCheckedLockingSingleton() {
System.out.println("DoubleCheckedLockingSingleton instance created");
}

public static DoubleCheckedLockingSingleton getInstance() {
// First check (no locking)
if (instance == null) {
synchronized (DoubleCheckedLockingSingleton.class) {
// Second check (with locking)
if (instance == null) {
instance = new DoubleCheckedLockingSingleton();
}
}
}
return instance;
}

public void doSomething() {
System.out.println("DoubleCheckedLockingSingleton doing something...");
}
}

// Client code to test thread safety
public class ThreadSafetyTest {
public static void main(String[] args) {
// Create multiple threads to test thread safety
for (int i = 1; i <= 5; i++) {
final int threadId = i;
Thread thread = new Thread(() -> {
DoubleCheckedLockingSingleton singleton =
DoubleCheckedLockingSingleton.getInstance();
System.out.println("Thread " + threadId + " got instance: " +
singleton.hashCode());
});
thread.start();
}
}
}

4. Bill Pugh Solution (Initialization-on-demand)

Koda bax
// Bill Pugh Singleton Implementation
class BillPughSingleton {
private BillPughSingleton() {
System.out.println("BillPughSingleton instance created");
}

// Static inner class - inner class is not loaded until getInstance() is called
private static class SingletonHelper {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}

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

public void doSomething() {
System.out.println("BillPughSingleton doing something...");
}
}

// Client code
public class BillPughDemo {
public static void main(String[] args) {
System.out.println("Before getting instance...");

BillPughSingleton singleton1 = BillPughSingleton.getInstance();
singleton1.doSomething();

BillPughSingleton singleton2 = BillPughSingleton.getInstance();
singleton2.doSomething();

System.out.println("Are both instances the same? " + (singleton1 == singleton2));
}
}

5. Enum Singleton

Koda bax
// Enum Singleton - Recommended approach
public enum EnumSingleton {
INSTANCE;

// Constructor is called only once
EnumSingleton() {
System.out.println("EnumSingleton instance created");
}

public void doSomething() {
System.out.println("EnumSingleton doing something...");
}

// Additional methods can be added
public String getInfo() {
return "This is EnumSingleton: " + this.hashCode();
}
}

// Client code
public class EnumSingletonDemo {
public static void main(String[] args) {
EnumSingleton singleton1 = EnumSingleton.INSTANCE;
singleton1.doSomething();

EnumSingleton singleton2 = EnumSingleton.INSTANCE;
singleton2.doSomething();

System.out.println("Are both instances the same? " + (singleton1 == singleton2));
System.out.println("Singleton info: " + singleton1.getInfo());
System.out.println("Singleton info: " + singleton2.getInfo());
}
}

Çıxış:

EnumSingleton instance created
EnumSingleton doing something...
EnumSingleton doing something...
Are both instances the same? true
Singleton info: This is EnumSingleton: 12345678
Singleton info: This is EnumSingleton: 12345678

Real-World Example: Database Connection Manager

Koda bax
// Database Connection Manager using Singleton
class DatabaseConnectionManager {
private static volatile DatabaseConnectionManager instance;
private String connectionUrl;
private boolean connected;

private DatabaseConnectionManager() {
this.connectionUrl = "jdbc:mysql://localhost:3306/mydb";
this.connected = false;
System.out.println("DatabaseConnectionManager initialized");
}

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

public void connect() {
if (!connected) {
System.out.println("Connecting to database: " + connectionUrl);
connected = true;
System.out.println("Database connection established");
} else {
System.out.println("Already connected to database");
}
}

public void disconnect() {
if (connected) {
System.out.println("Disconnecting from database");
connected = false;
System.out.println("Database connection closed");
} else {
System.out.println("Not connected to database");
}
}

public boolean isConnected() {
return connected;
}

public String getConnectionInfo() {
return "Connection URL: " + connectionUrl + ", Connected: " + connected;
}
}

// Client code
public class DatabaseManagerDemo {
public static void main(String[] args) {
// Get database manager instance
DatabaseConnectionManager dbManager1 = DatabaseConnectionManager.getInstance();
DatabaseConnectionManager dbManager2 = DatabaseConnectionManager.getInstance();

// Verify singleton behavior
System.out.println("Are both managers the same instance? " +
(dbManager1 == dbManager2));

// Use database manager
System.out.println(dbManager1.getConnectionInfo());

dbManager1.connect();
System.out.println(dbManager1.getConnectionInfo());

// Try to connect again
dbManager2.connect();

// Disconnect
dbManager1.disconnect();
System.out.println(dbManager2.getConnectionInfo());
}
}

Singleton Pattern İmplementasiya Növləri Müqayisəsi

MetodThread-SafeLazy LoadingPerformanceKarmaşıqlıq
EagerBəliXeyrYaxşıAşağı
Lazy (Simple)XeyrBəliYaxşıAşağı
Thread-Safe LazyBəliBəliZəifOrta
Double-CheckedBəliBəliYaxşıYüksək
Bill PughBəliBəliYaxşıOrta
EnumBəliXeyrYaxşıAşağı

Singleton Pattern-nin Üstünlükləri

  • Memory Control: Yaddaş istifadəsini məhdudlaşdırır
  • Global Access: Bütün aplikasiyada çıxış imkanı
  • Resource Management: Məhdud resursları idarə edir
  • Consistency: Vahid vəziyyəti təmin edir

Singleton Pattern-nin Çatışmazlıqları

  • Testing Difficulty: Test etmək çətin olur
  • Hidden Dependencies: Gizli asılılıqlar yaradır
  • Scalability Issues: Miqyaslama problemləri
  • Violation of SRP: Single Responsibility Principle-i pozur

İstifadə Halları

  • Database Connection Pools: Verilənlər bazası əlaqə hovuzları
  • Logger Classes: Log yazma xidmətləri
  • Configuration Settings: Konfiqurasiya parametrləri
  • Cache Managers: Keş idarəetmə sistemləri
  • Thread Pools: Thread hovuzları

Singleton Pattern-dən Qaçınmaq

  • Dependency Injection: DI konteynerləri istifadə edin
  • Static Classes: Bəzi hallarda static class-lar daha münasibdir
  • Factory Pattern: Obyekt yaratma üçün factory istifadə edin

Tövsiyələr

  • Enum istifadə edin: Ən sadə və etibarlı həll
  • Thread safety-ni nəzərə alın: Çox thread mühitdə diqqətli olun
  • Serialization: Serialization zamanı readResolve() metodunu implement edin
  • Testing: Unit test yazmaq üçün interface istifadə edin

Singleton Pattern, məhdud sayda obyekt yaratmaq tələb olunan hallarda faydalıdır, lakin istifadəsi zamanı diqqətli olmaq və alternativləri də nəzərə almaq vacibdir.