Java设计模式之享元模式

什么是享元模式

享元模式(Flyweight)就是把部分和整体的关系用树形结构来表示,从而使客户端能够把一个个的部分对象和有他们组合起来的整体对象采用同样的方式看待,他也是一个继承的替代,其实具体的说,享元模式就是用时间交换了空间。用程序的运行速度来读取是否重复的对象内容,然后不创建一个重复的对象来节省空间,于此同时就大大提高了程序的运行效率。亨元模式核心在于运用共享技术来有效地支持大量细粒度的对象。
Java设计模式之享元模式

内部状态与外部状态

亨元模式要求将对象的属性划分为内部状态和外部状态。亨元模式的目标旨在减少共享对象的数量,关于如何划分内部状态和外部状态,下面有点经验指引:

  • 内部状态存储于对象内部
  • 内部状态可以被一些对象共享
  • 内部状态独立于具体的场景,通常不会改变
  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享 ,这样我们可以把所有内部状态(性别)相同的对象(例如性别为女的一类50个人)指定为同一个共享独享(最终只需要以一个人),外部对象可以从对象上脱离出来(T恤被脱离),并存储于外部。
  • 剥离了外部状态(T恤)的对象成为共享对象(maleModel),外部状态在必要时被传入共享对象然后组装成为一个完整的对象。虽然耗时,但是减少了内存占用,亨元模式就是以时间换空间的优化模式。

示例

通过一个字符的库创建简单的理解了一下享元模式,我们要创建一个字符库,这样就避免不了一些字符使我们想重复用的,这样如果我们每次用都重新的创建一个对象,然后分配空间,那么对于好多重复的字符会明显的浪费空间,这个时候就引入了享元模式。

首先创建一个字符的接口,定义了一个负责拿到所在实例中的字符的方法getName();如下:

package coml.designpattern.flyweight;
 
public interface Characters {
	public String getName();
}

紧接着定义了一个公用的子类实现了这个接口,书中是做了N个子类实现,这里我就用了一个子类,并在Flyweight种也有区别,个人觉得这样减少了代码的复用:

package coml.designpattern.flyweight;
 
public class Character implements Characters {
 
	private String name;
 
	public Character(String name) {
		this.name = name;
	}
 
	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return this.name;
	}
 
}

然后在Flyweight中定义创建字符的具体的方法和相关的逻辑:

package coml.designpattern.flyweight;
 
import java.util.ArrayList;
import java.util.List;
 
public class Flyweight {
	private List list = new ArrayList();
 
	public Characters getCharacter(String name) {
		Characters characters = null;
 
		for (int i = 0; i < list.size(); i++) {
			characters = (Characters) list.get(i);
			if (name.equals(characters.getName())) {
				System.out.println(name + "不是第一次使用,未分配空间");
				break;
			} else {
				characters = null;
			}
		}
 
		if (characters == null) {
			characters = new Character(name);
			System.out.println(name + "第一次使用,并分配空间");
			list.add(characters);
		}
 
		return characters;
	}
}

定义了一个list,每次有新对象的时候就放进去,然后每次调用GetCharacter时候都会去遍历整个list想要的对象有没有创建好,如果有就不再创建,如果没有在创建,书中这里是用了N个Characters的实现类,在if语句块里面定义了N中情况,每一个字符就是一个对象,我这里就一个实现类,然后用不同的引用而已, 其实效果是一样的,然后客户端的调用:

package coml.designpattern.flyweight;
 
public class Client {
	public static void main(String[] args) {
		Flyweight flyweight = new Flyweight();
		Characters study1 = flyweight.getCharacter("study");
		Characters pattern = flyweight.getCharacter("pattern");
		Characters study2 = flyweight.getCharacter("study");
		Characters java = flyweight.getCharacter("java");
		Characters study3 = flyweight.getCharacter("study");
		Characters js = flyweight.getCharacter("js");
 
		System.out.println(study1.getName() + "\t" + java.getName() + "\t"
				+ study2.getName() + "\t" + pattern.getName() + "\t"
				+ study3.getName() + "\t" + js.getName() + "\t");
	}
}

输出结果:

study第一次使用,并分配空间
pattern第一次使用,并分配空间
study不是第一次使用,未分配空间
java第一次使用,并分配空间
study不是第一次使用,未分配空间
js第一次使用,并分配空间
study	java	study	pattern	study	js	

这样发现study虽然使用了三次但是他只创建了一次,如果使用N次,那么会很明显的减少了空间的使用; 在数据库连接池就是实际中的应用,先创建好一个池,每次有人要取得数据不会再像JDBC一样创建一个新的连接,这样减少了服务器端的压力,提高了项目的性能;使用享元模式可以大大节省空间,但是需要维护所有的享元对象,如果 要维护的享元很多,在查找的时候要消耗大量的时间,因此享元模式是典型的以时间来交换空间。

适用场景

  • 一个程序使用了大量相似的对象
  • 由于使用大量对象造成内存开销
  • 对象的大多数状态为外部状态
  • 剥离对象外部状态之后,可以使用相对较少的共享对象来取代大量对象。

优缺点

享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:

●  享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。

●  享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。