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

介绍

写博客总是需要动力和动机的,最近在看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-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图和享元模板代码说明享元模式。
  • 并用简单的例子说明享元模式的实际应用场景,分析使用后带来的结果。
  • 在下篇博客中会有对享元模式有更深入的分析说明。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值