享元模式

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对象而不是创建一个新对象,这就是享元模式的应用。




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值