技术成神之路:设计模式(十四)享元模式

介绍

享元模式(Flyweight Pattern)是一种结构性设计模式,旨在通过共享对象来有效地支持大量细粒度的对象。

1.定义

享元模式通过将对象状态分为内部状态(可以共享)和外部状态(不可共享),来减少内存使用和提高性能。

2. 主要作用

  • 降低内存消耗
  • 提高性能
  • 共享相似对象

3. 解决的问题

当程序中存在大量相似对象时,使用享元模式可以有效减少内存占用,避免重复对象的创建。

4. 模式原理

包含角色:

  1. Flyweight: 抽象享元类,定义了享元对象的接口。
  2. ConcreteFlyweight: 具体享元类,实现了抽象享元类的接口,负责存储内部状态。
  3. FlyweightFactory: 享元工厂类,用于创建和管理享元对象,确保共享。

UML类图:

示例: 模拟一个图形绘制的场景,其中使用享元模式共享相同的图形对象(如圆):

java 复制代码
// 享元接口
interface Shape {
    void draw(String color);
}

// 具体享元类
class Circle implements Shape {
    private String color;

    public Circle(String color) {
        this.color = color;
        System.out.println("Creating Circle of color: " + color);
    }

    @Override
    public void draw(String color) {
        System.out.println("Drawing Circle of color: " + color);
    }
}

// 享元工厂类
class ShapeFactory {
    private static final Map<String, Shape> circleMap = new HashMap<>();

    public static Shape getCircle(String color) {
        Circle circle = (Circle) circleMap.get(color);
        if (circle == null) {
            circle = new Circle(color);
            circleMap.put(color, circle);
        }
        return circle;
    }
}

// 使用
public class FlyweightPatternDemo {
    public static void main(String[] args) {
        ShapeFactory shapeFactory = new ShapeFactory();

        // 共享相同颜色的圆
        Shape circle1 = shapeFactory.getCircle("Red");
        circle1.draw("Red");

        Shape circle2 = shapeFactory.getCircle("Green");
        circle2.draw("Green");

        Shape circle3 = shapeFactory.getCircle("Red");
        circle3.draw("Red");

        System.out.println("Total Circles created: " + ShapeFactory.circleMap.size());
    }
}

打印输出:

yaml 复制代码
Creating Circle of color: Red
Drawing Circle of color: Red
Creating Circle of color: Green
Drawing Circle of color: Green
Drawing Circle of color: Red
Total Circles created: 2

🆗,从这个简单的示例,你会发现享元模式其实很简单,就是共享对象 复用对象,一般开发中并不常用。

在安卓中 HandlerMessage 就使用了享元模式,因为在安卓中 几乎所有事件驱动都是通过Message来进行的,可以说它无处不在,这时候就可以使用享元模式以优化内存使用和提高性能。

下面就以 Message 为例,从源码角度剖析其实现原理!

Message 的池化机制 在 Message 类中,有一个静态的对象池,用于存放可重用的 Message 实例。

java 复制代码
    public static final Object sPoolSync = new Object();
    private static Message sPool;//这是一个链表的头指针,指向池中可重用的 Message 对象
    private static int sPoolSize = 0;//记录池中当前的对象数量。

    private static final int MAX_POOL_SIZE = 50;//定义池的最大容量。

    private static boolean gCheckRecycle = true;//一个标志位,用于检查回收的消息是否有效。

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    @UnsupportedAppUsage
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

这里所谓的池 并不是一个集合容器 而是 Message 对象通过 next 属性构成一个链表。每个 Message 对象可以指向下一个可用的 Message,实现了简单的对象池。

当对象不再需要时(如在处理完消息后),调用recycle()可以将其放回池中(通过设置 next 指向池头 sPool),这样下次调用 obtain 时就能复用这些对象,而不是每次都新建。

在调用recycle()可以发现 有一个 isInUse()方法判断这个Message是否正在使用,这个其实不用开发者操心的,因为所有的消息都要经过Looper这个 "传送带",内部自动回收 ,翻开Looper的源码文件你会发现在loopOnce方法中最后会调用 msg.recycleUnchecked(),如果你进行了某种自定义操作,导致 Message 未能通过正常的 Handler 流程处理,那么你可能需要手动调用 msg.recycle()

简单概括就是:一条由Message 组成的链表,你想用Message时,就从这个链表上 掐掉 一个Message来使用,这个掐掉 操作就是将m.next = null,当你不再使用时 就将其放回到链表头,操作就是 next = sPoolsPool = this

5. 优缺点

优点:

  1. 节省内存空间。
  2. 提高性能,特别是创建和管理大量对象时。

缺点:

  1. 外部状态管理可能导致系统逻辑混乱。

6. 应用场景

  • 游戏中的角色、场景元素(如树、建筑)等。
  • 文本处理系统中的字符、字体。
  • 大量相似对象需要频繁创建的场景。
  • ...

7. 总结

享元模式通过共享对象来优化内存使用和性能,适合于需要创建大量相似对象的场景,但设计复杂性和状态管理需要谨慎处理。

相关推荐
掐指一算乀缺钱12 分钟前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
晚睡早起₍˄·͈༝·͈˄*₎◞ ̑̑17 分钟前
苍穹外卖学习笔记(七)
java·windows·笔记·学习·mybatis
就这个java爽!23 分钟前
JAVA网络编程【基于TCP和UDP协议】超详细!!!
java·开发语言·网络·tcp/ip·udp·eclipse·idea
一叶飘零_sweeeet27 分钟前
为什么 Feign 要用 HTTP 而不是 RPC?
java·网络协议·http·spring cloud·rpc·feign
懒洋洋大魔王1 小时前
7.Java高级编程 多线程
java·开发语言·jvm
茶馆大橘1 小时前
【黑马点评】已解决java.lang.NullPointerException异常
java·开发语言
星辰@Sea1 小时前
服务注册中心对比及使用场景分析
java·云原生
马剑威(威哥爱编程)1 小时前
除了递归算法,要如何优化实现文件搜索功能
java·开发语言·算法·递归算法·威哥爱编程·memoization
yunhuibin1 小时前
ffmpeg面向对象——参数配置秘密探索及其设计模式
学习·设计模式·ffmpeg
bug菌¹1 小时前
滚雪球学SpringCloud[4.1讲]: Spring Cloud Gateway详解
java·spring cloud·微服务