拥有大量相似对象?享元模式让你通过共享相同的对象来节省内存,不再浪费资源。

想象一下你走进一个巨大的游乐园,每个小摊位旁边都有一个类似的售货员,脸上带着相似的笑容,穿着一样的制服,手里拿着相似的商品。每一个摊位的售货员几乎都一样,只不过他们所站的位置不同,卖的东西也不完全相同。可问题来了:如果每个摊位都需要一个全新的售货员,那得消耗多少资源?如果游乐园想节省成本,那该怎么办呢?

享元模式就像是这个游乐园的管理者,他提出了一个巧妙的解决方案:为所有售货员设定统一的外观和行为,只在需要的地方提供不同的信息或状态。这样,游乐园就可以共享这些售货员对象,不需要为每一个摊位都创建新的对象,而是复用已有的售货员,从而节省内存和资源。

趣味解读:共享售货员!

假设你是游乐园的老板,每个摊位的售货员必须有统一的外形和行为:比如相同的服装、微笑和打招呼的方式。但问题是,摊位太多了,售货员数量也太多,如果每个摊位都单独雇佣一个售货员,你的资源会被大量浪费。

于是,你决定采用享元模式。你只为每种摊位类型创造一个售货员对象,并通过共享这些对象来满足多个摊位的需求。当摊位需要不同的售货员时,你只需要告诉这些售货员具体的任务(比如“卖冰淇淋”或“卖气球”),其他的就交给共享的外观和行为来统一处理。这样,既能满足需求,又能大幅节省资源。

Java代码案例:享元模式 - 字符串池

在很多程序中,字符串是一个常见的例子。如果每个字符串都创建一个新的对象,可能会浪费大量内存,尤其是当字符串内容相同但内存地址不同的情况下。利用享元模式,字符串池就可以让相同的字符串对象共享同一份内存,从而节省空间。

// 1. 享元类 - 字符串享元
class Flyweight {
    private String value;
​
    public Flyweight(String value) {
        this.value = value;
    }
​
    public String getValue() {
        return value;
    }
}
​
// 2. 享元工厂 - 字符串池
class FlyweightFactory {
    private static final Map<String, Flyweight> pool = new HashMap<>();
​
    public static Flyweight getFlyweight(String value) {
        // 如果池中存在该字符串对象,则返回该对象
        if (!pool.containsKey(value)) {
            pool.put(value, new Flyweight(value));
        }
        return pool.get(value);
    }
}
​
// 3. 客户端代码
public class Main {
    public static void main(String[] args) {
        Flyweight flyweight1 = FlyweightFactory.getFlyweight("Hello");
        Flyweight flyweight2 = FlyweightFactory.getFlyweight("Hello");
        Flyweight flyweight3 = FlyweightFactory.getFlyweight("World");
​
        System.out.println(flyweight1.getValue()); // 输出: Hello
        System.out.println(flyweight2.getValue()); // 输出: Hello
        System.out.println(flyweight3.getValue()); // 输出: World
​
        // 验证flyweight1和flyweight2是同一个对象
        if (flyweight1 == flyweight2) {
            System.out.println("flyweight1 和 flyweight2 是同一个对象!");
        }
    }
}

解析:

  • 享元类Flyweight 类保存了享元对象的内部状态(如字符串值)。这些对象在不同的客户端之间共享。

  • 享元工厂FlyweightFactory 类是享元对象的管理者,它维护了一个共享对象池(pool)。如果一个相同的对象已经存在池中,工厂就返回池中的对象,否则会创建一个新的对象并将其加入池中。

  • 客户端代码:在客户端代码中,多个Flyweight对象共享相同的字符串值,并且我们可以验证flyweight1flyweight2指向的是同一个对象。

实际应用场景:

  1. 内存优化:在处理大量重复对象时,享元模式非常有用。例如,某些游戏中可能会有大量相似的对象(比如树木、建筑等),如果每个对象都创建一个新的实例,会消耗大量内存。通过享元模式,共享相同类型的对象,可以大大节省内存。

  2. 图形绘制:在图形绘制软件中,很多相似的图形元素(比如相同颜色、形状的图标或按钮)需要重复使用。通过享元模式,可以避免重复创建相同的图形对象,从而减少内存的占用。

  3. 字符缓存池:例如,Java的字符串池就是一个经典的享元模式实现。Java在内存中缓存了所有的字符串常量,当你使用相同的字符串时,它直接返回已有的实例,而不是重新创建一个新的对象。

  4. 数据库连接池:享元模式也可以应用于数据库连接池的实现。多个客户端在进行数据库操作时,都会使用数据库连接池中的连接对象,而不是每次都重新创建新的连接。

总结:

享元模式通过共享相同的对象,避免了大量相似对象的重复创建,节省了内存资源。在内存受限的系统中,享元模式可以极大地提高系统的性能和资源利用率。它帮助你在保持对象的灵活性和可扩展性的同时,最大化地减少资源浪费。