享元模式通过使用共享对象从而有效地支持大量的细粒度的对象,其共享的关键在于享元模式区分了内部状态和外部状态。
内部状态是存储在享元对象内部的、可以共享的信息,并且不会随着环境的改变而改变。外部状态是随环境而改变且不可共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。在Java中最常见的如:线程池、数据库连接池等,就是享元模式的一种体现。我们知道线程池可以避免不停地创建和销毁多个对象而导致的性能问题,而享元模式也是为了减少内存的使用,避免出现大量重复的创建销毁对象的场景。享元模式将多个对同一对象的访问集中起来,不必为每个访问者创建一个单独的对象,从而降低内存的消耗。
享元模式的角色划分:
抽象享元角色(FlyWeight):产品的抽象类,同时定义出对象的外部状态和内部状态的接口或者实现。具体享元角色(ConcreteFlyWeight):具体的产品类,实现抽象角色定义相关业务。非共享角色(UnSharedConcreteFlyWeight):一般不会出现在享元工厂。享元工厂类(FlyWeightFactory):用于构建一个池容器(集合),同时提供从池中获取对象的方法。享元模式的UML如下:
(1)优点
大幅减少内存中对象的数量。降低程序内存占用的空间。提高性能。(2)缺点
增加了系统的复杂性,需要区分出内部状态和外部状态,而且内部状态具有固化特性,不应该随外部状态改变而改变,使得程序的逻辑更为复杂化。享元模式将享元对象的状态外部化,而读取外部状态使得运行时间更长。场景描述:江流儿和黑木的围棋对决,具体实现如下:
建立抽象享元角色如下:
/** * 抽象享元角色:该接口中规范了江流儿和黑木的围棋落子方法:put */ public interface Chesspiece { /** * 落子方法 * @param x 落子的位置 * @param y 落子的位置 */ void put(int x, int y); }棋子具体享元角色如下:
/** * 具体享元角色:实现棋子接口Chesspiece */ public class WhitePieces implements Chesspiece{ //color属性就是棋子的内部状态 private String color; public WhitePieces(String color) { this.color = color; } @Override public void put(int x, int y) { System.out.println(this.color + "棋子的落子的位置:x : " + x + "\t\t" + "y : " + y); } }/** * 具体享元角色:实现棋子接口Chesspiece */ public class BlackPieces implements Chesspiece{ //color属性就是棋子的内部状态 private String color; public BlackPieces(String color) { this.color = color; } @Override public void put(int x, int y) { System.out.println(this.color + "棋子的落子的位置:x : " + x + "\t\t" + "y : " + y); } }
建立享元工厂类如下:
/** * 享元工厂类,负责创建和管理棋子 */ public class ChesspieceFactory { private static Map<String, Chesspiece> map = new HashMap<>(); /** * 下棋时需要黑白棋子,所以在棋子工厂中创建好了黑白两个妻子对象,放入Map,而getPiece()则通过颜色返回相应的棋子对象 */ static { map.put("白色", new WhitePieces("白色")); map.put("黑色", new BlackPieces("黑色")); } public static Chesspiece getPiece(String color){ if(color.equals("白色")){ return map.get(color); }else if(color.equals("黑色")){ return map.get(color); }else{ return null; } } }测试类如下:
public class Game { public static void main(String[] args) { Chesspiece p1 = ChesspieceFactory.getPiece("白色"); p1.put(1,1); Chesspiece p2 = ChesspieceFactory.getPiece("黑色"); p2.put(5,7); Chesspiece p3 = ChesspieceFactory.getPiece("黑色"); p3.put(5,3); Chesspiece p4 = ChesspieceFactory.getPiece("白色"); p4.put(3,4); } }执行结果如下:
原型模式是通过原型实例指定创建对象,从而达到类的复用的目的,通过深浅拷贝类的方式来实现复用,从而节约资源开销。享元模式通过定义内在状态和外在状态来达到对象共享,运用共享的技术来支持大量细粒度的对象。
(1)单例模式保证的是类仅有一个实例,然后提供一个全局访问点,所有访问都可以访问得到。达到的是一个实例数据的共享。享元模式通过控制内在状态和外在状态来达到共享的目的。
(2)享元设计模式是一个类有很多对象,而单例是一个类仅一个对象。
(3)享元模式更加注重的是节约内存空间,提高其性能。单例模式注重的是数据的共享。
我们先来看一个程序实例:
public class TestInteger { public static void main(String[] args) { Integer x = Integer.valueOf(127); Integer y = new Integer(127); Integer z = Integer.valueOf(127); Integer w = new Integer(127); System.out.println(x.equals(y));//1 System.out.println(x == y);//2 System.out.println(x == z);//3 System.out.println(w == x);//4 System.out.println(w == y);//5 } }执行结果如下:
这里可以来分析一下执行结果:
对于1处,由于equals()方法比较的是值,所以输出结果为1。对于5处,由于w和y对象都是new出来的,所以它们的地址肯定不同,输出结果为false。
接下来,对2、3、4处进行分析,我们可以先来看一下Integer的valueOf()方法的源码:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }这里的low和high的值分别为:-128和127。也即是说在valueOf()方法中,先判断值是否在IntegerCache中,如果不在,就创建新的Integer实例,否则,就直接从缓存池中返回。而这里正是使用到了享元模式。这样我们分析2、3、4处就比较清晰了。当x创建的时候,此时IntegerCache中并不存在127,所以创建新的对象,而y也是new出来的,因此2处输出结果为false。当创建z时,发现IntegerCache中已有127,则直接返回,因此3处输出结果为true。4处由于是new出来的,自然也就和x地址不同,输出false。
