享元模式的作用在于节省内存开销,对于系统内存在的大量相似的对象,通过享元的设计方式,可以提取出可共享的部分,将其只保留一份进而节省大量的内存开销。
并不是所有的对象都适合进行享元设计,它要求对象具有可共享的特征,这些可共享的特征可以做享元设计,对象的可共享特征比例越大,进行享元设计后节省的内存越多。
注意,对象的不可共享特征不能计入享元设计,所以需要仔细判断区分对象的可共享特征与不可共享特征。
外部特征,不可共享特征,保证调用过程不会影响下次调用。
============================
游戏开发中的享元设计:
一次炮弹的发射过程有这些特征:炮弹外观图片、飞行轨迹、爆炸效果、伤害值等,炮弹是被频繁发射的,如果每发射一个炮弹都创建一个对象,那么喜欢散弹枪的玩家会创建数不清的炮弹对象,这样就会给系统造成很大内存压力。
其实仔细分析一下,炮弹虽然发射了很多,但其实他们都可以共享一个对象,就是炮弹对象本身(包括外观图片、特效图片、爆炸效果等,这些都是可共享特征),不同的是炮弹的运行轨迹(不可共享特征)而已,轨迹是个很小的对象或方法的参数而已不会对系统造成负担,这样的享元设计就可以大量节省内存。
场景:一家饮品小厅,可以提供咖啡、茶、水3种饮料,客人会选择坐几号桌子并点一杯饮品,之后饮品为客人服务(即被慢慢地喝掉)。
分析:在现实生活中,每个客人会选择一杯饮品,每个人的饮品肯定不是同一杯,但是在程序运行过程中,完全可以使用同一个对象来实现,饮品本身存在的可共享特征(如成分、毫升、冷热等)都可计入到享元设计中,饮品要服务的对象(如几号桌哪个人)属于不可共享特征不能计入享元设计。
设计:
示例代码:
import java.util.HashMap;
import java.util.Map;
class ServiceContext {
private int tableIndex;
private String customerName;
public ServiceContext(int tableIndex, String customerName) {
this.tableIndex = tableIndex;
this.customerName = customerName;
}
public int getTableIndex() {
return tableIndex;
}
public String getCustomerName() {
return customerName;
}
}
interface Drink {
void provideService(ServiceContext serviceContext);
}
class Coffee implements Drink {
public Coffee() {
System.out.println("Coffee is created.");
}
@Override
public void provideService(ServiceContext serviceContext) {
System.out.println("Coffee is serving for table " + serviceContext.getTableIndex() + " customer " + serviceContext.getCustomerName());
}
}
class Tea implements Drink {
public Tea() {
System.out.println("Drink is created.");
}
@Override
public void provideService(ServiceContext serviceContext) {
System.out.println("Drink is serving for table " + serviceContext.getTableIndex() + " customer " + serviceContext.getCustomerName());
}
}
class Water implements Drink {
public Water() {
System.out.println("Water is created.");
}
@Override
public void provideService(ServiceContext serviceContext) {
System.out.println("Water is serving for table " + serviceContext.getTableIndex() + " customer " + serviceContext.getCustomerName());
}
}
class DrinkFactory {
private Map<String, Drink> drinks = new HashMap<>();
public Drink createDrink(String type) {
Drink drink = drinks.get(type);
if (drink == null) {
// 以下可以考虑抽象工厂模式实现以符合开闭原则,也可以使用反射
if (type.equals("Water")) {
drink = new Water();
} else if (type.equals("Tea")) {
drink = new Tea();
} else if (type.equals("Coffee")) {
drink = new Coffee();
}
drinks.put(type, drink);
}
return drink;
}
}
public class WaiterTest {
public static void main(String[] args) {
String[] types = {"Water", "Tea", "Coffee"};
DrinkFactory drinkFactory = new DrinkFactory();
for (int i = 1; i <= 9; i++) {
Drink drink = drinkFactory.createDrink(types[i % 3]);// Drink 可共享特征 在 DrinkFactory 内部实现享元
ServiceContext serviceContext = new ServiceContext(i, "Sir" + i);// 服务细节为不可共享特征,不能享元
drink.provideService(serviceContext);//外部特征,不可共享特征,保证调用过程不会影响下次调用。
}
}
}
输出:
Drink is created.
Drink is serving for table 1 customer Sir1
Coffee is created.
Coffee is serving for table 2 customer Sir2
Water is created.
Water is serving for table 3 customer Sir3
Drink is serving for table 4 customer Sir4
Coffee is serving for table 5 customer Sir5
Water is serving for table 6 customer Sir6
Drink is serving for table 7 customer Sir7
Coffee is serving for table 8 customer Sir8
Water is serving for table 9 customer Sir9
可以看出:在9次的服务过程中,饮品只创建了三次,一种饮品都仅仅创建了一次,减小了很多内存开销,服务可以很好的进行。
这就是享元模式的精髓,但务必注意区分出可共享与不可共享部分,保证不可共享部分在使用之后不会影响下次使用,即不会改变可共享部分。