面向对象技术可以很好地解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致运行代价过高,带来性能下降等问题。享元模式通过共享技术实现相同或相似对象的重用提高系统资源的利用率。
运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。享元模式结构较为复杂,一般结合工厂模式一起使用。
面向对象技术可以很好地解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致运行代价过高,带来性能下降等问题。享元模式正是为解决这一类问题而诞生的
享元模式通过共享技术实现相同或相似对象的重用,示意图如下(我们可以共用一个 Hello world 对象,其中字符串 “Hello world” 为内部状态,可共享;字体颜色为外部状态,不可共享,由客户端设定):
在享元模式中可以共享的相同内容称为 内部状态(Intrinsic State),而那些需要外部环境来设置的不能共享的内容称为 外部状态(Extrinsic State),其中外部状态和内部状态是相互独立的,外部状态的变化不会引起内部状态的变化。
由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。
也就是说,享元模式的本质是分离与共享 : 分离变与不变,并且共享不变。
把一个对象的状态分成内部状态和外部状态,内部状态即是不变的,外部状态是变化的;然后通过共享不变的部分,达到减少对象数量并节约内存的目的。
在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)(用于存储具有相同内部状态的享元对象)。在享元模式中,共享的是享元对象的内部状态,外部状态需要通过环境来设置。在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为 细粒度对象。
享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。
//抽象享元角色类
public interface Flyweight {
//一个示意性方法,参数state是外蕴状态
public void operation(String state);
}
//具体享元角色类
//具体享元角色类ConcreteFlyweight有一个内蕴状态,
//在本例中一个Character类型的intrinsicState属性代表,它的值应当在享元对象
//被创建时赋予。所有的内蕴状态在对象创建之后,
//就不会再改变了。如果一个享元对象有外蕴状态的话,所有的外部状态都必须存储在客户端,
//在使用享元对象时,再由客户端传入享元对象。
//这里只有一个外蕴状态,operation()方法的参数state就是由外部传入的外蕴状态。
public class ConcreteFlyweight implements Flyweight {
private Character intrinsicState = null;
/**
* 构造函数,内蕴状态作为参数传入
* @param state
*/
public ConcreteFlyweight(Character state){
this.intrinsicState = state;
}
/**
* 外蕴状态作为参数传入方法中,改变方法的行为,
* 但是并不改变对象的内蕴状态。
*/
@Override
public void operation(String state) {
// TODO Auto-generated method stub
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}
//享元工厂角色类
//享元工厂角色类,必须指出的是,客户端不可以直接将具体享元类实例化,
//而必须通过一个工厂对象,利用一个factory()方法得到享元对象。
//一般而言,享元工厂对象在整个系统中只有一个,因此也可以使用单例模式。
//当客户端需要单纯享元对象的时候,需要调用享元工厂的factory()方法,
//并传入所需的单纯享元对象的内蕴状态,由工厂方法产生所需要的
//享元对象。
public class FlyweightFactory {
private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
public Flyweight factory(Character state){
//先从缓存中查找对象
Flyweight fly = files.get(state);
if(fly == null){
//如果对象不存在则创建一个新的Flyweight对象
fly = new ConcreteFlyweight(state);
//把这个新的Flyweight对象添加到缓存中
files.put(state, fly);
}
return fly;
}
}
//客户端类
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
FlyweightFactory factory = new FlyweightFactory();
Flyweight fly = factory.factory(new Character('a'));
fly.operation("First Call");
fly = factory.factory(new Character('b'));
fly.operation("Second Call");
fly = factory.factory(new Character('a'));
fly.operation("Third Call");
}
}
虽然客户端申请了三个享元对象,但是实际创建的享元对象只有两个,这就是共享的含义。运行结果如下:
一个复合享元对象的所有单纯享元对象元素的外蕴状态都是与复合享元对象的外蕴状态相等的; operation方法操作的是外蕴状态,而享元对象的构造方法是初始化享元对象的内蕴状态
//抽象享元角色类
public interface Flyweight {
//一个示意性方法,参数state是外蕴状态
public void operation(String state);
}
//具体享元角色类
//具体享元角色类ConcreteFlyweight有一个内蕴状态,
//在本例中一个Character类型的intrinsicState属性代表,它的值应当在享元对象
//被创建时赋予。所有的内蕴状态在对象创建之后,就不会再改变了。
//如果一个享元对象有外蕴状态的话,所有的外部状态都必须存储在客户端,
//在使用享元对象时,再由客户端传入享元对象。
//这里只有一个外蕴状态,operation()方法的参数state就是由外部传入的外蕴状态。
public class ConcreteFlyweight implements Flyweight {
private Character intrinsicState = null;
/**
* 构造函数,内蕴状态作为参数传入
* @param state
*/
public ConcreteFlyweight(Character state){
this.intrinsicState = state;
}
/**
* 外蕴状态作为参数传入方法中,改变方法的行为,
* 但是并不改变对象的内蕴状态。
*/
@Override
public void operation(String state) {
// TODO Auto-generated method stub
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}
//复合享元角色类
//复合享元对象是由单纯享元对象通过复合而成的,
//因此它提供了add()这样的聚集管理方法。由于一个复合享元对象具有不同的聚集元素,
//这些聚集元素在复合享元对象被创建之后加入,
//这本身就意味着复合享元对象的状态是会改变的,因此复合享元对象是不能共享的。
//复合享元角色实现了抽象享元角色所规定的接口,
//也就是operation()方法,这个方法有一个参数,代表复合享元对象的外蕴状态。
//一个复合享元对象的所有单纯享元对象元素的外蕴状态都是与复合享元对象的外蕴状态相等的;
//而一个复合享元对象所含有的单纯享元对象的内蕴状态一般是不相等的,不然就没有使用价值了。
public class ConcreteCompositeFlyweight implements Flyweight {
private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
/**
* 增加一个新的单纯享元对象到聚集中
*/
public void add(Character key , Flyweight fly){
files.put(key,fly);
}
/**
* 外蕴状态作为参数传入到方法中
*/
@Override
public void operation(String state) {
Flyweight fly = null;
for(Object o : files.keySet()){
fly = files.get(o);
fly.operation(state);
}
}
}
//享元工厂角色类
//享元工厂角色提供两种不同的方法,一种用于提供单纯享元对象,另一种用于提供复合享元对象。
public class FlyweightFactory {
private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
/**
* 复合享元工厂方法
*/
public Flyweight factory(List<Character> compositeState){
ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight();
for(Character state : compositeState){
compositeFly.add(state,this.factory(state));
}
return compositeFly;
}
/**
* 单纯享元工厂方法
*/
public Flyweight factory(Character state){
//先从缓存中查找对象
Flyweight fly = files.get(state);
if(fly == null){
//如果对象不存在则创建一个新的Flyweight对象
fly = new ConcreteFlyweight(state);
//把这个新的Flyweight对象添加到缓存中
files.put(state, fly);
}
return fly;
}
}
//客户端类
public class Client {
public static void main(String[] args) {
List<Character> compositeState = new ArrayList<Character>();
compositeState.add('a');
compositeState.add('b');
compositeState.add('c');
compositeState.add('a');
compositeState.add('b');
FlyweightFactory flyFactory = new FlyweightFactory();
Flyweight compositeFly1 = flyFactory.factory(compositeState);
Flyweight compositeFly2 = flyFactory.factory(compositeState);
compositeFly1.operation("Composite Call");
System.out.println("---------------------------------");
System.out.println("复合享元模式是否可以共享对象:" + (compositeFly1 == compositeFly2));
Character state = 'a';
Flyweight fly1 = flyFactory.factory(state);
Flyweight fly2 = flyFactory.factory(state);
System.out.println("单纯享元模式是否可以共享对象:" + (fly1 == fly2));
}
}
典型的享元工厂类代码:
public class FlyweightFactory
{
private HashMap flyweights = new HashMap();
public Flyweight getFlyweight(String key)
{
if(flyweights.containsKey(key))
{
return (Flyweight)flyweights.get(key);
}
else
{
Flyweight fw = new ConcreteFlyweight();
flyweights.put(key,fw);
return fw;
}
}
}
享元模式以共享的方式高效地支持大量的细粒度对象,享元对象能做到共享的关键是区分内部状态(Internal State)和外部状态(External State)。其中:
典型的享元类代码:
public class Flyweight
{
//内部状态作为成员属性
private String intrinsicState;
public Flyweight(String intrinsicState)
{
this.intrinsicState = intrinsicState;
}
public void operation(String extrinsicState)
{
......
}
}
抽象享元类
//池对象
//将其包裹成可被池管理的对象。
public abstract class PooledObject
{
//进行连接操作
public abstract void ConnectOperation(String con);
}
具体享元类
//具体享元对象
//处理Tcp连接
public class TcpPooledObject extends PooledObject
{
//内蕴对象
String tcpConnection=null;
TcpPooledObject(String tcpCon)
{
//连接
tcpConnection=tcpCon;
}
@Override
public void ConnectOperation(String timeout)
{
System.out.println("连接"+tcpConnection);
System.out.println("连接超时时间:"+timeout);
}
}
享元工厂
//池对象工厂
public class PooledObjectFactory
{
//工厂采用单例模式创建
private PooledObjectFactory(){}
private static class SigntonInstance{
private static final PooledObjectFactory factory=new PooledObjectFactory();
}
static public PooledObjectFactory getPooledObjectFactory() {
return SigntonInstance.factory;
}
//使用guava创建HashMap
Map<String,PooledObject> conPools= Maps.newHashMap();
//获得连接池对象
public PooledObject getConnection(String connection)
{
//遍历map集合,看是否存在对应的连接
//存在直接获取
//否则创建新连接,放入池中
PooledObject PooledObject=null;
if(!conPools.containsKey(connection))
{
PooledObject = new TcpPooledObject(connection);
conPools.put(connection,PooledObject);
}
PooledObject=conPools.get(connection);
return PooledObject;
}
}
测试
public class Test
{
@org.junit.Test
public void test()
{
//获取连接池工厂
PooledObjectFactory factory=PooledObjectFactory.getPooledObjectFactory();
PooledObject mysqlConnection = factory.getConnection("mysql");
PooledObject oracleConnection = factory.getConnection("oracle");
PooledObject mysqlConnection2=factory.getConnection("mysql");
System.out.println(mysqlConnection==mysqlConnection2);
mysqlConnection.ConnectOperation("1000");
oracleConnection.ConnectOperation("2000");
}
}
Java中将String类定义为final(不可改变的),JVM中字符串一般保存在字符串常量池中,java会确保一个字符串在常量池中只有一个拷贝,这个字符串常量池在JDK6.0以前是位于常量池中,位于永久代,而在JDK7.0中,JVM将其从永久代拿出来放置于堆中。
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = "he" + "llo";
String s4 = "hel" + new String("lo");
String s5 = new String("hello");
String s6 = s5.intern();
String s7 = "h";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1==s2);//true
System.out.println(s1==s3);//true
System.out.println(s1==s4);//false
System.out.println(s1==s9);//false
System.out.println(s4==s5);//false
System.out.println(s1==s6);//true
}
}
使用例子如下:
public static void main(String[] args) {
Integer i1 = 12 ;
Integer i2 = 12 ;
System.out.println(i1 == i2);
Integer b1 = 128 ;
Integer b2 = 128 ;
System.out.println(b1 == b2);
}
输出是
true
false
为什么第一个是true,第二个是false?
反编译后可以发现 Integer b1 = 128; 实际变成了 Integer b1 = Integer.valueOf(128);,所以我们来看 Integer 中的 valueOf 方法的实现
public final class Integer extends Number implements Comparable<Integer> {
public static Integer valueOf(int var0) {
return var0 >= -128 && var0 <= Integer.IntegerCache.high ?
Integer.IntegerCache.cache[var0 + 128] : new Integer(var0);
}
//...省略...
}
IntegerCache 缓存类
//是Integer内部的私有静态类,里面的cache[]就是jdk事先缓存的Integer。
private static class IntegerCache {
static final int low = -128;//区间的最低值
static final int high;//区间的最高值,后面默认赋值为127,也可以用户手动设置虚拟机参数
static final Integer cache[]; //缓存数组
static {
// high value may be configured by property
int h = 127;
//这里可以在运行时设置虚拟机参数来确定h :-Djava.lang.Integer.IntegerCache.high=250
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {//用户设置了
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);//虽然设置了但是还是不能小于127
// 也不能超过最大值
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
//循环将区间的数赋值给cache[]数组
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
可以看到 Integer 默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象