设计模式之禅23

来源:互联网 发布:淘宝上怎么看淘宝达人 编辑:程序博客网 时间:2024/05/29 17:16

设计模式之禅23

真刀实枪之享元模式

  • 从报考系统的内存溢出说起
    • 内存溢出的两种可能
      1. 内存泄漏:无意识的代码缺陷,导致的内存泄漏,JVM不能获得连续的内存空间
      2. 对象太多:代码写的不严谨,产生了很多的对象,内存被耗尽
  • 熟悉一下报考系统的业务流程
    1. 没有账号先进行注册
    2. 登录
    3. 填写信息
      1. 考试科目
      2. 考试地点
      3. 准考证邮寄地址
      4. 。。。
    4. 确认提交
  • 通过属性业务流程图,可以看出这个过程是非常简单的,那为什么还会导致内存溢出,出现宕机和崩溃?

    • 先看看这个过程的类图
    • 类图有了,因为框架需求,写了个工厂方法模式,下面来实现一下这个类图

      • SignInfo

        package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description 报考信息 */public class SignInfo {    // 报考人员的id    private String id;    // 考试地点    private String location;    // 考试科目    private String subect;    // 邮寄地址    private String postAddress;    public String getId() {        return id;    }    public void setId(String id) {        this.id = id;    }    public String getLocation() {        return location;    }    public void setLocation(String location) {        this.location = location;    }    public String getSubect() {        return subect;    }    public void setSubect(String subect) {        this.subect = subect;    }    public String getPostAddress() {        return postAddress;    }    public void setPostAddress(String postAddress) {        this.postAddress = postAddress;    }}
      • SignInfoFactory

        package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class SignInfoFactory {    // 报名信息的对象工厂--简单演示(实际情况会比这个复杂)    public static SignInfo getSignInfo() {        return new SignInfo();    }}
      • Client

        package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class Client {    public static void main(String[] args) {        // 从工厂中获得一个对象        SignInfo signInfo = SignInfoFactory.getSignInfo();        // 进行其他业务处理    }}
  • 就是这么简单的代码,但是为什么还会产生内存溢出的问题,很标准的工厂方法模式呀!
  • 看下错误分析报告是这么说的吧!

    1. 内存突然从888MB飙升到1.6GB2. 新的对象申请不到内存空间,于是出现了OutOfMemoryError3. 报告列出宕机时刻内存中的对象,其中SignInfo对象就有400MB之多
  • 这个就比较清晰了吧,内存中产生的对象还没有被回收又产生了一批对象,直到将内存全部用完,那该怎么办呢?
    • 共享技术,类似于线程池和数据库连接池,这里咱们用的是对象池(Object Pool)

对象池

  • 一个共享的对象池,需要实现两个功能
    1. 容器定义:容器可以容纳哪些对象
    2. 提供客户端访问的接口:池中有可用的对象时提供给客户端进行使用,否则建立新的对象,并放到对象池中
  • 设计一下咱们的对象池
    1. 相同的属性提取出来
    2. 不同的属性在系统内进行赋值处理
  • 类图设计

    • 增加了一个子类,实现带缓冲池对象的建立,同时在工厂中加入了容器HashMap,保存所有的对象
    • 代码

      • SignInfo4Pool【增加一个key值:一个key值唯一对应一个对象】

        package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class SignInfo4Pool extends SignInfo {    // 定义一个对象的key值    private String key;    // 构造函数获得相同的标志    public SignInfo4Pool(String key) {        super();        this.key = key;    }    public String getKey() {        return key;    }    public void setKey(String key) {        this.key = key;    }}
      • SignInfoFactory

        package com.peng.xy;import java.util.HashMap;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class SignInfoFactory {    // 池容器    private static HashMap<String, SignInfo> pool = new HashMap<String, SignInfo>();    // 从池中获取对象    public static SignInfo getSignInfo(String key) {        // 设置返回对象        SignInfo result = null;        // 池中没有该对象则建立,并放入其中        if (!pool.containsKey(key)) {            // 建立对象,并放入对象池中            System.out.println("建立对象,并放入池中!");            result = new SignInfo4Pool(key);            pool.put(key, result);        } else {            System.out.println("直接从池中获取对象~~");            result = pool.get(key);        }        return result;    }}
      • SignInfo

        package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description 报考信息 */public class SignInfo {    // 报考人员的id    private String id;    // 考试地点    private String location;    // 考试科目    private String subect;    // 邮寄地址    private String postAddress;    public String getId() {        return id;    }    public void setId(String id) {        this.id = id;    }    public String getLocation() {        return location;    }    public void setLocation(String location) {        this.location = location;    }    public String getSubect() {        return subect;    }    public void setSubect(String subect) {        this.subect = subect;    }    public String getPostAddress() {        return postAddress;    }    public void setPostAddress(String postAddress) {        this.postAddress = postAddress;    }}
      • Client

        package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class Client {    public static void main(String[] args) {        String subject = "科目";        // 初始化对象池        for (int j = 0; j < 10; j++) {            String key = subject + j + "考试地点" + j;            SignInfoFactory.getSignInfo(key);        }        for (int i = 1; i <= 9; i += 2) {            SignInfoFactory.getSignInfo("科目" + i + "考试地点" + i);        }    }}
      • 执行结果

        建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!直接从池中获取对象~~直接从池中获取对象~~直接从池中获取对象~~直接从池中获取对象~~直接从池中获取对象~~

享元模式的定义

  • FLYweight Pattern
  • 池技术的重要实现方式
  • Use sharing to support large numbers of finegrained objects officiently.(使用共享对象可有效地支持大量的细粒度的对象)
    • 共享对象
    • 细粒度对象
      • 内部状态:可共享的信息--不会随环境的改变而改变的信息
      • 外部状态:依赖的标记,随环境的变化而变化

享元模式的通用类图

  • 类图中的角色
    • FLYweight:抽象享元角色【产品地抽象类:内部状态和外部状态】
    • ConcreteFLYweight:具体享元角色【共享的享元角色】
    • unharedConcreteFLYweight:具体的享元角色【不共享的享元角色】
    • FLYweightFactory:享元工厂:职责非常简单,就是构造一个池容器,同时从容器中取出对象的方法
  • 享元模式的关键在于共享技术,使得一些细粒度的对象 可以共享

通用代码展示

  • FLYweight

    package xy2;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public abstract class Flyweight {    // 内部状态    private String intrinsic;    // 外部状态    private String extrinsic;    // 要求享元元素角色必须接受外部状态    public Flyweight(String extrinsic) {        super();        this.extrinsic = extrinsic;    }    // 定义业务操作    public abstract void operate();    // 获取内部状态    public String getIntrinsic() {        return intrinsic;    }    // 设置内部状态    public void setIntrinsic(String intrinsic) {        this.intrinsic = intrinsic;    }}
  • ConcreteFlyweight1

    package xy2;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class ConcreteFLYweight1 extends Flyweight {    // 接受外部状态    public ConcreteFLYweight1(String extrinsic) {        super(extrinsic);    }    // 根据外部状态进行逻辑处理    public void operate() {        // 业务逻辑    }}
  • ConcreteFlyweight2

    package xy2;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class ConcreteFLYweight2 extends Flyweight {    // 接受外部状态    public ConcreteFLYweight2(String extrinsic) {        super(extrinsic);    }    // 根据外部状态进行逻辑处理    public void operate() {        // 业务逻辑    }}
  • FlyweightFactory

    package xy2;import java.util.HashMap;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class FLYweightFactory {    // 定义一个池容器    private static HashMap<String, Flyweight> pool = new HashMap<String, Flyweight>();    // 享元工厂    public static Flyweight getFlyweight(String Extrinsic) {        // 需要返回的对象        Flyweight flyweight = null;        // 在池中没有该对象        if (!pool.containsKey(Extrinsic)) {            // 创建对象            flyweight = new ConcreteFLYweight1(Extrinsic);            // 加入池中            pool.put(Extrinsic, flyweight);        } else {            flyweight = pool.get(Extrinsic);        }        return flyweight;    }}

享元模式的应用

  • 享元模式的优点
    1. 简单--池技术
    2. 减少程序创建对象,共享某些数据
    3. 降低内存的占用
    4. 增强程序的性能
  • 享元模式的缺点
    1. 增加了系统的复杂性
    2. 需要分离出内部和外部状态,而且外部状态有固化特性,不应该随着内部状态的改变而改变,否则导致逻辑混乱

享元模式的响应场景

  • 系统中存在大量的相似的对象
  • 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份
  • 需要缓冲池的场景

享元模式的扩展

  • 线程安全的问题

    • 例子

      • SignInfo

        package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description 报考信息 */public class SignInfo {    // 报考人员的id    private String id;    // 考试地点    private String location;    // 考试科目    private String subect;    // 邮寄地址    private String postAddress;    public String getId() {        return id;    }    public void setId(String id) {        this.id = id;    }    public String getLocation() {        return location;    }    public void setLocation(String location) {        this.location = location;    }    public String getSubect() {        return subect;    }    public void setSubect(String subect) {        this.subect = subect;    }    public String getPostAddress() {        return postAddress;    }    public void setPostAddress(String postAddress) {        this.postAddress = postAddress;    }}
      • SignInfoFactory

        package com.peng.xy;import java.util.HashMap;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class SignInfoFactory {    // 池容器    private static HashMap<String, SignInfo> pool = new HashMap<String, SignInfo>();    // 从池中获取对象    public static SignInfo getSignInfo(String key) {        // 设置返回对象        SignInfo result = null;        // 池中没有该对象则建立,并放入其中        if (!pool.containsKey(key)) {            // 建立对象,并放入对象池中            System.out.println("建立对象,并放入池中!");            result = new SignInfo4Pool(key);            pool.put(key, result);        } else {            System.out.println("直接从池中获取对象~~");            result = pool.get(key);        }        return result;    }}
      • MultiThread

        package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class MultiThread extends Thread {    private SignInfo signinfo;    public MultiThread(SignInfo signinfo) {        super();        this.signinfo = signinfo;    }    @Override    public void run() {        for (int i = 0; i < 10; i++) {            i++;        }        if (!signinfo.getId().equals(signinfo.getLocation())) {            System.out.println("编号:" + signinfo.getId());            System.out.println("考试地址" + signinfo.getLocation());            System.out.println("线程不安全!");        }    }}
      • Client

        package com.peng.xy;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class Client {    public static void main(String[] args) {        // 对象池中初始化四个对象        SignInfoFactory.getSignInfo("科目1");        SignInfoFactory.getSignInfo("科目2");        SignInfoFactory.getSignInfo("科目3");        SignInfoFactory.getSignInfo("科目4");        // 获得对象        SignInfo signinfo = SignInfoFactory.getSignInfo("科目2");        // 多线程执行        while (true) {            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            signinfo.setId("aaa");            signinfo.setLocation("aaa");            new MultiThread(signinfo).start();            signinfo.setId("b");            new MultiThread(signinfo).start();            signinfo.setLocation("b");            new MultiThread(signinfo).start();        }    }}
      • 某次的执行结果

        建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!建立对象,并放入池中!直接从池中获取对象~~编号:b考试地址aaa线程不安全!
    • 设置的享元元素数量太少,多个线程可能获得对象之后,去修改其属性值,于是就出现了一些不和谐的数据,只要使用java开发,多线程是不可避免的,那么该怎么避免这个问题呢?
  • 性能平衡

    • 尽量使用java的基本类型来作为外部状态

      • 来看类图
      • 代码

        • ExtrinsicState

          package xy3;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class ExtrinsicState {    // 考试科目    private String subject;    // 考试地点    private String location;    public String getSubject() {        return subject;    }    public void setSubject(String subject) {        this.subject = subject;    }    public String getLocation() {        return location;    }    public void setLocation(String location) {        this.location = location;    }    // 重写equals方法    @Override    public boolean equals(Object obj) {        if (obj instanceof ExtrinsicState) {            ExtrinsicState state = (ExtrinsicState) obj;            return state.getLocation().equals(location)                    && state.getSubject().equals(subject);        }        return false;    }    // 重写hashCode方法    @Override    public int hashCode() {        return subject.hashCode() + location.hashCode();    }}
        • SignInfo

          package xy3;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class SignInfo {    // 报考人员的id    private String id;    // 考试地点    private String location;    // 考试科目    private String subect;    // 邮寄地址    private String postAddress;    // 外部状态    private ExtrinsicState es;    public ExtrinsicState getEs() {        return es;    }    public void setEs(ExtrinsicState es) {        this.es = es;    }    public String getId() {        return id;    }    public void setId(String id) {        this.id = id;    }    public String getLocation() {        return location;    }    public void setLocation(String location) {        this.location = location;    }    public String getSubect() {        return subect;    }    public void setSubect(String subect) {        this.subect = subect;    }    public String getPostAddress() {        return postAddress;    }    public void setPostAddress(String postAddress) {        this.postAddress = postAddress;    }}
        • SignInfoFactory

          package xy3;import java.util.HashMap;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class SignInfoFactory {    // 池容器    private static HashMap<ExtrinsicState, SignInfo> pool = new HashMap<ExtrinsicState, SignInfo>();    // 从池中获取对象    public static SignInfo getSignInfo(ExtrinsicState key) {        // 设置返回对象        SignInfo result = null;        // 池中没有该对象则建立,并放入其中        if (!pool.containsKey(key)) {            // 建立对象,并放入对象池中            System.out.println("建立对象,并放入池中!");            result = new SignInfo();            pool.put(key, result);        } else {            result = pool.get(key);        }        return result;    }}
        • Client

          package xy3;/** * @author kungfu~peng * @data 2017年12月5日 * @description */public class Client {    public static void main(String[] args) {        // 初始化对象池        ExtrinsicState state1 = new ExtrinsicState();        state1.setSubject("科目1");        state1.setLocation("山西");        SignInfoFactory.getSignInfo(state1);        ExtrinsicState state2 = new ExtrinsicState();        state2.setSubject("科目1");        state2.setLocation("山西");        long t1 = System.currentTimeMillis();        for (int i = 0; i < 1000000; i++) {            SignInfoFactory.getSignInfo(state1);        }        long t2 = System.currentTimeMillis();        System.out.println("取对象1000000次的时间:" + (t2 - t1) + "毫秒");    }}
        • 执行结果【由于电脑的配置不同,所产生的时间不一定相同】

          建立对象,并放入池中!取对象1000000次的时间:90毫秒

最佳实践

  • Flyweight:拳击比赛中的特轻量级比赛
  • java API中的享元模式:String中的intern方法【如果是String的对象中有该类型的值,则直接返回对象池中的对象】

  • 对象池与享元
    • 对象池:着重在对象的复用上,池中的每个对象是可以替换的,从同一个池中获取的对象a和对象b是完全一样的,它解决的是复用问题
    • 享元:着重共享,如何建立多个可共享的细粒度对象

声明

  • 摘自秦小波《设计模式之禅》第2版;
  • 仅供学习,严禁商业用途;
  • 代码手写,没有经编译器编译,有个别错误,自行根据上下文改正;
原创粉丝点击