Singleton Pattern สำหรับเก็บข้อมูล HashMap แบบ Global
Singleton เป็น Design Pattern ที่จำกัดการสร้าง Instance เพียง 1 ตัวตลอดชีวิตของแอปพลิเคชัน เหมาะสำหรับการจัดการข้อมูลร่วมกัน (Shared State) เช่น Cache, Configuration, User Session บทความนี้สาธิตการเก็บ HashMap<String, Double> ใน Singleton
ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่
Android Studio → File → New Project → Empty Activity
ขั้นตอนที่ 2: Layout - activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Singleton HashMap Demo"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<Button
android:id="@+id/btnSave"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="บันทึกข้อมูลลง Singleton"
android:layout_marginBottom="12dp" />
<Button
android:id="@+id/btnShow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="แสดงข้อมูลจาก Singleton" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
ขั้นตอนที่ 3: Singleton Class - DataRepository.java
package com.example.singletondemo;
import android.content.Context;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class DataRepository {
// 1. Private static instance (Lazy Initialization + Thread Safe)
private static volatile DataRepository instance;
private final Map<String, Double> dataMap = new HashMap<>();
private Context appContext;
// 2. Private Constructor
private DataRepository() {}
// 3. Public static getInstance() - Double-Checked Locking
public static DataRepository getInstance() {
if (instance == null) {
synchronized (DataRepository.class) {
if (instance == null) {
instance = new DataRepository();
}
}
}
return instance;
}
// 4. Initialize Context (เรียกครั้งเดียวใน Application class หรือ Activity แรก)
public void init(Context context) {
if (appContext == null) {
this.appContext = context.getApplicationContext();
}
}
public Context getContext() {
return appContext;
}
// 5. Business Methods
public void put(String key, Double value) {
dataMap.put(key, value);
}
public Double get(String key) {
return dataMap.get(key);
}
public void remove(String key) {
dataMap.remove(key);
}
public void clear() {
dataMap.clear();
}
public Map<String, Double> getAll() {
// คืนค่า Unmodifiable Map เพื่อป้องกันการแก้ไขจากภายนอก
return Collections.unmodifiableMap(dataMap);
}
public String getFormattedData() {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Double> entry : dataMap.entrySet()) {
sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
}
return sb.length() > 0 ? sb.toString() : "ไม่มีข้อมูล";
}
}
ขั้นตอนที่ 4: MainActivity.java
package com.example.singletondemo;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private DataRepository repository;
private Button btnSave, btnShow;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialize Singleton
repository = DataRepository.getInstance();
repository.init(getApplicationContext());
btnSave = findViewById(R.id.btnSave);
btnShow = findViewById(R.id.btnShow);
btnSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// บันทึกข้อมูลตัวอย่าง
repository.put("กระดาษ", 10.0);
repository.put("เติมเงิน", 100.0);
repository.put("ค่าสาธารณูปโภค", 500.0);
repository.put("ค่าอาหาร", 250.50);
Toast.makeText(MainActivity.this, "บันทึกข้อมูลเรียบร้อย", Toast.LENGTH_SHORT).show();
}
});
btnShow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String data = repository.getFormattedData();
Toast.makeText(MainActivity.this, data, Toast.LENGTH_LONG).show();
}
});
}
}
Best Practices สำหรับ Singleton ใน Android
1. ใช้ Application Class สำหรับ Initialize Context
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); DataRepository.getInstance().init(this); } }ใน AndroidManifest.xml:
<application android:name=".MyApplication" ...>2. ใช้ Dependency Injection (Hilt/Koin) แทน Singleton โดยตรง
// Hilt Module @Module @InstallIn(SingletonComponent.class) public class RepositoryModule { @Provides @Singleton DataRepository provideDataRepository() { return DataRepository.getInstance(); } } // ใช้งาน @Inject DataRepository repository;3. Thread Safety
HashMap ไม่ Thread-Safe หากมีหลาย Thread เขียนพร้อมกัน ควรใช้:
ConcurrentHashMapแทน HashMap- หรือ `synchronized` ใน method put/get/clear
4. หลีกเลี่ยง Memory Leak
- เก็บ
Application Contextเท่านั้น (ไม่ใช่ Activity Context) - ไม่ถือ Reference ของ View/Activity ใน Singleton
การทดสอบ
- รันแอป → กด "บันทึกข้อมูล"
- กด "แสดงข้อมูล" → จะแสดง Toast พร้อมข้อมูลทั้งหมด
- ปิดแอป → เปิดใหม่ → ข้อมูลยังคงอยู่ (เพราะ Process ยังไม่ตาย)
- Force Stop แอป → ข้อมูลจะหาย (เพราะ Process ถูก Kill)
สรุป
Singleton Pattern เหมาะสำหรับข้อมูลที่ต้องการเข้าถึงได้ทั่วแอป และมีชีวิตอยู่ร่วมกับ Process แต่ควรพิจารณา Dependency Injection และ Thread Safety ในโปรเจกต์จริง