享元模式详解

适用场景:文本处理/游戏开发/大规模GUI组件


一、模式本质

用空间换时间的艺术
通过共享细粒度对象,减少内存消耗并提升性能。核心在于区分:

  • 内在状态(Intrinsic)‌:可共享的固定属性(如字符Unicode值)
  • 外在状态(Extrinsic)‌:不可共享的上下文属性(如字符坐标)

二、模式结构解剖

1. 核心组件

组件作用类比现实案例
FlyweightFactory享元对象仓库(带缓存机制)字体库
Flyweight享元对象接口字符模板协议
ConcreteFlyweight具体享元对象(存储内在状态)具体字符'A'的模板
UnsharedFlyweight非共享扩展对象(可选组件)特殊字符组合

2. 工作流程

客户端 --> 请求对象 -->首次请求:[FlyweightFactory] --> 新建[ConcreteFlyweight] --> 存入对象池 -->重复请求:返回已有[Flyweight],传递外在状态 --> 执行操作

三、Java实现

以下是通过享元模式实现奶茶点单的Java代码示例。享元模式的核心是“共享对象”,通过分离‌内在状态(不变属性)‌和‌外在状态(可变属性)‌,减少重复对象的创建。

内在状态‌(可共享):奶茶的基础类型(如珍珠奶茶、抹茶拿铁)、杯型(大/中/小)。

外在状态‌(不可共享):订单号、客户名称、加料(珍珠、椰果等)。

享元对象接口

//享元接口:定义奶茶内在状态(类型、杯型)
public interface MilkTea {
 //类型
 String getType();
 //杯型
 String getSize();
}

具体享元类:存储奶茶的内在状态

//具体享元类:存储奶茶的内在状态
public class ConcreteMilkTea implements MilkTea {
	// 类型(如"珍珠奶茶")
	private final String type;
	// 杯型(如"大杯")
	private final String size;

	public ConcreteMilkTea(String type, String size) {
		this.type = type;
		this.size = size;
	}

	@Override
	public String getType() {
		return type;
	}

	@Override
	public String getSize() {
		return size;
	}
}

享元工厂

//享元工厂:缓存已创建的奶茶对象
public class MilkTeaFactory {
	private static final Map<String, MilkTea> milkTeaMap = new HashMap<>();

	public static MilkTea getMilkTea(String type, String size) {
		String key = type + "_" + size;
		if (!milkTeaMap.containsKey(key)) {
			milkTeaMap.put(key, new ConcreteMilkTea(type, size));
		}
		return milkTeaMap.get(key);
	}
}

订单类:存储外在状态(客户、订单号、加料)

//订单类:存储外在状态(客户、订单号、加料)
public class Order {
	private final MilkTea milkTea;
	private final String customer;
	private final String orderId;
	private final String toppings; // 加料(如"珍珠+椰果")

	public Order(MilkTea milkTea, String customer, String orderId, String toppings) {
		this.milkTea = milkTea;
		this.customer = customer;
		this.orderId = orderId;
		this.toppings = toppings;
	}

	public void printOrder() {
		System.out.printf("订单号:%s\n客户:%s\n奶茶:%s %s\n加料:%s\n\n", orderId, customer, milkTea.getSize(), milkTea.getType(),
				toppings);
	}
}

客户端

public class ClientApp {

	public static void main(String[] args) {
		// 创建享元对象(共享内在状态)
		MilkTea milkTea1 = MilkTeaFactory.getMilkTea("珍珠奶茶", "大杯");
		MilkTea milkTea2 = MilkTeaFactory.getMilkTea("珍珠奶茶", "大杯"); // 复用对象
		MilkTea milkTea3 = MilkTeaFactory.getMilkTea("抹茶拿铁", "中杯");

		// 创建订单(处理外在状态)
		Order order1 = new Order(milkTea1, "张三", "20250224001", "珍珠+椰果");
		Order order2 = new Order(milkTea2, "李四", "20250224002", "布丁");
		Order order3 = new Order(milkTea3, "王五", "20250224003", "无");

		// 打印订单信息
		order1.printOrder();
		order2.printOrder();
		order3.printOrder();

	}

}

运行结果

订单号:20250224001
客户:张三
奶茶:大杯 珍珠奶茶
加料:珍珠+椰果

订单号:20250224002
客户:李四
奶茶:大杯 珍珠奶茶
加料:布丁

订单号:20250224003
客户:王五
奶茶:中杯 抹茶拿铁
加料:无

四、模式深潜

内存优化对比

实现方式对象数量内存消耗适用场景
传统new对象10000100GB小规模场景
享元模式550MB大规模重复元素场景

性能陷阱警示

// 错误的外在状态存储方式 
class BadFlyweight { 
    private List<Position> positions = new ArrayList<>(); 
    //存储外在状态 
    void addPosition(Position pos) { 
        positions.add(pos); 
    } 
}

正确做法‌:外在状态应由客户端持有并通过参数传递,而非存储在享元对象中

五、高频面试闪电战

Q1:享元模式与对象池的区别?

  • 享元:‌对象复用‌(同一对象多处使用)
  • 对象池:‌资源回收‌(防止频繁创建销毁)

Q2:如何处理线程安全问题?

    //双检锁优化后的工厂
	public static TreeModel getTree(String type) {
	if(!cache.containsKey(type)) { 
		synchronized(cache) {
			if(!cache.containsKey(type)) {
				cache.put(type, createTree(type)); 
				} 
			} 
		} 
	return cache.get(type); 
	}

六、模式延展思考

现代框架中的应用

  • Java String常量池‌:"abc"复用机制
  • GUI组件复用‌:Swing的Border共享
  • 数据库连接池‌:Proxy模式混合应用

新型变种

  • Lazy Flyweight‌:延迟加载模型数据
  • Distributed Flyweight‌:分布式对象缓存(如Redis共享)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值