定义
享元模式以共享的方式高效的支持大量的细粒度对象。它使用共享物件,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于只是因重复而导致使用无法令人接受的大量内存的大量物件。通常物件中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。
内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的
外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。
UML
单纯享元模式的结构。
1) 抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。在Java中可以由抽象类、接口来担当。
2) 具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
3) 享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!
4) 客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
怎么咋看咋像简单工厂模式呢!没错,可以说结构型的单纯享元模式和创建型的简单工厂模式实现上非常相似,但是它的重点或者用意却和工厂模式截然不同。工厂模式的使用主要是为了使系统不依赖于实现得细节(见《深入浅出工厂模式》);而在享元模式的主要目的如前面所述:采用共享技术来避免大量拥有相同内容对象的开销。正所谓“旧瓶装新酒”阿!
复合享元模式的结构。
1) 抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。在Java中可以由抽象类、接口来担当。
2) 具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
3) 复合享元角色:它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。
4) 享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!
5) 客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
统比一下单纯享元对象和复合享元对象,里面只多出了一个复合享元角色,但是它的结构就发生了很大的变化。我们还是使用类图来表示下:
你也许又纳闷了,这个也似曾相逢!单看左半部,和简单工厂模式类似;再看右半部,怎么这么像合成模式呢(请参看关于合成模式的文章或者期待我的《深入浅出合成模式》)!合成模式用在此处就是为了将具体享元角色和复合享元角色同等对待和处理,通过将享元模式与合成模式组合在一起,可以确保复合享元中所包含的每个单纯享元都具有相同的外蕴状态,而这些单纯享元的内蕴状态往往是不同的。
实例
假如我们要开发一个类似MS Word的字处理软件,下面分析一下将如何来实现。对于这样一个字处理软件,它需要处理的对象既有单个字符,又有由字符组成的段落以及整篇文档,根据面向对象的设计思想,不管是字符、段落还是文档都应该作为单个的对象去看待。我们暂不考虑段落和文档对象,只考虑单个的字符,于是可以很容易的得到下面的结构图:
//抽象的字符类
public abstract class Charactor{
//属性
protected char letter;
protected int fontsize;
//显示方法
public abstract void display();
}
//具体的字符类A
public class CharactorA extends Charactor{
//构造函数
public CharactorA(){
this.letter = 'A';
this.fontsize = 12;
}
//显示方法
public void display(){
try{
System.out.println(this.letter);
}catch(Exception err){
}
}
}
//具体的字符类B
public class CharactorB extends Charactor{
//构造函数
public CharactorB(){
this.letter = 'B';
this.fontsize = 12;
}
//显示方法
public void display(){
try{
System.out.println(this.letter);
}catch(Exception err){
}
}
}
我们的这段代码完全符合面向对象的思想,但是却为此搭上了太多的性能损耗,代价很昂贵。
一篇文档的字符数量很可能达到成千上万,甚至更多,那么在内存中就会同时存在大量的Charactor对象,这时候的内存开销可想而知。
我们把这些不可共享的状态仍然保留在Charactor对象中,把不同的状态通过参数化的方式,由客户程序注入。letter为内蕴,fontsize为外韵
public abstract class Charactor{
//属性
protected char letter;
protected int fontsize;
//显示方法
public abstract void display();
//设置字体大小
public abstract void setFontSize(int fontsize);
}
//具体的字符类A
public class CharactorA extends Charactor{
//构造函数
public CharactorA(){
this.letter = 'A';
this.fontsize = 12;
}
//显示方法
public void display(){
try{
System.out.println(this.letter);
}catch(Exception err){
}
}
//设置字体大小
public void setFontSize(int fontsize){
this.fontsize = fontsize;
}
}
//具体的字符类B
public class CharactorB extends Charactor{
//构造函数
public CharactorB(){
this.letter = 'B';
this.fontsize = 12;
}
//显示方法
public void display(){
try{
System.out.println(this.letter);
}catch(Exception err){
}
}
//设置字体大小
public void setFontSize(int fontsize){
this.fontsize = fontsize;
}
}
//客户程序
public class ClinetTest{
public static void main(String[] args){
Charactor a = new CharactorA();
Charactor b = new CharactorB();
//设置字符A的大小
a.setFontSize(12);
//显示字符B
a.display();
//设置字符B的大小
b.setFontSize(14);
//显示字符B
b.display();
}
}
优点
享元模式优点就在于它能够大幅度的降低内存中对象的数量
缺点
享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。
使用场景
一个应用程序使用了大量的对象。 完全由于使用大量的对象,造成很大的存储开销。 对象的大多数状态都可变为外部状态。 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。且 应用程序不依赖对象标识。
以上内容部分摘自:http://blog.youkuaiyun.com/ai92/article/details/224598