设计模式与内存管理:享元模式共享对象、代理模式延迟加载、备忘录模式保存状态

内存管理,说白了就是怎么用好你手头那点内存。我做了十几年项目,见过太多因为内存爆炸导致线上事故的案例。今天聊的三个模式——享元、代理、备忘录,恰好从三个不同角度帮你管好内存。

享元模式:共享对象,减少重复

先说说享元模式。这个模式的核心思想很简单:能共享的就别重复创建

我记得有一次做游戏开发,场景里有成千上万个子弹对象。每个子弹都有自己的位置、速度、颜色。但仔细一想,其实很多子弹的纹理、形状、碰撞逻辑是完全一样的。如果每个子弹都独立保存这些数据,内存很快就爆了。

享元模式的做法是:把对象的状态分成两类——

  • 内部状态:可以共享的,比如纹理、颜色、字体
  • 外部状态:不能共享的,比如位置、速度、方向

内部状态存在享元对象里,外部状态由客户端传入。这样,一千个子弹只需要一个纹理对象。

核心要点:享元模式的关键是区分内部状态和外部状态。内部状态越少,共享效果越好。

来看个代码示例:

// Java 示例:享元模式实现字符缓存
public class CharacterFlyweight {
    private final char symbol;  // 内部状态
    private final String font;  // 内部状态
    
    public CharacterFlyweight(char symbol, String font) {
        this.symbol = symbol;
        this.font = font;
    }
    
    public void display(int x, int y) {  // 外部状态传入
        System.out.println("绘制字符 " + symbol + 
                           " 字体 " + font + 
                           " 位置 (" + x + "," + y + ")");
    }
}

// 享元工厂
public class CharacterFactory {
    private Map<String, CharacterFlyweight> pool = new HashMap<>();
    
    public CharacterFlyweight getCharacter(char symbol, String font) {
        String key = symbol + ":" + font;
        CharacterFlyweight flyweight = pool.get(key);
        if (flyweight == null) {
            flyweight = new CharacterFlyweight(symbol, font);
            pool.put(key, flyweight);
        }
        return flyweight;
    }
}

我的经验:享元模式特别适合用在文本编辑器、游戏粒子系统、数据库连接池这些场景。我曾经在一个报表系统里用享元模式缓存了上万个单元格样式,内存占用从 200MB 降到了 30MB。

代理模式:延迟加载,按需创建

代理模式解决的是另一个问题:别急着创建,等真正需要的时候再说

你想想看,一个大型系统里,有些对象创建成本很高,比如加载一张高清图片、建立数据库连接、初始化一个重量级服务。如果一启动就全部加载,启动时间会变得不可接受。

代理模式的做法是:用一个轻量级的代理对象代替真实对象。客户端操作代理对象,代理对象在真正需要时才去创建真实对象。

我遇到过这样一个场景:一个文档查看器,打开文档时不需要加载所有图片,只需要加载第一屏的图片。剩下的图片等用户滚动到那里再加载。这就是典型的代理模式——虚拟代理。

// C++ 示例:代理模式实现图片延迟加载
class Image {
public:
    virtual void display() = 0;
};

class RealImage : public Image {
private:
    string filename;
    
    void loadFromDisk() {
        cout << "正在从磁盘加载图片: " << filename << endl;
        // 模拟耗时操作
        this_thread::sleep_for(chrono::seconds(2));
    }
    
public:
    RealImage(const string& filename) : filename(filename) {
        loadFromDisk();
    }
    
    void display() override {
        cout << "显示图片: " << filename << endl;
    }
};

class ProxyImage : public Image {
private:
    RealImage* realImage = nullptr;
    string filename;
    
public:
    ProxyImage(const string& filename) : filename(filename) {}
    
    void display() override {
        if (realImage == nullptr) {
            realImage = new RealImage(filename);
        }
        realImage->display();
    }
    
    ~ProxyImage() {
        delete realImage;
    }
};

注意:代理模式不是万能的。如果对象总是会被用到,那延迟加载反而增加了开销。我曾经见过有人对每个对象都套一层代理,结果性能反而下降了。用之前先问问自己:这个对象真的可能用不到吗?

备忘录模式:保存状态,随时回滚

备忘录模式,说白了就是给对象拍快照。你玩游戏时存档、写代码时撤销、做设计时保存历史版本——都是备忘录模式的应用。

这个模式由三部分组成:

  • 发起人:需要保存状态的对象
  • 备忘录:保存状态的对象
  • 管理者:管理备忘录的存储和恢复

我记得在一个编辑器项目里,用户每输入一个字符都要保存一次状态,以便支持撤销功能。如果每次都保存完整状态,内存很快就撑不住了。后来我们只保存了变化的部分——增量备忘录。

// Java 示例:备忘录模式实现文本编辑器撤销
class TextEditor {
    private StringBuilder content = new StringBuilder();
    
    public void write(String text) {
        content.append(text);
    }
    
    public Memento save() {
        return new Memento(content.toString());
    }
    
    public void restore(Memento memento) {
        content = new StringBuilder(memento.getState());
    }
    
    public String getContent() {
        return content.toString();
    }
    
    // 备忘录类
    static class Memento {
        private final String state;
        
        public Memento(String state) {
            this.state = state;
        }
        
        private String getState() {
            return state;
        }
    }
}

// 管理者
class History {
    private Stack<TextEditor.Memento> history = new Stack<>();
    
    public void push(TextEditor.Memento memento) {
        history.push(memento);
    }
    
    public TextEditor.Memento pop() {
        return history.pop();
    }
}

避坑指南:我曾经犯过一个错误——备忘录里保存了对象的引用而不是副本。结果修改了对象后,备忘录里的状态也跟着变了。记住,备忘录一定要保存状态的深拷贝,否则你恢复的永远是当前状态。

三个模式的对比

模式 核心思想 内存管理策略 典型场景
享元模式 共享内部状态 减少对象数量 文本编辑器、游戏粒子
代理模式 延迟对象创建 按需分配内存 图片加载、远程服务
备忘录模式 保存状态快照 管理历史版本 撤销操作、存档系统

这三个模式其实可以组合使用。比如,享元模式共享对象,代理模式延迟加载,备忘录模式保存状态——它们从不同维度帮你管好内存。

总结一下:内存管理不是靠一个模式就能搞定的。享元模式帮你减少重复,代理模式帮你按需创建,备忘录模式帮你管理历史。三个模式配合使用,才能写出既高效又健壮的系统。

设计模式与内存管理知识体系 内存管理 享元模式 代理模式 备忘录模式 共享内部状态 减少对象数量 延迟对象创建 按需分配内存 保存状态快照 管理历史版本 三个模式从不同维度解决内存问题,可组合使用
公众号:蓝海资料掘金营,微信deep3321