结构型模式关注如何将现有类或对象组织一起形成更加强大的结构。
一、概念
享元模式(FlyWeight Pattern):运用共享技术有效地支持大量细粒度对象的复用。
享元模式的内外部状态:
- 内部状态(Intrinsic State):存储在享元对象内部并且不会随着环境改变而改变的状态,内部状态可以共享。
- 外部状态(Extrinsic State):随环境变化而变化的、不可共享的状态。通常由客户端保存,并在享元对象被创建后,需要使用的时候再传入享元对象内部。
正因为区别了内外部状态,可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以共享的,当需要的时候就将对象从享元池中取出,实现对象的复用。
通过想去出的对象注入不同的外部状态可以得到一系列相似的对象,而它们在内存中只存在一份。
二、结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pwpUQzbl-1635853614033)(C:\Users\64454\Desktop\ABC\结构型模式\FlyWeight\结构.jpg)]
- Flyweight(抽象享元类):定义为接口或抽象类,声明了具体享元类的公共方法。
- ConCreteFlyweight(具体享元类):实现了抽象享元类,存储内部状态。通常结合单例模式提供唯一的享元对象。
- UnsharedConcreteFlyweight(非共享具体享元类):并非所有抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类。
- FlyweightFactory(享元工厂类):用于创建、管理享元对象,将各种类型的享元对象存储在享元池中,一般设计为“K-V”映射集合,可结合工厂模式设计。
Flyweight(抽象享元类)
public abstract class Flyweight {
public abstract void operation(String extrinsicState);
}
ConCreteFlyweight(具体享元类)
public class ConcreteFlyweight extends Flyweight {
//内部状态 intrinsicState 作为成员变量,同一个享元对象的内部状态是一致的
private String intrinsicState;
public ConcreateFlyweight(String intrinsicState) {
this.intinsicState = intrinsicState;
}
//外部状态 extrinsicState 在使用时由外部设置,不保存在享元对象中,即使是同一个对象,在每一次调用时可以传入不同的外部状态
public void operation(String extrincState) {
//实现业务方法
}
}
UnsharedConcreteFlyweight(非共享具体享元类)
public class UnsharedConcreteFlyweight extends Flyweight {
public void operation(String extrinsicState) {
//实现业务方法
}
}
FlyweightFactory(享元工厂类)
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;
}
}
}
三、举例
软件公司需开发围棋软件,现需对黑白子进行定义。围棋棋子大小、形状一致,为节省存储空间、提供性能,使用享元模式设计棋子类:
围棋棋子类:充当抽象享元类
public abstract class IgoChessman {
public abstract String getColor();
public void display(Coordinates coordinates) {
System.out.println("棋子颜色:" + this.getColor() + "棋子位置" + coordinates.getX() + "," + coordinates.getY());
}
}
黑色棋子类:充当具体享元类
public class BlackIgoChessman extends IgoChessman {
@Override
public String getColor() {
return "黑色";
}
}
白色棋子类:充当具体享元类
public class WhiteIgoChessman extends IgoChessman {
@Override
public String getColor() {
return "白色";
}
}
围棋棋子工厂类:充当享元工厂类,使用单例模式对其进行设计
public class IgoChessmanFactory {
private static IgoChessmanFactory instance = new IgoChessmanFactory();
private static HashMap pool; //使用 HashMap 来存储享元对象,充当享元池
private IgoChessmanFactory() {
pool = new HashMap();
IgoChessman black, white;
black = new BlackIgoChessman();
pool.put("b",black);
white = new WhiteIgoChessman();
pool.put("w",white);
}
//返回享元工厂类的唯一实例
public static IgoChessmanFactory getInstance(){
return instance;
}
//通过 Key 获取存储在 HashMap 中的享元对象
public static IgoChessman getIgoChessman(String color) {
return (IgoChessman) pool.get(color);
}
}
棋子位置类:充当外部状态类
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;
}
}
测试代码:
IgoChessman black1, black2, black3, white1, white2;
IgoChessmanFactory factory;
//获取享元工厂对象
factory = IgoChessmanFactory.getInstance();
//通过享元工厂获取 3 颗黑子
black1 = factory.getIgoChessman("b");
black2 = factory.getIgoChessman("b");
black3 = factory.getIgoChessman("b");
//通过享元工厂获取 2 颗白子
white1 = factory.getIgoChessman("w");
white2 = factory.getIgoChessman("w");
//判断两颗棋子是否相同
System.out.println("判断两颗黑子是否相同:" + (black1 == black3));
System.out.println("判断两颗白子是否相同:" + (white1 == white2));
//显示棋子,同时设置棋子的坐标位置
black1.display(new Coordinates(1,2));
black2.display(new Coordinates(3,4));
black3.display(new Coordinates(1,3));
white1.display(new Coordinates(2,5));
white2.display(new Coordinates(2,4));
测试结果:
四、特点
☯ 优点
- 享元模式可以减少内存中对象的数量,使得相同/相似对象在内存中只保存一份。节约系统资源,提高性能。
- 外部状态相对独立,不会影响内部状态。
☯ 缺点
- 因需要分离内外部状态,使得系统逻辑复杂化。
- 为使对象共享,需将部分状态外部化。
☯ 适用
- 一个系统有啊大量相同/相似对象,造成大量内存浪费
- 对象的大部分状态都可以外部化,可以传入对象
- 维护享元池需要消耗系统资源,应在需要多次重复使用想元对象时使用享元模式
享元模式的注意事项和细节:
- 享元模式:“享”:共享,“元”:对象
- 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
- 用唯一标识码判断,如果内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable 存储
- 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
- 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们 使用享元模式需要注意的地方
- 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制(内部状态)
- 享元模式经典的应用场景是需要缓存池的场景,比如String常量池、数据库连接池
五、Integer 源码探索
//如果 Integer.valueOf(x) x 在 -128 ~ 127 之间,就使用享元模式返回
Integer x = Integer.valueOf(127); //得到x的实例,类型Integer
Integer y = new Integer(127); //得到y的实例,类型Integer
Integer z = Integer.valueOf(127); //...
Integer w = new Integer(127); //...
System.out.println(x.equals(y)); //比较大小:true
System.out.println(x == y); // false
System.out.println(x == z); // true
System.out.println(w == x); // false
System.out.println(w == y); // false
//如果 Integer.valueOf(x) x 未在 -128 ~ 127 之间,则使用new创建对象
Integer x = Integer.valueOf(200);
Integer y = Integer.valueOf(200);
System.out.println(x == y); // false
valueOf源码:
本方法总是将 -128 ~ 127 的数值进行缓存,如果是其他值则不进行此操作。
Cache缓存池(享元池):
按范围(-128 ~ 127,即low ~ high)存储缓存数值
数组大小:
小结:
- 在 valueOf 方法中,先判断数值是否在 Integer 的cache中:如果不在就创建新的Integer对象;否则,就直接从缓存池中返回
- valueOf 方法,部分应用享元模式
img-GHMdja3v-1635853614037)]
Cache缓存池(享元池):
按范围(-128 ~ 127,即low ~ high)存储缓存数值
[外链图片转存中…(img-B3lniWg6-1635853614039)]
数组大小:
[外链图片转存中…(img-I2hBl0qH-1635853614040)]
小结:
- 在 valueOf 方法中,先判断数值是否在 Integer 的cache中:如果不在就创建新的Integer对象;否则,就直接从缓存池中返回
- valueOf 方法,部分应用享元模式