一个例子读懂享元模式

本文介绍了一种在设计俄罗斯方块游戏时,通过使用享元模式来优化内存的方法。通过共享形状对象而非每个方块都创建新的对象,减小了内存占用,特别是在屏幕尺寸较大时。享元模式适用于对象大量重复且状态可外部化的场景。
摘要由CSDN通过智能技术生成

俄罗斯方块想必大家都不陌生,从小玩到大的,如果让你设计一款俄罗斯方块游戏,那么该如何下手呢?今天咱们只讨论如何创建各种图形,那么开始吧

参考上面的俄罗斯方块类,每个方块有不同的形状shap对象以及不同的坐标station对象。由于方块的形状是有限的,如果每个方块对象都需要一个新的shap对象,如果屏幕足够大,那么内存占用会比较大,造成了资源的浪费。

分析一下,每个方块的形状都是有限的,既然如此,是不是可以考虑共享shap对象呢?而坐标对于每个方块来说都是唯一的,因此无法共享。

import java.util.HashMap;
import java.util.Map;

// 形状类
class Shape {
    private final String shapeName;

    public Shape(String shapeName) {
        this.shapeName = shapeName;
    }

    public String getShapeName() {
        return shapeName;
    }
}

// 坐标类
class Position {
    private final int x;
    private final int y;

    public Position(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }
}

// 俄罗斯方块类
class TetrisBlock {
    private final Shape shape; // 内部状态:形状
    private final Position position; // 外部状态:坐标

    public TetrisBlock(Shape shape, Position position) {
        this.shape = shape;
        this.position = position;
    }

    public void draw() {
        System.out.println("绘制了一个" + shape.getShapeName() + "形状的俄罗斯方块,坐标为:" + position.getX() + "," + position.getY());
    }
}

// 享元工厂类
class TetrisBlockFactory {
    // 缓存形状对象的hashMap
    private static final Map<String, Shape> shapeMap = new HashMap<>();

    public static TetrisBlock getTetrisBlock(String shapeName, int x, int y) {
        // 先查看是否已经存在对应形状的对象,如果存在则直接返回
        Shape shape = shapeMap.get(shapeName);
        if (shape == null) {
            // 如果不存在,则创建新的形状对象,并将其存储在 map 中
            shape = new Shape(shapeName);
            shapeMap.put(shapeName, shape);
            System.out.println("创建了一个" + shapeName + "形状的俄罗斯方块");
        }
        Position position = new Position(x, y);
        return new TetrisBlock(shape, position);
    }
}

// 客户端代码
public class Main {
    public static void main(String[] args) {
        // 获取不同形状的俄罗斯方块对象,并设置不同的坐标位置
        TetrisBlock block1 = TetrisBlockFactory.getTetrisBlock("L形", 10, 10);
        TetrisBlock block2 = TetrisBlockFactory.getTetrisBlock("T形", 20, 20);
        TetrisBlock block3 = TetrisBlockFactory.getTetrisBlock("I形", 30, 30);

        // 再次获取相同形状的俄罗斯方块对象,但坐标位置不同,此时形状对象可以复用
        TetrisBlock block4 = TetrisBlockFactory.getTetrisBlock("L形", 40, 40);
        TetrisBlock block5 = TetrisBlockFactory.getTetrisBlock("T形", 50, 50);

        // 绘制俄罗斯方块
        block1.draw();
        block2.draw();
        block3.draw();
        block4.draw();
        block5.draw();
    }
}

通过上面的例子可以发现,享元模式实际上是缓存了可以重用的部分,在本例中就是Shap对象,这种情况下主要是减少形状对象(Shap)的创建,从而减少了内存的消耗。

鉴别是否可以用享元模式,需要确认可共享的部分是有限的并且大量重复出现的。在本例中形状(Shap)对象就符合这种特征,而位置(Sataion)对象则不符合。同时,如果共享的部分是唯一的,那么直接使用单例就可以,也就没有使用享元的必要了。

其次,共享部分通常被设计为不可变的,假设某个共享对象被多个客户端同时引用,其中一个客户端修改了共享部分的状态,这样会影响到其他客户端引用该共享对象的状态,从而导致不确定的行为。在本例子中,Shap通过构造函数参数初始化其状态一次,它没有向其他对象公开任何 setter 或公共字段。

享元模式的使用场景:

1 大量对象的重复使用:当系统中存在大量相似对象,且这些对象的状态可以被外部化时,可以考虑使用享元模式。通过共享相同的对象实例,可以减少内存的消耗,提高系统的性能。

2 对象的状态可以被分为内部状态和外部状态:享元模式通过将对象的内部状态和外部状态分离,可以在一定程度上减少对象的数量。内部状态是对象可以共享的状态,而外部状态是对象不同的变化部分。通过在享元对象中共享内部状态,并在需要时传递外部状态,可以实现对象的共享。

3 需要缓存的对象:享元模式常用于需要缓存对象的情况,或者对象的创建和销毁代价较高时,例如数据库连接池、线程池等。通过共享已经创建的对象实例,可以避免频繁地创建和销毁对象,从而提高系统的性能和资源利用率。这种方式比直接缓存对象来说能够更加精细和灵活的控制需要缓存的共享部分和可变部分,避免缓存整个对象


参考:Flyweight (refactoring.guru)

设计模式总结文章(一边学习一边更新中)

建造者模式和工厂模式的区别-CSDN博客

代理模式和装饰器模式的区别-CSDN博客

JDK动态代理原理浅析(通俗易懂版)-CSDN博客

一个例子读懂享元模式-CSDN博客

策略模式中的上下文你可能用错了-CSDN博客

策略模式和命令模式的区别-CSDN博客

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值