1.1 使用享元模式有什么好处?
享元模式是对象池的一种实现,主要目的是用来尽可能减少内存使用量。适合于存在大量重复对象的场景,来缓存可共享的对象,达到对象共享,避免创建过多对象的效果,这样可以提升性能。
1.2 关于享元对象?
享元对象中的部分状态可以共享,可以共享的状态为内部状态(内蕴状态),内部状态不会随环境变化,不可共享的状态是外部状态(外蕴状态),外部状态随外部环境变化。
1.3 享元模式的核心—-如何缓存?
在享元模式中会建立一个对象容器,可以是 一个Map,它的键是享元对象的内部状态,它的值就是享元对象本身。客户端程序通过这个内部状态从享元工厂中获取享元对象,如果有缓存则使用缓存对象,如果没有则创建一个享元对象并且存入容器,这样一来就避免了创建多个对象的问题。
享元模式分为单纯享元模式和复合享元模式两种,单纯享元模式的UML类图如图:
角色介绍:
(1)Flyweight: 抽象对象基类或者接口,为具体享元角色规定了必须实现的方法,外在状态就是以参数的形式通过此方法传入。
(2)ConcreteFlyweight: 具体享元角色,实现抽象角色规定的方法,并负责内部状态提供存储空间。
(3)FlyweightFactory:享元工厂角色。负责管理享元对象池和创建享元对象,是实现对象缓存的核心。
(4)Client:客户端角色。维护享元对象的引用和调用对象相应的方法。
复合享元模式的UML类图如图:
其中多了一个角色是复合享元角色ConcreteConpositeFlyweight: 它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。
示例背景:
我们都在某06网站买过火车票,在购票时,当我们设置好出发地和目的地之后,每次请求某06都会返回一个查询的车票结果,假设今年十一你要和你的女朋友从北京去厦门,而北京到厦门就一列火车Z307次。那么十一旅游高峰,很可能有数以万计的人都在不间断的请求从北京到厦门的Z307次车票数据,如果每次都创建一个查询的车票结果,那么必然会大量重复创建对象,销毁,使得GC任务很重。那么使用享元模式处理,创建一个享元对象Ticket,出发地和目的地是享元对象的内部状态,车票的种类(硬座,硬卧,软卧)为享元对象的外部状态,在用户查询北京到厦门的火车时,优先使用缓存,如果没有缓存则重新创建一个享元对象,并存放到Map容器中。
首先创建一个Ticket接口,该接口定义了展示车票信息的函数,这相当于类图中的Flyweight。
public interface Ticket {
public void showTicketInfo(String siteType);//根据座位的类型展示车票结果信息
}
它的具体实现类是火车票TrainTicket,相当于UML类图中ConcreteFlyweight,是具体的享元角色。
/** * Created by Administrator on 2016/4/14. * 写一个Ticket的具体实现类TrainTicket; */
public class TrainTicket implements Ticket {
public String from; //始发地
public String to;//目的地
public String siteType;//座位type
public int price;//价格
public TrainTicket(String from, String to) {//构造方法
this.from = from;
this.to = to;
}
public void showTicketInfo(String siteType){
price=new Random().nextInt(365);//随机价格
Log.i("TAG","购买 从"+from+"到"+to+"的"+siteType+"的火车票"+",价格:"+price);
}
}
真正实现缓存的是火车票工厂类TicketFactory,里面使用一个ConcurrentHashMap
/**真是实现对象缓存的类*/
public class TicketFactory {
static Map<String,Ticket> stringTicketMap=new ConcurrentHashMap<>();
public static Ticket getTicket(String from,String to){
String key=from+"-"+to;//使用出发地和目的地作为Map的key
if(stringTicketMap.containsKey(key)){
Log.i("TAG","使用缓存==> "+key);
return stringTicketMap.get(key);//获取以出发地和目的地为key的对象
}else {
Log.i("TAG","创建对象==> "+key);
Ticket ticket=new TrainTicket(from, to);//创建对象
stringTicketMap.put(key,ticket);
return ticket;
}
}
}
我们在TicketFactory中添加了一个Map容器,Map容器我们使用的是ConcurrentHashMap,ConcurrentHashMap是一个线程安全的Map,里面引入了“分段锁”,相当于把一个大的Map拆分成N个小的线程安全的HashTable。上述代码中,用户请求的时候,先通过Key判断缓存是否已经有此对象,如果有则返回一个缓存对象;如果没有则先创建一个对象,并存放在Map中去。
客户端实现代码如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Ticket ticket01=TicketFactory.getTicket("北京","厦门");//每一个Ticket表示一个用户的请求
ticket01.showTicketInfo("卧铺");
Ticket ticket02=TicketFactory.getTicket("北京","厦门");
ticket02.showTicketInfo("硬座");
Ticket ticket03=TicketFactory.getTicket("北京","厦门");
ticket03.showTicketInfo("软卧");
}
}
看到Logcat打印的信息如下:
04-14 07:27:06.114 10763-10763/com.troy.flyweightpattern I/TAG: 创建对象==> 北京-厦门
04-14 07:27:06.114 10763-10763/com.troy.flyweightpattern I/TAG: 购买 从北京到厦门的卧铺的火车票,价格:56
04-14 07:27:06.125 10763-10763/com.troy.flyweightpattern I/TAG: 使用缓存==> 北京-厦门
04-14 07:27:06.125 10763-10763/com.troy.flyweightpattern I/TAG: 购买 从北京到厦门的硬座的火车票,价格:33
04-14 07:27:06.125 10763-10763/com.troy.flyweightpattern I/TAG: 使用缓存==> 北京-厦门
04-14 07:27:06.125 10763-10763/com.troy.flyweightpattern I/TAG: 购买 从北京到厦门的软卧的火车票,价格:252
从运行结果可以看出,只有第一次查询车票时创建了一次对象,后续的查询都使用的是Map容器中缓存的对象,其实就是相当于只有一个对象,避免了对象的重复创建和回收。在这个DEMO中,享元对象的内部状态就是出发地和目的地,内部状态不会发生变化;外部状态就是座位类型和价格,价格会随着座位类型的变化而变化。其实在JDK中String就是一种享元设计模式,String对象在第一次被定义后是缓存在常量池中的,当其他地方要用到的时候直接使用的是缓存,而不会重复创建。
以上就是对享元设计模式的理解,希望对大家的学习有所帮助,然后感受最深的一点就是设计模式在我们生活中无处不在,这个会后续和大家一起分享。