有状态类一定是多例吗?无状态类一定是单例吗?
答案是否定的。有状态类和无状态类与单例/多例模式是正交概念,它们可以自由组合,具体如何设计取决于业务需求。下面详细分析:
1. 有状态类 ≠ 多例
有状态类可以是单例
- 场景:当全局只需要一个实例,并且该实例需要维护某些状态时。
- 例子:
- 全局计数器(单例 + 有状态):
public class GlobalCounter { private static final GlobalCounter INSTANCE = new GlobalCounter(); private int count = 0; // 可变状态 private GlobalCounter() {} public static GlobalCounter getInstance() { return INSTANCE; } public synchronized int increment() { return ++count; // 需要同步 } }
- 应用配置管理器(单例 + 有状态):
public class AppConfig { private static final AppConfig INSTANCE = new AppConfig(); private String theme = "dark"; // 可变状态 private AppConfig() {} public static AppConfig getInstance() { return INSTANCE; } public void setTheme(String theme) { this.theme = theme; } }
- 全局计数器(单例 + 有状态):
- 关键点:
- 虽然是单例,但内部维护可变状态(如
count
、theme
)。 - 由于是单例,多线程访问时需加锁(如
synchronized
)。
- 虽然是单例,但内部维护可变状态(如
有状态类也可以是多例
- 场景:每个实例需要独立的状态(如用户会话、数据库连接)。
- 例子:
- 用户会话管理(多例 + 有状态):
public class UserSession { private final String userId; // 实例级状态 private List<String> permissions; public UserSession(String userId) { this.userId = userId; } public void loadPermissions() { this.permissions = fetchFromDB(userId); // 初始化状态 } }
- 数据库连接池(多例 + 有状态):
public class DatabaseConnection { private final String connectionId; private boolean isBusy = false; // 状态 public DatabaseConnection(String id) { this.connectionId = id; } public void executeQuery(String sql) { this.isBusy = true; // ... } }
- 用户会话管理(多例 + 有状态):
- 关键点:
- 每个实例独立维护自己的状态(如
userId
、isBusy
)。 - 多例模式下,状态隔离,无需全局锁(但实例内部仍需线程安全)。
- 每个实例独立维护自己的状态(如
2. 无状态类 ≠ 单例
无状态类可以是单例
- 场景:工具类或全局服务,无需维护状态,且只需一个实例。
- 例子:
- 数学工具类(单例 + 无状态):
public class MathUtils { private static final MathUtils INSTANCE = new MathUtils(); private MathUtils() {} public static MathUtils getInstance() { return INSTANCE; } public int add(int a, int b) { // 无状态,仅依赖参数 return a + b; } }
- 日志工具类(单例 + 无状态):
public class Logger { private static final Logger INSTANCE = new Logger(); private Logger() {} public static Logger getInstance() { return INSTANCE; } public void log(String message) { // 无状态 System.out.println(message); } }
- 数学工具类(单例 + 无状态):
- 关键点:
- 无状态类作为单例时,天然线程安全(因为没有共享可变数据)。
- 通常直接用
static
方法(如Math.max()
),无需显式单例。
无状态类也可以是多例
- 场景:虽然无状态,但需要多个实例(如线程池任务、策略模式)。
- 例子:
- Runnable 任务(多例 + 无状态):
public class CleanTask implements Runnable { @Override public void run() { // 无状态,仅依赖外部输入 System.out.println("Cleaning..."); } }
- 策略模式中的策略(多例 + 无状态):
public interface DiscountStrategy { double apply(double price); // 无状态,纯计算 } public class HalfDiscount implements DiscountStrategy { @Override public double apply(double price) { return price * 0.5; } }
- Runnable 任务(多例 + 无状态):
- 关键点:
- 虽然类本身无状态,但可能通过多例实现不同行为(如不同折扣策略)。
- 多例无状态类通常用于组合模式或依赖注入(如 Spring Bean)。
3. 组合关系总结
类别 | 单例 | 多例 |
---|---|---|
有状态类 | ✅ 全局计数器、配置管理器 | ✅ 用户会话、数据库连接 |
无状态类 | ✅ 工具类(MathUtils ) | ✅ 策略模式、线程任务 |
4. 如何选择?
有状态类设计建议
- 单例:当状态需要全局共享(如应用配置),但需注意线程安全。
- 多例:当状态需要隔离(如用户会话、连接池)。
无状态类设计建议
- 单例:工具类(如
StringUtils
),避免重复创建。 - 多例:需要灵活组合时(如策略模式)。
5. 经典误区
❌ 误区1:无状态类必须用单例
- 实际上,无状态类可以是多例(如
Runnable
),只是通常更省内存。
❌ 误区2:有状态类必须用多例
- 单例有状态类很常见(如全局缓存),但需处理好并发。
❌ 误区3:静态方法 = 无状态单例
- 静态方法本质是无状态的,但单例是对象生命周期管理策略,二者不同。
6. 总结
- 有状态类可以单例(全局状态)或多例(独立状态)。
- 无状态类可以单例(工具类)或多例(策略模式)。
- 关键区别:
- 有状态类依赖内部可变数据。
- 无状态类仅依赖输入参数。
- 设计核心:根据业务需求选择 状态管理方式 和 实例化策略。