您的位置:首页 > 移动开发 > Android开发

设计模式-享元模式(Flyweight)的分析说明和Android中的关键应用

2016-05-15 18:13 1276 查看

介绍

写博客总是需要动力和动机的,最近在看Android线程和进程的分析,其实就是Handler这个Android开发中最重要的东西。说来惭愧现在也只是会用的程度。本着要进步的心态我需要在这块下功夫研究。代码要一行一行的写,书要一本一本的看。

相信大家都写过这样的代码:

Message message= handler.obtainMessage();
message.what=0x1;
message.arg1=MSG_ARG;
message.obj=mData;
handler.sendMessage(message);


这是Google官方推荐的Message使用代码,同样的其实我们还可以new出Message实例来发送消息,但是为什么要用obtainMessage呢?这其实就是享元模式在Android的应用,这就是我这篇博客重点分析的内容。

为什么要用享元模式

我们在开发中大量的使用Handler来处理线程间通信回调(其实用RxJava就不怎么需要),在子线程中刷新UI界面。同样的在Android源码中也大量使用了Handler。这就需要构造大量的Message对象类发送消息,每次构造放入数据,发送到handler处理,然后弃用等待虚拟机GC回收。这明显是非常烂的代码,Google显然不会这么做,我们的代码也不能这么写。

这就需要使用到享元模式,先看它的定义:

享元模式的定义:运用共享技术有效的支持大量细粒度的对象。

回到刚才分析使用场景,我们为什么要每次构造Message对象然后使用完之后弃用,为什么不重用它缓存起来,等待下次的使用,从缓存中取出对象重新赋值再次发送。

这个问题刚好就是
为什么要用obtainMessage呢?
的答案。

享元模式解决思路

仔细分析Message和Handler的关系,和它们的逻辑。有时候我们只发送非常简单的Message对象
sendEmptyMessage(int what)
,就一个int标志位。如果这都需要新建对象的话就会产生大量的细粒度对象,并且存在大量重复的数据。如果能够有效的减少对象数量,减少重复数据,就可以节省很多内存开销。

享元模式的基本思路:

缓存这些包含着重复数据的对象,让这些对象只出现一次,每次操作都从缓存中取数据,避免出现大量对象。

下面详细说明享元模式的结构逻辑和举例应用

享元模式结构说明

用Android源码来说明享元模式有点困难,源码包含了多线程的处理和变形比较复杂,不易理解。

而其实享元模式本质是比较简单的,用UML图和模板代码就能很好的说明它的结构和逻辑。

UML图描述



Flyweight:享元接口,通过这个接口Flyweight可以接受并作用于外部状态。通过这个接口传入外部的状态,在方法中处理可能会使用到的外部数据。

ConcreteFlyweight:具体的享元实现对象,必须是共享的,需要封装Flyweight的内部状态。

UnsharedConcreteFlyweight:非共享的享元对象,并不是所有的Flyweight实现对象都需要共享,非共享的对象通常是对共享享元对象的组合对象。

FlyweightFactory:享元工厂,主要用来创建和管理共享的享元对象,并提供外部访问共享享元的接口。

代码描述

享元接口:

/**
* Created by LiCola on  2016/05/15  16:39
* 享元接口 通过这个接口享元可以接受并作用于外部状态
*/
public interface Flyweight {
/**
* 实例操作 传入外部状态
* @param extrinsicState 外部状态值
*/
void operation(String extrinsicState);
}


具体的享元对象实现:

/**
* Created by LiCola on  2016/05/15  16:40
* 可共享的 享元对象
*/
public class ConcreteFlyweight implements Flyweight {

/**
* 描述内部状态 的变量
*/
private String intrinsicStatic;

/**
* 构造方法 传入享元对象的内部状态数据
* @param intrinsicStatic 内部状态数据
*/
public ConcreteFlyweight(String intrinsicStatic) {
this.intrinsicStatic = intrinsicStatic;
}

@Override
public void operation(String extrinsicState) {
//具体的功能处理 可能会用到享元内、外部的状态
}
}


不需要共享的享元对象:

/**
* Created by LiCola on  2016/05/15  16:40
* 不可共享的 享元对象
* 通常是将被共享的享元对象作为子节点组合出来的对象
*/
public class UnsharedConcreteFlyweight implements Flyweight {
/**
* 描述对象状态的变量
*/
private String allState;

@Override
public void operation(String extrinsicState) {
//具体的功能处理
}
}


享元工厂:

/**
* Created by LiCola on  2016/05/15  16:50
* 享元工厂
*/
public class FlyweightFactory {

/**
* 缓存多个Flyweight的集合对象
*/
private Map<String,Flyweight> mMap=new HashMap<>();

/**
* 根据key值 获取缓存中的享元对象
* @param key 获取享元对象的key值
* @return 根据key值 得到的享元对象
*/
public Flyweight getFlyweight(String key){
//先从缓存中查找 是否存在key对应的享元对象
Flyweight flyweight=mMap.get(key);
//如果不在存在 为空
if (flyweight==null){
//创建新的享元对象
flyweight=new ConcreteFlyweight(key);
//放入缓存中
mMap.put(key,flyweight);
}
//返回 对象的享元对象
return flyweight;
}
}


具体的模板代码就这么多,没有实际案例感觉还是说不清楚。下面用个具体的例子说明使用。

享元模式的示例使用

以网络查询火车票某车次票价为例,我们每次通过网络请求向服务器请求某个车次票价数据。假设服务器从数据库查询到数据包装成Ticket对象返回给我们。当高峰时期这是非常可怕的访问量会产生大量的对象。使用享元模式就可以把Ticket对象缓存起来,控制对象的实例数量。

代码

Ticket接口:对应享元接口

public interface Ticket {
public void showTicketInfo(String bunk);
}


TrainTicket火车票对象:对应需要共享的享元对象

public class TrainTicket implements Ticket {
public String from;//始发地
public String to;//目的地
public String bunk;//铺位
public int price;//票价

public TrainTicket(String from, String to) {
this.from = from;
this.to = to;
}

@Override
public void showTicketInfo(String bunk) {
price = new Random().nextInt(700);//模拟查询到的票价
System.out.println("查询 从" + from + " - " + to + " 的 " + bunk + " 火车票 " + " 价格为:" + price);
}
}


TicketFactory工厂类:对应享元工厂,采用饿汉式单例模式

/**
* Created by LiCola on  2016/05/15  17:32
* 工厂类 设计成单例模式
*/
public class TicketFactory {

private static TicketFactory factory = new TicketFactory();

//缓存集合
private Map<String, Ticket> mMapTicket = new HashMap<>();

private TicketFactory() {

}

public static TicketFactory getInstance() {
return factory;
}

/**
* 查询票价 根据始发地和目的地
*
* @param from 查询的始发地
* @param to   查询的目的地
* @return Ticket 返回对象
*/
public Ticket getTicket(String from, String to) {
String key = from + "-" + to;//包装key值
if (mMapTicket.containsKey(key)) {
System.out.println("取出缓存");
return mMapTicket.get(key);
} else {
System.out.println("新建对象");
Ticket ticket = new TrainTicket(from, to);
mMapTicket.put(key, ticket);
return ticket;
}
}
}


这里场景比较简单,就不需要
UnsharedConcreteFlyweight
非共享的享元对象。

TicketQueryTest:Client调用客户端测试查询结果

public class TicketQueryTest {
public static void main(String[] args) {
Ticket ticket01 = TicketFactory.getInstance().getTicket("深圳", "金华");
ticket01.showTicketInfo("一等座");

Ticket ticket02 = TicketFactory.getInstance().getTicket("深圳", "金华");
ticket02.showTicketInfo("二等座");

Ticket ticket03 = TicketFactory.getInstance().getTicket("深圳", "金华");
ticket03.showTicketInfo("商务座");

}
}


输出结果

打印结果模拟查询到的数据:

新建对象--> 深圳-金华
查询 从深圳 - 金华 的 一等座 火车票  价格为:469
取出缓存--> 深圳-金华
查询 从深圳 - 金华 的 二等座 火车票  价格为:612
取出缓存--> 深圳-金华
查询 从深圳 - 金华 的 商务座 火车票  价格为:394


分析

从输出结果上可以看到,只有当第一次查询结果的时候创建了Ticket对象,后两次的查询都是使用了缓存集合中的对象,3次查询只创建了1个对象,享元模式很好的控制缓存的对象数量,避免了频繁的新建对象浪费内存。

提示:在Java源码中String,也采用了类似的消息池缓存,也就是String常量池,基本逻辑就是享元模式的实现。

总结

本文主要从obtainMessage引出了享元模式的概念,并用UML图和享元模板代码说明享元模式。

并用简单的例子说明享元模式的实际应用场景,分析使用后带来的结果。

在下篇博客中会有对享元模式有更深入的分析说明。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: