面向对象技术可以很好地解决一些灵活性或可以扩展性问题,但是很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致对象创建以及垃圾回收的代价过高,造成性能下降等问题。
一. 享元模式的基本介绍
意图
运用共享技术有效地支持大量细粒度的对象。
结构
享元模式的基本结构如下:
这里涉及到的参与者有如下几种:
二. 享元模式的示例
接下来,我们以一群好朋友周末去西湖游玩,然后在喜欢边茶馆喝茶闲聊为场景,,给出一个简单的享元模式示例。
package com.wangmengjun.tutorial.designpattern.flyweight;
public abstract class TeaOrder {
public abstract void serveTea(TeaContext context);
}
package com.wangmengjun.tutorial.designpattern.flyweight;
public class Tea extends TeaOrder{
private String flavor;
public Tea(String flavor) {
super();
this.flavor = flavor;
System.out.println("创建Tea对象,falvor为" + flavor);
}
@Override
public void serveTea(TeaContext context) {
System.out.println("向"+ context.getTable() +"桌提供一杯[" +flavor +"]");
}
/**
* @return the flavor
*/
public String getFlavor() {
return flavor;
}
}
package com.wangmengjun.tutorial.designpattern.flyweight;
public class TeaContext {
private int tableNumber;
public TeaContext(int tableNumber) {
this.tableNumber = tableNumber;
}
public int getTable() {
return this.tableNumber;
}
}
package com.wangmengjun.tutorial.designpattern.flyweight;
import java.util.HashMap;
import java.util.Map;
public class TeaFactory {
private Map<String, Tea> flavorsMap = new HashMap<String, Tea>();
public Tea getTeaFlavor(String flavor) {
Tea tea = flavorsMap.get(flavor);
if (tea == null) {
tea = new Tea(flavor);
flavorsMap.put(flavor, tea);
}
return tea;
}
public int getTotalTeaFlavorsMade() {
return flavorsMap.size();
}
}
package com.wangmengjun.tutorial.designpattern.flyweight;
public class Client {
public static void main(String[] args) {
System.out.println("周末天气不错,10个小伙伴一起到西湖边游玩~~~");
System.out.println("大家在西湖边的一个茶馆坐下来,点了10杯茶,然后一起闲聊");
System.out.println("其中1号桌子坐了4个人");
System.out.println("其中2号桌子坐了3个人");
System.out.println("其中3号桌子坐了3个人");
System.out.println("可选的茶有3种:龙井、普洱和碧螺春");
TeaContext table1 = new TeaContext(1);
TeaContext table2 = new TeaContext(2);
TeaContext table3 = new TeaContext(3);
TeaFactory teaFactory = new TeaFactory();
teaFactory.getTeaFlavor("龙井").serveTea(table1);
teaFactory.getTeaFlavor("普洱").serveTea(table1);
teaFactory.getTeaFlavor("龙井").serveTea(table1);
teaFactory.getTeaFlavor("普洱").serveTea(table1);
teaFactory.getTeaFlavor("龙井").serveTea(table2);
teaFactory.getTeaFlavor("普洱").serveTea(table2);
teaFactory.getTeaFlavor("碧螺春").serveTea(table2);
teaFactory.getTeaFlavor("碧螺春").serveTea(table3);
teaFactory.getTeaFlavor("龙井").serveTea(table3);
teaFactory.getTeaFlavor("碧螺春").serveTea(table3);
System.out.println("不同口味的茶对象一共创建了[" + teaFactory.getTotalTeaFlavorsMade() + "]个");
}
}
输出结果:
周末天气不错,10个小伙伴一起到西湖边游玩~~~
大家在西湖边的一个茶馆坐下来,点了10杯茶,然后一起闲聊
其中1号桌子坐了4个人
其中2号桌子坐了3个人
其中3号桌子坐了3个人
可选的茶有3种:龙井、普洱和碧螺春
创建Tea对象,falvor为龙井
向1桌提供一杯[龙井]
创建Tea对象,falvor为普洱
向1桌提供一杯[普洱]
向1桌提供一杯[龙井]
向1桌提供一杯[普洱]
向2桌提供一杯[龙井]
向2桌提供一杯[普洱]
创建Tea对象,falvor为碧螺春
向2桌提供一杯[碧螺春]
向3桌提供一杯[碧螺春]
向3桌提供一杯[龙井]
向3桌提供一杯[碧螺春]
不同口味的茶对象一共创建了[3]个
至此一个简单的享元模式例子就完成了。从上面输出的结果来看,10个人点了十杯茶,包括3中口味,我们只创建了3个茶口味的对象,有7杯茶用到的Tea对象来自于共享。
其实针对这种对象共享或者缓存起来,我们在JDK的源码中也能看到很多。比如Integer,先来看个例子:
package com.wangmengjun.tutorial.designpattern.flyweight;
public class IntegerTest {
public static void main(String[] args) {
Integer v1 = 100;
Integer v2 = 100;
//true
System.out.println(v1 == v2);
Integer v3 = 150;
Integer v4 = 150;
//false
System.out.println(v3 == v4);
}
}
第一个输出true,是因为-128到127的数直接取自Cache,所以是同一个对象。
三. 小结
优缺点
优点:
1、大幅度降低内存中对象的数量。
缺点:
1、享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
2、享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。
适合场景:
当以下所有条件都满足的时,可以考虑使用享元模式:
1、一个系统有大量的对象
2、完全由于使用大量的对象,造成很大的存储开销
3、对象的大多数状态都可以变为外部状态。
4、这些对象可以按照内部状态分成很多组,如果剔除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象
5、软件系统不依赖于这些对象的身份,也就是说,这些对象可以是不可分辨的。
参考
[1]. 阎宏. Java与模式.电子工业出版社
[2]. Erich Gamma. 设计模式-可复用面向对象软件的基础. 机械工业出版社.