【设计模式】享元模式(Flyweight Pattern)

享元模式属于结构型模式,主要解决系统需要使用大量相似对象(细粒度对象)而消耗大量内存资源的问题。享元模式运用共享技术有效地支持大量细粒度的对象,其通过提取对象共同的信息抽象出享元对象,实现共享功能,以此进行一个对象的多次复用,本质是缓存共享对象,降低内存消耗。




享元模式的介绍

享元模式(Flyweight Pattern)又被称作轻量级模式蝇量模式,它经典的体现就是对象池。享元模式的根本目的就是"共享单元",让对象能够共享复用,就像共享单车一样,路人甲(客户程序A)骑完路人丙(客户程序B)用,以此减少内存占用

享元模式将对象的信息分为内部状态外部状态:内部状态是存在于享元对象内部的,不会随环境变化而改变的共享信息;外部状态是无法共享的信息,随环境改变而改变,是由调用者传入享元对象中的信息。使用享元模式时需要创建一个工厂(池)对象并持有一个HashMap集合来管理这些享元对象。不同的调用者通过唯一标识(key)获取指定的享元对象来达到共享的功能。

优点

  • 大大减少对象的创建,降低系统的内存,使效率提高

缺点

  • 提高了系统的复杂度,需要分离出内部状态和外部状态
  • 注意划分外部状态和内部状态,否则可能会引起线程安全问题

应用场景

  • 当系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源时
  • 当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多出需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗
  • 字符串常量池、线程池、缓冲池等池技术
  • 常用于系统底层开发,解决系统的性能问题:如数据库连接池,池中是创建好的连接对象,若池中有符合需求的对象时直接用,避免重新创建;若池中没有符合需求的,则创建一个
  • Integer类中,如果目标值在-128~127之间,则直接从缓存中取值,因为在-128~127之间的数据在int范围内是使用最频繁的,为了节省频繁创建对象带来的内存损耗,这就使用了享元对象



享元模式的使用

举例:现在我要在屏幕上画圆,圆有红、黄、蓝、绿、黑五种颜色,大概要画三十个,那就要建三十个对象,但此时可以使用享元模式设计程序结构,我们将圆的颜色抽取为内部属性,而坐标则是外部属性,由客户程序决定,随后通过图形工厂管理享元对象,相同颜色的圆可以进行复用。


类图

image-20221206201439351

四种角色

  • 抽象享元(Flyweight)角色:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。

  • 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。

  • 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。

  • 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

此类图中存在一个线程安全问题,也就是半径属性,此属性需要通过客户程序给出,但又属于享元对象的内部属性,当在多线程下,一个线程修改了半径属性,其他线程中的享元对象的该属性也会改变。这也就是上文所说的注意划分外部状态和内部状态,否则可能会引起线程安全问题。解决方法:可以将半径属性去除,在绘画方法中添加面积参数,这样就得到了外部属性面积,而半径可以通过面积与π得出。


实现方法

第一步,编写非享元

坐标类
package 设计模式.结构型模式.享元模式;

/**
 * 非享元角色,它以参数的形式注入具体享元的相关方法中
 */
public class 坐标类 {
    private Double 坐标x, 坐标y;

    public 坐标类(Double 坐标x, Double 坐标y) {
        this.坐标x = 坐标x;
        this.坐标y = 坐标y;
    }

    public Double 获取坐标x() {
        return 坐标x;
    }

    public Double 获取坐标y() {
        return 坐标y;
    }
}

第二步,编写抽象享元

图形类
package 设计模式.结构型模式.享元模式;

/**
 * 抽象享元角色,是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入
 */
public interface 图形类 {
    void 绘画(坐标类 坐标);
}

第三步,编写具体享元

圆形类
package 设计模式.结构型模式.享元模式;

/**
 * 具体享元角色,实现抽象享元角色中所规定的接口
 */
public class 圆形类 implements 图形类{

    private String 颜色;
    private Double 半径; // 此内部属性在多线程中会引起线程安全问题

    public 圆形类(String 颜色) {
        this.颜色 = 颜色;
    }

    /*
        此属性需要通过客户程序给出,但又属于享元对象的内部属性,当在多线程下,一个线程修改了`半径`属性,其他线程中的享元对象的该属性也会改变
        解决办法:可以将"半径"属性去除,在绘画方法中添加"面积"参数,这样就得到了外部属性"面积",而半径可以通过面积与π得出
     */
    public void 设置半径(Double 半径) {
        this.半径 = 半径;;
    }

    @Override
    public void 绘画(坐标类 坐标) {
        // .2f 保留小数点后两位
        System.out.printf("【%s】圆形:坐标(%.2f,%.2f) 半径%.2f \n",颜色,坐标.获取坐标x(),坐标.获取坐标y(),半径);
    }
}

第四步,编写享元工厂

图形工厂类
package 设计模式.结构型模式.享元模式;


import java.util.HashMap;

public class 图形工厂类 {

    private static HashMap<String, 图形类> 图形集合 = new HashMap<>() ;

    private 图形工厂类(){}

    public static 图形类 获取图形(String 颜色){
        图形类 图形 = 图形集合.get(颜色);
        // 如果图形集合中没有该共享对象的原型,则创建一个
        if (图形 == null){
            图形 = new 圆形类(颜色);
            图形集合.put(颜色, 图形);
        }
        return 图形;
    }
}

第五步,编写客户程序测试

客户程序类
package 设计模式.结构型模式.享元模式;

public class 客户程序类 {

    private static final String[] 颜色列表 = new String[]{"红","黄","蓝","绿","黑"};

    public static void main(String[] args) {
		// 创建30
        for (int i = 0; i < 30; i++) {
            坐标类 随机坐标 = new 坐标类(Math.random()*100, Math.random() * 100);
            圆形类 圆形 = (圆形类) 图形工厂类.获取图形(获取随机颜色());
            圆形.设置半径(Math.random()*100);
            圆形.绘画(随机坐标);
        }
    }

    private static String 获取随机颜色(){
        return 颜色列表[(int) (Math.random()*颜色列表.length)];
    }

}
测试结果
【绿】圆形:坐标(83.22,92.62) 半径38.38 
【红】圆形:坐标(72.57,95.39) 半径96.26 
【黑】圆形:坐标(18.29,22.11) 半径33.26 
【黑】圆形:坐标(48.28,32.95) 半径9.79 
【黑】圆形:坐标(25.65,91.99) 半径21.51 
【黄】圆形:坐标(92.08,18.76) 半径48.55 
【黑】圆形:坐标(35.10,20.12) 半径0.53 
【黄】圆形:坐标(57.73,99.47) 半径90.03 
【黑】圆形:坐标(3.87,75.13) 半径41.67 
【蓝】圆形:坐标(46.09,78.58) 半径71.46 
【黑】圆形:坐标(73.91,44.40) 半径51.43 
【黄】圆形:坐标(30.00,48.53) 半径8.46 
【黑】圆形:坐标(29.40,99.16) 半径93.00 
【红】圆形:坐标(62.56,35.63) 半径93.06 
【蓝】圆形:坐标(26.88,44.36) 半径62.43 
【黑】圆形:坐标(53.38,58.30) 半径77.71 
【黄】圆形:坐标(95.18,66.50) 半径38.43 
【黄】圆形:坐标(22.78,23.79) 半径76.93 
【黑】圆形:坐标(27.47,17.04) 半径50.00 
【黄】圆形:坐标(68.51,14.11) 半径43.27 
【黑】圆形:坐标(12.65,94.49) 半径31.03 
【红】圆形:坐标(4.87,36.21) 半径99.06 
【绿】圆形:坐标(78.40,12.93) 半径56.09 
【黄】圆形:坐标(58.96,37.03) 半径30.91 
【黑】圆形:坐标(43.07,69.88) 半径76.29 
【黄】圆形:坐标(33.85,8.76) 半径78.20 
【蓝】圆形:坐标(98.77,51.53) 半径99.75 
【绿】圆形:坐标(73.40,34.64) 半径72.56 
【绿】圆形:坐标(72.90,37.99) 半径85.42 
【蓝】圆形:坐标(88.49,50.77) 半径9.74 

Process finished with exit code 0

在这里插入图片描述



本文参考

享元模式 (菜鸟教程)

26 设计模式——享元模式(详解版)(知乎)

享元模式详解 (csdn)

享元模式 (博客园)

五分钟学设计模式.14.享元模式 (哔哩哔哩)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值