享元模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。通过对象的共享减少创建对象的数量、降低内存消耗,从而提高系统资源的利用率。属于结构型模式。
享元模式提出了两个要求,细粒度和共享对象。我们把对象的状态分为两个部分:内部状态和外部状态。内部状态指对象共享出来的信息,存储在享元信息内部,并且是不变的,外部状态变化的。享元模式的本质就是缓存共享对象,降低内存消耗。
享元模式的结构:享元模式的主要角色有3个。
- 抽象享元角色(Flyweight):定义了具体享元类需要实现的公共接口,可以是接口或抽象类。
- 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
- 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
- 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户端请求一个享元对象时,享元工厂检査是否已存在该享元对象,如果存在直接返回该对象,如果不存在就创建享元对象并返回。
享元模式的通用实现:
//抽象享元角色
public interface Flyweight {
void operation(String outState);
}
//具体享元角色
public class ConcreteFlyweight implements Flyweight {
private String key;
public ConcreteFlyweight(String key){
this.key = key;
System.out.println("具体享元对象" + key + "被创建!");
}
@Override
public void operation(String outState) {
System.out.print("具体享元对象" + key + "被调用,");
System.out.println("外部状态是:" + outState);
}
}
//享元工厂
public class FlyweightFactory {
private ConcurrentHashMap<String, Flyweight> flyweightMap = new ConcurrentHashMap<String, Flyweight>();
public Flyweight getFlyweight(String key) {
Flyweight flyweight = flyweightMap.get(key);
if (flyweight == null) {
flyweight = new ConcreteFlyweight(key);
flyweightMap.put(key,flyweight);
} else {
System.out.println("具体享元" + key + "已经存在,被成功获取!");
}
return flyweight;
}
}
//测试类
public class FlyweightTest {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweightA1 = factory.getFlyweight("a");
Flyweight flyweightA2 = factory.getFlyweight("a");
Flyweight flyweightA3 = factory.getFlyweight("a");
flyweightA1.operation("第1次调用a");
flyweightA2.operation("第2次调用a");
flyweightA3.operation("第3次调用a");
}
}
享元模式的结构图:
享元模式的应用实例:租房买房时一般都需要中介带看房源,不同中介之间房源是可以共享的,因此房源就是内部状态,是不会改变的,而带看的中介是外部状态,每次带看可能不是同一个中介。我们就来模拟一下中介带看房源的场景。
//房源接口
public interface IHouse {
void showMsg(String anency);
}
//具体房源类
public class House implements IHouse{
private String address;
public House(String address){
this.address = address;
System.out.println("创建房源:"+address);
}
@Override
public void showMsg(String anency) {
System.out.println("中介"+anency+"带看房源:"+address);
}
}
//房源工厂
public class HouseFactory {
private static ConcurrentHashMap<String,IHouse> houseMap = new ConcurrentHashMap<>();
public static IHouse getHouseMag(String address){
if(houseMap.containsKey(address)){
System.out.println("缓存查看:"+address);
return houseMap.get(address);
}
System.out.println("首次查看,创建房源!");
IHouse house = new House(address);
houseMap.put(address,house);
return house;
}
}
//测试类
public class HouseTest {
public static void main(String[] args) {
IHouse house1 = HouseFactory.getHouseMag("北京天安门");
house1.showMsg("张三");
IHouse house2 = HouseFactory.getHouseMag("北京天安门");
house2.showMsg("李四");
IHouse house3 = HouseFactory.getHouseMag("北京天安门");
house3.showMsg("王五");
}
}
享元模式的优点:减少对象的创建,降低内存中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
享元模式的缺点:
- 关注对象内、外部状态,关注线程安全。
- 增加程序的逻辑复杂性。
享元模式的使用场景:享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。
- 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
- 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
- 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
享元模式在源码中的应用:
- String字符串常量池。
- 当Integer类型的值在-128~127之间时,对象直接从IntegerCache中取。