您的位置:首页 > 其它

享元模式

2016-05-23 00:10 411 查看

1、什么是享元模式?

  享元模式(Flyweight Pattern):以共享的方式高效的支持大量的细粒度对象。通过复用内存中已存在的对象,降低系统创建对象实例的性能消耗。

  享元的英文是Flyweight,是一个来自体育方面的专业用语,在拳击、摔跤和举重比赛中特指最轻量的级别。把这个单词移植到软件工程中,也是用来表示特别小的对象,即细粒度的对象。至于为什么把Flyweight翻译为“享元”,可以理解为共享元对象,也就是共享细粒度对象。

  在面向对象中,大量细粒度对象的创建、销毁及存储所造成的资源和性能上的损耗,可能会在系统运行时形成瓶颈。那么该如何避免产生大量的细粒度对象,同时又不影响系统使用面向对象的方式进行操作呢?享元模式提供了一个比较好的解决方案。

2、享元模式类图:



  享元模式又分为内蕴状态和外蕴状态,接下来将使用案例进行分析。

3、案例

  案例需求:在五子棋中,会用到很多的黑子和白子,但是对于每一个黑子或白子都创建一个对象的话,那么会太过消耗内存。我们能不能共享对象实例呢?使得在整个游戏中只有“黑子”和“白子”两个对象。这就需要使用享元模式。

  首先创建一个棋子抽象类作为棋子的超类,含有一个棋子标识的属性:

<span style="font-size:18px;">
/**
* 需求:棋子的超类,含有一个棋子类别的属性,标志具体的棋子类型
* @author 猛龙过江
*
*/
public abstract class AbstractChessman {
//棋子类别
protected String chess;
//构造方法
public AbstractChessman(String chess){
this.chess = chess;
}
//显示棋子信息
public void show(){
System.out.println(this.chess);
}
}</span>

  黑子类:
<span style="font-size:18px;">
/**
* 需求:黑子类
* @author 猛龙过江
*
*/
public class BlackChessman extends AbstractChessman {
/*
* 构造方法,初始化黑棋子
*/
public BlackChessman(){
super("●");
System.out.println("--一颗黑棋子诞生了!--");
}

}</span>

  白子类:
<span style="font-size:18px;">
/**
* 需求:白棋子
* @author 猛龙过江
*
*/
public class WhiteChessman extends AbstractChessman {
/*
* 构造方法,初始化黑棋子
*/
public WhiteChessman(){
super("○");
System.out.println("--一颗白棋子诞生了!--");
}
}</span>

  下面来设计棋子工厂类,棋子工厂类我们设计为单例模式,该类用来生产棋子对象实例,并放入缓存当中,下次再获得棋子对象的时候就从缓存当中获得。内容如下:
<span style="font-size:18px;">
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

/**
* 需求:棋子工厂,用于生产棋子对象实例,并放入缓存中,采用单例模式完成
* @author 猛龙过江
*
*/
public class ChessmanFactory {
//单例模式
private static ChessmanFactory chessmanFactory = new ChessmanFactory();
//缓存共享对象
private final Hashtable<Character, AbstractChessman> cache = new Hashtable<Character, AbstractChessman>();
//构造方法私有化
private ChessmanFactory(){
}
//获得单例工厂对象
public static ChessmanFactory getInstance(){
return chessmanFactory;
}
/*
* 根据字母获得棋子
*/
public AbstractChessman getChessmanObject(char c){
//从缓存中获得棋子对象实例
AbstractChessman abstractChessman = this.cache.get(c);
//判空
if (abstractChessman==null) {
//说明缓存中没有该棋子对象实例,需要创建
switch (c) {
case 'B':
abstractChessman = new BlackChessman();
break;
case 'W':
abstractChessman = new WhiteChessman();
break;
default:
System.out.println("非法字符,请重新输入!");
break;
}
//如果有非法字符,那么对象必定仍为空,所以再进行判断
if (abstractChessman!=null) {
//放入缓存
this.cache.put(c, abstractChessman);
}
}
//如果缓存中存在棋子对象则直接返回
return abstractChessman;
}
}</span>

  通过客户端进行测试:
<span style="font-size:18px;">import java.util.Random;

/**
* 需求:客户端(测试类)
* @author 猛龙过江
*
*/
public class Test {
public static void main(String[] args) {
//创建工厂
ChessmanFactory chessmanFactory = ChessmanFactory.getInstance();
//随机数,用于生成棋子对象
Random random = new Random();
int radom = 0;
AbstractChessman abstractChessman = null;
//随机获得棋子
for (int i = 0; i < 10; i++) {
radom = random.nextInt(2);
switch (radom) {
case 0:
//获得黑棋子
abstractChessman = chessmanFactory.getChessmanObject('B');
break;
case 1:
//获得黑棋子
abstractChessman = chessmanFactory.getChessmanObject('W');
break;
}
if (abstractChessman!=null) {
abstractChessman.show();
}
}
}
}</span>

  执行后,我们发现“一颗黑棋子诞生了!”和“一颗白棋子诞生了!”各执行了一次,说明在众多棋子中只有一个黑棋子和一个白棋子,实现了对象的共享,这就是享元。

4、需求改了

  我们还需要改动需求,因为棋子必须有位置,所以我们还需要让棋子显示位置。显然,棋子对象是可以共享的,但是棋子位置都是不一样的,是不能够共享的,这久涉及到了享元模式的两种状态:内蕴状态(Internal State)和外蕴状态(External State)。

  内蕴状态:

  享元对象的内蕴状态是不会随环境的改变而改变的,是存储在享元对象内部的状态信息,因此内蕴状态是可以共享的,对于任何一个享元对象来讲,它的值是完全相同的。就想上边的“黑子”和“白子”,它代表的状态就是内蕴状态。

  外蕴状态:

  享元对象的第二类状态就是外蕴状态,它会随着环境的改变而改变,因此是不可以共享的状态,对于不同的享元对象来说,它的值可能是不同的。享元对象的外蕴状态必须由客户端保存,在享元对象被创建之后,需要使用的时候再传入到享元对象内部,就像五子棋的位置信息,代表的就是享元对象的外蕴状态。

  所以,享元对象的外蕴状态与内蕴状态是两类相互独立的状态,彼此没有关联。

5、实现外蕴状态

  外蕴状态变量是需要随着环境的变化而改变的,我们需要在抽象棋子类中增加棋子位置即坐标信息,以及设置位置的方法内容。

  增加棋子位置信息的抽象类为:

<span style="font-size:18px;">
/**
* 需求:棋子的超类,含有一个棋子类别的属性,标志具体的棋子类型
* @author 猛龙过江
*
*/
public abstract class AbstractChessman {
//棋子类别
protected String chess;
//棋子坐标
protected int x;
protected int y;
//构造方法
public AbstractChessman(String chess){
this.chess = chess;
}
//坐标设置
public abstract void point(int x,int y);
//显示棋子信息
public void show(){
System.out.println(this.chess+"("+this.x+","+this.y+")");
}
}</span>

  完善后的黑子类:
<span style="font-size:18px;">
/**
* 需求:黑子类
* @author 猛龙过江
*
*/
public class BlackChessman extends AbstractChessman {
/*
* 构造方法,初始化黑棋子
*/
public BlackChessman(){
super("●");
System.out.println("--一颗黑棋子诞生了!--");
}
/*
* 重写方法
*/
@Override
public void point(int x, int y) {
this.x = x;
this.y = y;
this.show();

}

}</span>

  完善后的白子类为:
<span style="font-size:18px;">
/**
* 需求:白棋子
* @author 猛龙过江
*
*/
public class WhiteChessman extends AbstractChessman {
/*
* 构造方法,初始化黑棋子
*/
public WhiteChessman(){
super("○");
System.out.println("--一颗白棋子诞生了!--");
}
/*
* 重写方法
*/
@Override
public void point(int x, int y) {
this.x = x;
this.y = y;
this.show();

}
}</span>

  在棋子工厂中不需要进行任何改变,因为在棋子工厂中我们获得的是共享对象,外蕴状态(位置信息)是需要在客户端进行设置的。
  客户端:

<span style="font-size:18px;">import java.util.Random;

/**
* 需求:客户端(测试类)
* @author 猛龙过江
*
*/
public class Test {
public static void main(String[] args) {
//创建工厂
ChessmanFactory chessmanFactory = ChessmanFactory.getInstance();
//随机数,用于生成棋子对象
Random random = new Random();
int radom = 0;
AbstractChessman abstractChessman = null;
//随机获得棋子
for (int i = 0; i < 10; i++) {
radom = random.nextInt(2);
switch (radom) {
case 0:
//获得黑棋子
abstractChessman = chessmanFactory.getChessmanObject('B');
break;
case 1:
//获得黑棋子
abstractChessman = chessmanFactory.getChessmanObject('W');
break;
}
if (abstractChessman!=null) {
abstractChessman.point(i, random.nextInt(15));
}
}
}
}</span>
  

  经过测试后,我们就能够得到带有不同位置的五子棋位置了。我们得到,享元模式的重点在于共享元对象,降低内存的使用空间,提高系统性能。享元对象的外蕴状态是通过客户端来保存传入的,它是可能会发生变化的,因此,在我们进行软件系统设计的时候,一定要区分享元对象的内蕴状态和外蕴状态,不能混淆,更不能互相关联,二者应该是彼此分开的。

6、享元对象的特点:

  享元模式的重点在于“共享对象实例,降低内存空间”。不是一需要对象实例就需要去new对象的,如果可以利用其他对象实例,则应该共享对象实例。共享细粒度对象,降低内存空间,提高系统性能;

  “封装变化的部分”,在这里内蕴状态是不变的部分,而外蕴状态是变化的部分,所以我们使内蕴状态在类中被传入,而外蕴状态是从客户端传入;

7、使用场景:

  当系统中某个对象类型的实例较多的时候;

  当系统设计时候,对象实例真正有区别的分类很少,例如对于拼音,如果对每个字母都new一个对象实例的话,我们需要52个对象,这样实例就太多了,享元模式一般是给出本地内存资源节省的方案,不适于互联网分布式应用的情况;

  单例模式本身就是一种享元模式,单例模式中只有一个对象实例,被其他对象所共享。

8、Java中的享元模式

  在Java中,lang包下的Integer类,对于经常使用的-128 到 127 范围内的Integer对象当类一被加载时就被创建了,并保存在cache数组中,一旦程序调用valueOf 方法,如果i的值是在-128 到 127 之间就直接在cache缓存数组中去取Integer对象而不是创建一个新对象,这就是享元模式的应用。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: