设计模式-亨元模式

37 阅读4分钟

1. 亨元模式

1.简介

亨元模式是一种结构型设计模式,它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能在有限的内存容量中载入更多对象。

当你的程序中存在大量相似对象,而每个对象之间只是根据不同的使用场景有些许变化时,你可以使用这个模式来减少内存容量的消耗。

比如说我们要渲染一片森林,里面有1百万棵树,每棵树都由包含一些状态的对象来表示(坐标、颜色、纹理)。尽管程序能够完成其主要工作,但很显然它会消耗大量内存。

但是其实很多树对象包含重复数据(名称、颜色、纹理等)。因此我们可以使用亨元模式来将这些数值存储在单独的亨元对象中,使用一组特殊的数值来引用其中一个亨元对象。

调用者不会知道你其中的逻辑,因为你充用对象的复杂逻辑会隐藏在亨元工厂中。

2.UML图

亨元模式uml.png

  1. 亨元模式只是一种优化,在应用该模式之前, 你要确定程序中存在与大量类似对象同时占用内存相关的内存消耗问题, 并且确保该问题无法使用其他更好的方式来解决。
  2. 享元 (Flyweight) 类包含原始对象中部分能在多个对象中共享的状态。 同一享元对象可在许多不同情景中使用。 享元中存储的状态被称为 “内在状态”。 传递给享元方法的状态被称为 “外在状态”。
  3. 情景 (Context) 类包含原始对象中各不相同的外在状态。 情景与享元对象组合在一起就能表示原始对象的全部状态。
  4. 负责计算或存储享元的外在状态。 在客户端看来, 享元是一种可在运行时进行配置的模板对象, 具体的配置方式为向其方法中传入一些情景数据参数。
  5. 享元工厂 (Flyweight Factory) 会对已有享元的缓存池进行管理。 有了工厂后, 客户端就无需直接创建享元, 它们只需调用工厂并向其传递目标享元的一些内在状态即可。 工厂会根据参数在之前已创建的享元中进行查找, 如果找到满足条件的享元就将其返回; 如果没有找到就根据参数新建享元。

3.示例

假如我们现在要做一个五子棋的简单应用,在棋子的这一部分需要大量的棋子对象,但它们其实除了颜色有黑白之分,摆放的位置不同之外其他基本都一样,符合亨元模式的概念。

首先我们定一个一个共享对象通用的接口:

package com.gs.designmodel.hengyuan.piece;

/**
 * @author: Gaos
 * @Date: 2023-07-28 09:48
 *
 *  棋子对象接口
 **/
public interface Chess {
    // 绘制棋子
    void draw(int x, int y);
}

我们实现需要共享的对象类:

我们把棋子对象分为黑白两类,所以我们需要将颜色设计为对象的内部状态来共享,就需要两个对象类。如果吧颜色作为像坐标一样的外部状态,那么就只需要一个棋子类了。

黑子:

package com.gs.designmodel.hengyuan.piece;


import java.awt.*;

/**
 * @author: Gaos
 * @Date: 2023-07-28 09:49
 *
 * 黑棋
 **/
public class BlackChess implements Chess{

    private final Color color = Color.BLACK;

    private final String sharp = "圆形";

    public Color getColor() {
        return color;
    }

    @Override
    public void draw(int x, int y) {
        System.out.println("棋子形状:" + sharp);
        System.out.println("棋子颜色:" + color.toString());
        System.out.println("x轴坐标:" + x);
        System.out.println("y轴坐标:" + y);

    }
}

白子:

package com.gs.designmodel.hengyuan.piece;

import java.awt.*;

/**
 * @author: Gaos
 * @Date: 2023-07-28 10:19
 **/
public class WhiteChess implements Chess{

    private final Color color = Color.WHITE;

    private final String sharp = "圆形";

    public Color getColor() {
        return color;
    }

    @Override
    public void draw(int x, int y) {
        System.out.println("棋子形状:" + sharp);
        System.out.println("棋子颜色:" + color.toString());
        System.out.println("x轴坐标:" + x);
        System.out.println("y轴坐标:" + y);
    }
}

共享对象工厂:

负责提供共享对象,客户端需要通过此工厂来获取棋子对象。使用 map来存储共享对象。

package com.gs.designmodel.hengyuan.piece;

import java.awt.*;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: Gaos
 * @Date: 2023-07-28 10:29
 **/
public class ChessFactory {

    private static final Map<Color, Chess> chessMap = new HashMap<>();

    public static Chess getChess(Color color) {
        Chess chess = chessMap.get(color);
        if(chess == null) {
            chess = color == Color.WHITE ? new WhiteChess() : new BlackChess();
            chessMap.put(color, chess);
        }
        return chess;
    }
}

测试

package com.gs.designmodel.hengyuan.piece;

import java.awt.*;

/**
 * @author: Gaos
 * @Date: 2023-07-28 10:32
 **/
public class Client {
    public static void main(String[] args) {
        // 黑子说话
        Chess blackChess1 = ChessFactory.getChess(Color.BLACK);
        blackChess1.draw(2, 5);

        // 白子说话
        Chess whiteChess1 = ChessFactory.getChess(Color.WHITE);
        whiteChess1.draw(3, 5);

        // 黑子又说话
        Chess blackChess2 = ChessFactory.getChess(Color.BLACK);
        blackChess2.draw(2, 5);

        System.out.println("黑子1:" + blackChess1.hashCode());
        System.out.println("黑子2:" + blackChess2.hashCode());
        System.out.println("白子1" + whiteChess1.hashCode());
    }
}

结果:

棋子形状:圆形
棋子颜色:java.awt.Color[r=0,g=0,b=0]
x轴坐标:2
y轴坐标:5
棋子形状:圆形
棋子颜色:java.awt.Color[r=255,g=255,b=255]
x轴坐标:3
y轴坐标:5
棋子形状:圆形
棋子颜色:java.awt.Color[r=0,g=0,b=0]
x轴坐标:8
y轴坐标:6
黑子1:1558600329
黑子2:1558600329
白子1636718812

我们可以看到两个黑棋是同一个对象,而白棋是另外一个对象。所以不论棋盘上有多少颗棋子,程序中最多保持两个棋子对象。

4.总结

使用亨元模式你需要区分出内部状态和外部状态,共享对象支持有内部状态,内部状态不可以从客户端设置,外部状态必须从客户端设置。例如上面代码示例中颜色就是内部状态,形状、坐标就是外部状态。

如果程序中有很多相似对象,你可以节省大量的内存。但是你可能要牺牲执行速度来换取内存,因为每次调用亨元方法的时候都需要重新计算部分情景数据。

相关参考文章:

blog.csdn.net/ShuSheng000…

refactoringguru.cn/design-patt…