享元模式:减少内存占用的诀窍

一,概要

享元模式(Flyweight Pattern)是一种结构型设计模式,它主要通过共享对象来降低系统中对象的数量,从而减少内存占用和提高程序性能。这听起来有点像单例模式,但它们在实现和用途上有很大的区别。享元模式的核心是把一个对象的状态分成内部状态和外部状态,内部状态即是不变的,外部状态是变化的;然后通过共享不变的部分,达到减少对象数量并节约内存的目的。享元模式的本质是缓存共享对象,降低内存消耗。

  • 内部状态:内部状态是对象可共享的部分,它存储在享元对象内部,并且不随外部环境的变化而变化。内部状态可以被多个具体享元对象共享。
  • 外部状态:外部状态是对象的上下文相关部分,它在使用时动态传入享元对象,并且随外部环境的变化而变化。外部状态不可共享,每个具体享元对象都需要接收外部状态作为参数来完成其操作。

享元模式三大角色

  • 享元接口:定义了享元对象的公共方法,这些方法可以操作享元对象的外部状态,外部状态一般作为方法参数传入。

  • 具体享元类:实现享元接口,完成具体的对象操作。内部状态作为成员属性存在,一旦初始化完成就不再改变,不对外提供setter方法。

  • 享元工厂:负责创建和管理享元对象。当客户端请求一个享元对象时,享元工厂会检查是否有已经创建的享元对象,如果有,则直接返回;如果没有,则创建一个新的享元对象并加入到享元池中。

优点

  1. 减少内存占用:相同对象只要保存一份,大大降低了系统中对象的数量。
  2. 提高性能:享元模式减少了对象的创建和销毁,降低了垃圾回收的开销,从而提高了程序性能。
  3. 提高可扩展性:通过外部状态的引入,享元模式可以灵活地处理不同的上下文,使得系统更具可扩展性。

缺点

  1. 复杂度增加:享元模式需要将对象的状态进行拆分,引入了内部状态和外部状态的管理,增加了系统的复杂性。

  2. 线程安全问题:由于享元对象是共享的,因此在多线程环境下,对享元对象的操作需要考虑线程安全。

适用场景

  • 系统中存在大量细粒度的对象,且这些对象的状态可以分为内部状态和外部状态时,可以考虑使用享元模式。
  • 对象的大部分状态可以共享,而一小部分状态需要外部环境来改变时,可以使用享元模式。
  • 需要缓存对象以提高系统性能,并且可以接受一定的对象复用时,可以使用享元模式。
  • 需要对对象进行池化管理,以便于统一控制和管理对象的创建、销毁和状态维护时,可以考虑使用享元模式。

二,实现案例

假设我们需要在一个坐标图纸上,绘制100个固定半径的圆,圆分为三种颜色。常规方法,我们定义一个圆,里面包含半径,颜色,横坐标,纵坐标四个属性,再定义一个绘制的方法draw(),然后我们创建100个对象,调用draw()方法就可以实现。但是如果我们用享元模式实现,仅需创建3个对象即可,其关键就是将颜色和半径作为内部状态共享,将坐标作为外部状态分离出来。

步骤1:创建享元接口Shape

public interface Shape {
    //x,y表示坐标
    void draw(int x,int y);
}

步骤2:创建具体享元类Circle

public class Circle implements Shape{
    private String color;
    private int radius;

    public Circle(String color){
        this.color = color;
        this.radius = 10;
    }

    @Override
    public void draw(int x,int y) {
        System.out.println("--在坐标("+x+","+y+")处画圆: [颜色 : " + color
                +", 半径 :" + radius+"]");
    }
}

步骤3:创建享元工厂ShapeFactory

public class ShapeFactory {
    private static final HashMap<String, Shape> circleMap = new HashMap<>();

    public static Shape getCircle(String color) {
        Circle circle = (Circle)circleMap.get(color);
        System.out.println("从缓存中获取"+color+"色的圆");

        if(circle == null) {
            circle = new Circle(color);
            circleMap.put(color, circle);
            System.out.println("缓存中不存在,先创建"+color+"色的圆,并放入缓存中");
        }
        return circle;
    }
}

步骤4:客户端测试

public class Client {
    private static final String colors[] = { "Red", "Green", "Blue" };

    public static void main(String[] args) {
        for(int i=0; i < 10; ++i) {
            Circle circle = (Circle)ShapeFactory.getCircle(getRandomColor());
            circle.draw(getRandomX(),getRandomY());
        }
    }

    private static String getRandomColor() {
        return colors[(int)(Math.random()*colors.length)];
    }
    private static int getRandomX() {
        return (int)(Math.random()*100 );
    }
    private static int getRandomY() {
        return (int)(Math.random()*100);
    }
}

测试结果

image-20230608112221138

三,总结

享元模式是一种非常实用的设计模式,它的思想很简单,就是把一些可以共享的对象只创建一份,放入到缓存池中,供业务方引用。这样做可以大大减少对象的创建开销,减少内存中相似或相同对象的数量,减少内存占用。在java源码中也有很多享元模式的思想体现,如String的字符串常量池,Integer包装类中的IntegerCach,以及各种连接池,线程池等技术。我们在日常开发过程中,可以结合上下文场景,灵活运用享元模式,但需要注意线程安全性和共享池的管理。

最后,希望这篇文章能对大家有所帮助,如果你喜欢这篇文章,不妨点个赞和分享给你的朋友们。欢迎大家在评论区留言交流,我们下次再见!👋
在这里插入图片描述

  • 41
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 39
    评论
评论 39
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值