设计模式之享元模式:看19路棋盘如何做到一子千面


~犬📰余~
"我欲贱而贵,愚而智,贫而富,可乎? 曰:其唯学乎"


一、享元模式概述

\quad 在软件设计中,享元模式(Flyweight Pattern)的核心思想是通过共享来有效地支持大量细粒度对象的重用。这里的"享"体现在共享,"元"则体现在这些可以共享的基本元素上。正如共享单车系统一样,享元模式会维护一个对象池,其中存储可以复用的对象,当需要时直接从池中获取,而不是重新创建。

\quad 上图展示了享元模式的基本架构,我们可以看到多个客户端都在复用对象池中的共享对象。这些共享对象具有两种状态:

  • 内部状态:对象可共享的、固定不变的属性,就像自行车的基本构造。
  • 外部状态:对象不可共享的、随环境改变的属性,就像自行车的位置和使用状态。

\quad 这种模式特别适合处理需要创建大量相似对象的场景。通过识别对象的内部状态和外部状态,将可共享的部分集中管理,不仅可以显著减少内存占用,还能提升系统性能。

二、享元模式分类

\quad 在享元模式中,根据对象是否可以共享,我们可以将享元分为两种类型:共享享元和非共享享元,就像上图所展示的那样。

  • 共享享元是享元模式的核心,它代表那些可以被多个环境共享使用的对象。这类对象的特点是它们的内部状态是一致的,不会因为使用环境的改变而改变。就像围棋中的黑白棋子,每个黑棋的颜色和形状都是完全相同的,我们没必要为每个位置都创建新的棋子对象,而是可以共享使用现有的棋子,只需要改变它们的位置信息即可。
  • 非共享享元则是那些不能被共享的对象。这类对象可能具有特定的、不可共享的状态。虽然它们不共享,但仍然可以通过享元工厂来统一管理。比如在文字编辑器中,每个字符的字体样式可能都不相同,这时就需要使用非共享享元来处理这些特殊情况。

\quad 共享享元和非共享享元经常一起使用,它们各自处理不同的业务场景。共享享元主要用于那些需要大量创建相似对象的场景,通过共享来减少内存占用;而非共享享元则用于处理那些虽然结构相似,但状态必须独立的对象。

三、享元模式角色组成


\quad 如上图所示,享元模式主要由四个核心角色组成,它们共同协作来实现对象的高效共享和管理。

  • Flyweight(享元接口)是所有具体享元类的公共接口,它定义了享元对象需要实现的方法。这个接口通常包含一个传入外部状态的操作方法,使享元对象能够根据外部状态改变其行为。
  • ConcreteFlyweight(具体享元类)是实现了Flyweight接口的具体类。它包含内部状态,也就是那些可以共享的、不会随环境改变的信息。例如,在围棋程序中,棋子的颜色就是内部状态。这个类的实例会被多个客户端共享使用。
  • UnsharedConcreteFlyweight(非共享具体享元类)也实现了Flyweight接口,但它的实例不会被共享。这个类包含了不能共享的状态信息。比如在文本编辑器中,虽然字符'A'可以被共享,但如果这个'A'有特殊的样式,就需要使用非共享享元来处理。
  • FlyweightFactory(享元工厂)负责创建和管理享元对象。它通常维护一个享元池(用Map实现),用于存储已创建的享元对象。当客户端请求一个享元对象时,工厂会先检查池中是否存在满足要求的对象,如果存在就直接返回,否则才创建新的对象。这保证了相同内部状态的享元对象只会被创建一次。

四、享元模式案例

\quad 让我们通过实现一个简单的围棋程序来深入理解享元模式。在围棋中,棋子只有黑白两色,但要放置在棋盘的不同位置上。这里棋子的颜色就是内部状态,可以被共享;而位置则是外部状态,需要在使用时指定。

图片
\quad 首先定义棋子的位置类:

java 复制代码
// 棋子位置类-外部状态
public class Position {
    private int x;
    private int y;
    
    public Position(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int getX() { return x; }
    public int getY() { return y; }
}

\quad 接下来定义围棋的棋子共享接口以及实现类:

java 复制代码
// 围棋棋子接口
public interface GoChessPiece {
    void display(Position position);
}
// 具体的围棋棋子类
public class ConcreteChessPiece implements GoChessPiece {
    private String color; // 内部状态
    
    public ConcreteChessPiece(String color) {
        this.color = color;
    }
    
    @Override
    public void display(Position position) {
        System.out.printf("棋子颜色:%s,位置:(%d, %d)%n", 
            color, position.getX(), position.getY());
    }
}

\quad 然后是工厂类:

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class GoChessPieceFactory {
    private static final Map<String, GoChessPiece> pieces = new HashMap<>();
    
    public static GoChessPiece getChessPiece(String color) {
        GoChessPiece piece = pieces.get(color);
        if (piece == null) {
            piece = new ConcreteChessPiece(color);
            pieces.put(color, piece);
        }
        return piece;
    }
}

\quad 使用示例:

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 获取白色棋子并放在(2, 3)位置
        GoChessPiece white1 = GoChessPieceFactory.getChessPiece("白色");
        white1.display(new Position(2, 3));

        // 获取另一个白色棋子放在(3, 6)位置
        GoChessPiece white2 = GoChessPieceFactory.getChessPiece("白色");
        white2.display(new Position(3, 6));

        // 判断是否是同一个对象
        System.out.println("两个白棋是否共享同一个对象:" + (white1 == white2));
    }
}

\quad 测试结果:

\quad 在这个案例中,我们可以看到:

  1. 棋子的颜色(内部状态)被共享,每种颜色的棋子只会创建一个对象

  2. 棋子的位置(外部状态)在使用时由客户端指定

五、享元模式优缺点

优点

\quad 享元模式的核心优势是通过共享对象来减少内存占用,特别适合需要创建大量相似对象的场景。使用享元模式可以集中管理可复用的对象,使得对象的创建和维护更加规范和高效。

缺点

\quad 这种模式增加了系统的复杂度,需要额外的工厂类来管理对象池,同时还需要仔细区分内部状态和外部状态。在对象数量较少的场景下,这种模式带来的收益可能无法抵消其带来的开发成本。

六、享元模式适用场景

\quad 享元模式最适合应用在系统需要创建大量相似对象,且这些对象可以分离出共享部分的场景。典型的应用场景包括:

  • 文字编辑器中的字符渲染:相同的字符可以共享字形信息,只需要改变位置和样式
  • 游戏中的素材管理:相同的游戏素材(如树木、建筑)可以在不同位置重复使用
  • 地图应用中的图标:相同类型的地标可以共享图标资源
  • 网页中的图片缓存:相同的图片可以在多处被重复使用

\quad 当系统中存在大量重复对象,且这些对象的大部分状态都可以外部化时,使用享元模式可以显著降低内存占用并提高性能。

七、总结

\quad 享元模式通过对象共享来提高系统性能,是一种以时间换空间的设计模式。它将对象的状态分为内部状态和外部状态,通过共享内部状态来减少对象创建。在实现时,需要通过享元工厂来统一管理对象池,确保相同内部状态的对象只被创建一次。这种模式特别适合需要大量创建相似对象的场景,但在使用时需要权衡其带来的复杂性和收益。


关注犬余,共同进步
技术从此不孤单

相关推荐
pigzhouyb3 分钟前
指针学习-
java·学习·算法
zz.YE12 分钟前
【SpringMVC】REST 风格
java·后端·spring·restful
web1309332039831 分钟前
[JAVA Web] 02_第二章 HTML&CSS
java·前端·html
两点王爷31 分钟前
Java项目中Oracle数据库开发过程中相关内容
java·sql·oracle
乄bluefox33 分钟前
关于easy-es对时间范围查询遇到的小bug
java·数据库·spring boot·elasticsearch·搜索引擎·bug
白宇横流学长37 分钟前
基于SpringBoot的垃圾分类系统设计与实现【源码+文档+部署讲解】
java·spring boot·后端
hanbarger1 小时前
服务器反应慢,秒杀设计
java·运维·服务器
喵喵队摆大烂1 小时前
springai 简易聊天机器人设计
java·spring boot·ai
123yhy传奇1 小时前
【学习总结|DAY025】JAVA-WEB基础
java·学习·springboot·web
陶然同学1 小时前
【探花交友】day06—即时通信
java·开发语言·springcloud·项目·交友