1.什么是享元模式?
软件运行时存在大量相同或相似的对象,会浪费资源。
享元模式通过共享技术实现相同或相似对象的重用。在逻辑上每一个字符都由一个对象与之对应,在物理上共享一个享元对象。
在享元模式中存储这些共享实例对象的地方称为享元池。可以针对每一个不同的字符创建一个享元对象,将其放在享元池。
享元对象分为内部状态和外部状态:
内部状态:
存储在享元对象内部,不会随环境变化而变化的状态,可以共享。比如‘a’永远是'a',不会变成‘b’
外部状态:
随环境变化而变化,不可以共享的状态。通常由客户端保存,并在享元对象被创建之后需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态时相互独立的。比如'a'有时是绿色有时是红色。
享元模式:运用共享技术有效的支持大量细粒度对象的复用。
(这一张开局概念多有点。。。)
2.享元模式结构
(1)Flyweight(抽象享元类):抽象享元类通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
(2)ConcreteFlyweight(具体享元类):具体享元类实现了抽象享元类,其实例成为享元对象。在具体享元类中为内部状态提供了存储空间。通常可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的向源对象。
(3)UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可以设计为非共享具体享元类。当需要一个非共享具体享元类的对象时可以直接通过实例化创建
(4)FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合,也可以结合工厂模式设计。当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例,返回新创建的实例并将其存储在享元池中。
3.享元模式实现
(1)抽象享元类
/**
* 抽象享元类
*/
public abstract class FlyWeight {
public abstract void operation(String extrinsicState);
}
(2)具体享元类
/**
* 具体享元类
*/
public class ConcreteFlyWeight extends FlyWeight {
//内部状态intrinsicState作为成员变量,同一个享元对象的内部状态时一致的
private String intrinsicState;
public ConcreteFlyWeight(String intrinsicState){
this.intrinsicState = intrinsicState;
}
public ConcreteFlyWeight() {
}
//外部状态extrinsicState在使用时由外部设置,不保存在享元对象中,即使是同一个对象
//在每一次调用时可以传入不同的外部状态
@Override
public void operation(String extrinsicState) {
//实现业务方法
}
}
(3)非共享型具体享元类
/**
* 非共享型具体享元类
*/
public class UnsharedConcreteFlyweight extends FlyWeight {
@Override
public void operation(String extrinsicState) {
//实现业务方法
}
}
(4)享元工厂类
/**
* 享元工厂类
*/
public class FlyweightFactory {
//定义一个HashMap用于存储享元对象,实现享元池
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;
}
}
}
4.享元模式实例——设计围棋软件的棋子对象,以及显示棋子的位置
(1)围棋棋子类
/**
* 围棋棋子类
*/
public abstract class IgoChessman {
public abstract String getColor();
public void display(Coordinates coordinates){
System.out.println("棋子颜色:"+this.getColor()+",棋子位置:"+coordinates.getX()+","+coordinates.getY());
}
}
(2)黑色棋子,充当具体享元类
/**
* 黑色棋子,充当具体享元类
*/
public class BlackIgoChessman extends IgoChessman {
@Override
public String getColor() {
return "黑色";
}
}
(3)白色棋子,充当具体享元类
/**
* 白色棋子,充当具体享元类
*/
public class WhiteIgoChessman extends IgoChessman {
@Override
public String getColor() {
return "白色";
}
}
(4)棋子坐标
/**
* 棋子坐标
*/
public class Coordinates {
private int x;
private int y;
public Coordinates(int x,int y){
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
(5)围棋棋子工厂类,充当享元工厂类,使用单例模式对其进行设计
/**
* 围棋棋子工厂类,充当享元工厂类,使用单例模式对其进行设计
*/
public class IgoChessmanFactory {
private static IgoChessmanFactory instance = new IgoChessmanFactory();
private static Hashtable ht;//充当享元池
private IgoChessmanFactory(){
ht = new Hashtable();
IgoChessman black,white;
black = new BlackIgoChessman();
ht.put("b",black);
white = new WhiteIgoChessman();
ht.put("w",white);
}
//返回享元工厂类的唯一实例
public static IgoChessmanFactory getInstance(){
return instance;
}
//通过key获取存储在Hashtable中的享元对象
public IgoChessman getIgoChessman(String color){
return (IgoChessman)ht.get(color);
}
}
(6)客户端
public class Client {
public static void main(String[] args) {
IgoChessman black1,black2,black3,white1,white2;
IgoChessmanFactory factory;
//获取享元工厂对象
factory = IgoChessmanFactory.getInstance();
//通过享元工厂获取3颗黑子
black1 = factory.getIgoChessman("b");
black2 = factory.getIgoChessman("b");
black3 = factory.getIgoChessman("b");
System.out.println("判断两颗黑子是否相同:"+(black1 == black2));
//通过享元工厂获取2颗白子
white1 = factory.getIgoChessman("w");
white2 = factory.getIgoChessman("w");
System.out.println("判断两颗白子是否相同:"+(white1 == white2));
//显示棋子
black1.display(new Coordinates(1,1));
black2.display(new Coordinates(2,2));
black3.display(new Coordinates(3,3));
white1.display(new Coordinates(4,4));
white2.display(new Coordinates(5,5));
}
}
(6)输出结果:虽然客户端新建了多个,但是实际上对应的都是同一个
5.单纯享元模式和复合享元模式
(1)单纯享元模式
所有的具体享元类都是可以共享的,不存在非共享具体享元类
(2)复合享元模式
将一些单纯享元对象使用组合模式(看往期文章)加以组合还可以形成复合享元对象,这样的复合享元对象本身不能共享,但是他们可以分解为单纯享元模式
6.享元模式与String类
public class StringDemo {
public static void main(String[] args) {
String str1 = "abcd";
String str2 = "abcd";
String str3 = "ab"+"cd";
String str4 = "ab";
str4 += "cd";
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str1 == str4);
str2 += "e";
System.out.println(str1 == str2);
}
}
输出结果:
String用的其实也是享元模式。但是每次修改拷贝一份,然后再拷贝的那份做修改。
7.享元模式优缺点
优:
(1)减少内存中对象的数量
(2)外部状态相对独立,不会影响内部状态
缺:
(1)分外部内部,系统复杂
(2)读取外部状态使运行时间变长
8.使用情况
(1)大量相同或相似对象
(2)对象大部分状态可以外部化
(3)维护一个享元池,只有多次重复使用享元对象的时候才是用享元模式