博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
享元模式
阅读量:6845 次
发布时间:2019-06-26

本文共 5972 字,大约阅读时间需要 19 分钟。

1、什么是享元模式?

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

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

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

2、享元模式类图:

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

3、案例

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

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

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

  黑子类:

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

  白子类:

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

  下面来设计棋子工厂类,棋子工厂类我们设计为单例模式,该类用来生产棋子对象实例,并放入缓存当中,下次再获得棋子对象的时候就从缓存当中获得。内容如下:

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
cache = new Hashtable
(); //构造方法私有化 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; }}

  通过客户端进行测试:

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();			}		}	}}

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

4、需求改了

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

  内蕴状态:

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

  外蕴状态:

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

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

5、实现外蕴状态

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

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

/** * 需求:棋子的超类,含有一个棋子类别的属性,标志具体的棋子类型 * @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+")");	}}

  完善后的黑子类:

/** * 需求:黑子类 * @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();			}	}

  完善后的白子类为:

/** * 需求:白棋子 * @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();			}}

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

  客户端:

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));			}		}	}}

  

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

6、享元对象的特点:

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

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

7、使用场景:

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

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

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

8、Java中的享元模式

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

你可能感兴趣的文章
【yum】yum的使用
查看>>
RefreshListView中onItemClick点击错位
查看>>
xutils3批量上传文件
查看>>
BBS论坛(十)
查看>>
powerdesigner中实现PDM到MYSQl数据库的转换
查看>>
《新参者》—— 读后总结
查看>>
vim常用命令
查看>>
NodeJS写个爬虫,把文章放到kindle中阅读
查看>>
jQuery 修改 span 里的内容
查看>>
Domino SSO配置无问题,但就是不生效
查看>>
c语言 自定义strlen
查看>>
superdic cracked by TK
查看>>
开发人员需要熟知的常用Linux命令之七:Gzip及其常用打包、压缩、解压命令
查看>>
转一个打包程序教程
查看>>
Android -----listView的属性大全
查看>>
快速排序算法之我见(附上C代码)
查看>>
FineUI参考手册(离线版)现已免费提供下载!
查看>>
Nginx+Windows负载均衡(转载)
查看>>
[推荐]ORACLE PL/SQL编程之四:把游标说透(不怕做不到,只怕想不到)
查看>>
优化IPOL网站中基于DCT(离散余弦变换)的图像去噪算法(附源代码)。
查看>>