享元设计模式

2023-05-16

享元模式的结构与实现

享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。

  • 内部状态指对象共享出来的信息,存储在享元信息内部,并且不回随环境的改变而改变;
  • 外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。


比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。

享元模式的本质是缓存共享对象,降低内存消耗。

1. 模式的结构

享元模式的主要角色有如下。

  1. 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
  2. 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
  3. 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
  4. 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。


图 1 是享元模式的结构图,其中:

  • UnsharedConcreteFlyweight 是非享元角色,里面包含了非共享的外部状态信息 info;
  • Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部状态以参数的形式通过该方法传入;
  • ConcreteFlyweight 是具体享元角色,包含了关键字 key,它实现了抽象享元接口;
  • FlyweightFactory 是享元工厂角色,它是关键字 key 来管理具体享元;
  • 客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法。

享元模式的结构图
图1 享元模式的结构图

2. 模式的实现

享元模式的实现代码如下:

public class FlyweightPattern {
    public static void main(String[] args) {
        FlyweightFactory factory = new FlyweightFactory();
        Flyweight f01 = factory.getFlyweight("a");
        Flyweight f02 = factory.getFlyweight("a");
        Flyweight f03 = factory.getFlyweight("a");
        Flyweight f11 = factory.getFlyweight("b");
        Flyweight f12 = factory.getFlyweight("b");
        f01.operation(new UnsharedConcreteFlyweight("第1次调用a。"));
        f02.operation(new UnsharedConcreteFlyweight("第2次调用a。"));
        f03.operation(new UnsharedConcreteFlyweight("第3次调用a。"));
        f11.operation(new UnsharedConcreteFlyweight("第1次调用b。"));
        f12.operation(new UnsharedConcreteFlyweight("第2次调用b。"));
    }
}

//非享元角色
class UnsharedConcreteFlyweight {
    private String info;

    UnsharedConcreteFlyweight(String info) {
        this.info = info;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}

//抽象享元角色
interface Flyweight {
    public void operation(UnsharedConcreteFlyweight state);
}

//具体享元角色
class ConcreteFlyweight implements Flyweight {
    private String key;

    ConcreteFlyweight(String key) {
        this.key = key;
        System.out.println("具体享元" + key + "被创建!");
    }

    public void operation(UnsharedConcreteFlyweight outState) {
        System.out.print("具体享元" + key + "被调用,");
        System.out.println("非享元信息是:" + outState.getInfo());
    }
}

//享元工厂角色
class FlyweightFactory {
    private HashMap<String, Flyweight> flyweights = new HashMap<String, Flyweight>();

    public Flyweight getFlyweight(String key) {
        Flyweight flyweight = (Flyweight) flyweights.get(key);
        if (flyweight != null) {
            System.out.println("具体享元" + key + "已经存在,被成功获取!");
        } else {
            flyweight = new ConcreteFlyweight(key);
            flyweights.put(key, flyweight);
        }
        return flyweight;
    }
}

程序运行结果如下:


具体享元a被创建!
具体享元a已经存在,被成功获取!
具体享元a已经存在,被成功获取!
具体享元b被创建!
具体享元b已经存在,被成功获取!
具体享元a被调用,非享元信息是:第1次调用a。
具体享元a被调用,非享元信息是:第2次调用a。
具体享元a被调用,非享元信息是:第3次调用a。
具体享元b被调用,非享元信息是:第1次调用b。
具体享元b被调用,非享元信息是:第2次调用b。  

享元模式的应用实例

【例1】享元模式在五子棋游戏中的应用。

分析:五子棋同围棋一样,包含多个“黑”或“白”颜色的棋子,所以用享元模式比较好。

本实例中:

  • 棋子(ChessPieces)类是抽象享元角色,它包含了一个落子的 DownPieces(Graphics g,Point pt) 方法;
  • 白子(WhitePieces)和黑子(BlackPieces)类是具体享元角色,它实现了落子方法;
  • Point 是非享元角色,它指定了落子的位置;
  • WeiqiFactory 是享元工厂角色,它通过 ArrayList 来管理棋子,并且提供了获取白子或者黑子的 getChessPieces(String type) 方法;
  • 客户类(Chessboard)利用 Graphics 组件在框架窗体中绘制一个棋盘,并实现 mouseClicked(MouseEvent e) 事件处理方法,该方法根据用户的选择从享元工厂中获取白子或者黑子并落在棋盘上。


图 2 所示是其结构图。
 

五子棋游戏的结构图
图2 五子棋游戏的结构图


程序代码如下:

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;

public class WzqGame {
    public static void main(String[] args) {
        new Chessboard();
    }
}

//棋盘
class Chessboard extends MouseAdapter {
    WeiqiFactory wf;
    JFrame f;
    Graphics g;
    JRadioButton wz;
    JRadioButton bz;
    private final int x = 50;
    private final int y = 50;
    private final int w = 40;    //小方格宽度和高度
    private final int rw = 400;    //棋盘宽度和高度

    Chessboard() {
        wf = new WeiqiFactory();
        f = new JFrame("享元模式在五子棋游戏中的应用");
        f.setBounds(100, 100, 500, 550);
        f.setVisible(true);
        f.setResizable(false);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel SouthJP = new JPanel();
        f.add("South", SouthJP);
        wz = new JRadioButton("白子");
        bz = new JRadioButton("黑子", true);
        ButtonGroup group = new ButtonGroup();
        group.add(wz);
        group.add(bz);
        SouthJP.add(wz);
        SouthJP.add(bz);
        JPanel CenterJP = new JPanel();
        CenterJP.setLayout(null);
        CenterJP.setSize(500, 500);
        CenterJP.addMouseListener(this);
        f.add("Center", CenterJP);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        g = CenterJP.getGraphics();
        g.setColor(Color.BLUE);
        g.drawRect(x, y, rw, rw);
        for (int i = 1; i < 10; i++) {
            //绘制第i条竖直线
            g.drawLine(x + (i * w), y, x + (i * w), y + rw);
            //绘制第i条水平线
            g.drawLine(x, y + (i * w), x + rw, y + (i * w));
        }
    }

    public void mouseClicked(MouseEvent e) {
        Point pt = new Point(e.getX() - 15, e.getY() - 15);
        if (wz.isSelected()) {
            ChessPieces c1 = wf.getChessPieces("w");
            c1.DownPieces(g, pt);
        } else if (bz.isSelected()) {
            ChessPieces c2 = wf.getChessPieces("b");
            c2.DownPieces(g, pt);
        }
    }
}

//抽象享元角色:棋子
interface ChessPieces {
    public void DownPieces(Graphics g, Point pt);    //下子
}

//具体享元角色:白子
class WhitePieces implements ChessPieces {
    public void DownPieces(Graphics g, Point pt) {
        g.setColor(Color.WHITE);
        g.fillOval(pt.x, pt.y, 30, 30);
    }
}

//具体享元角色:黑子
class BlackPieces implements ChessPieces {
    public void DownPieces(Graphics g, Point pt) {
        g.setColor(Color.BLACK);
        g.fillOval(pt.x, pt.y, 30, 30);
    }
}

//享元工厂角色
class WeiqiFactory {
    private ArrayList<ChessPieces> qz;

    public WeiqiFactory() {
        qz = new ArrayList<ChessPieces>();
        ChessPieces w = new WhitePieces();
        qz.add(w);
        ChessPieces b = new BlackPieces();
        qz.add(b);
    }

    public ChessPieces getChessPieces(String type) {
        if (type.equalsIgnoreCase("w")) {
            return (ChessPieces) qz.get(0);
        } else if (type.equalsIgnoreCase("b")) {
            return (ChessPieces) qz.get(1);
        } else {
            return null;
        }
    }
}

 程序运行结果如图 3 所示。

五子棋游戏的运行结果
图3 五子棋游戏的运行结果

享元模式的应用场景

当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多出需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗。

享元模式其实是工厂方法模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存这一功能。

前面分析了享元模式的结构与特点,下面分析它适用的应用场景。享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。

  1. 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
  2. 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
  3. 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

享元设计模式 的相关文章

  • Retrofit2 源码解析

    0 基本使用 1 Retrofit 将我们的 HTTP API 转换成一个 接口形式 所以我们第一步定义一个 interface public interface GitHubService 64 GET 34 user user repo
  • Android Studio插件的源文件位置——mac端

    有些时候安装插件后 xff0c 整个android studio都卡住了 xff0c 无法通过Android Studio gt preferences gt plugins来卸载 xff0c 这时候就需要找到安装位置 xff0c 进行删除
  • H.264编码基础知识详解

    一 编码基础概念 1 为什么要进行视频编码 xff1f 视频是由一帧帧图像组成 xff0c 就如常见的gif图片 xff0c 如果打开一张gif图片 xff0c 可以发现里面是由很多张图片组成 一般视频为了不让观众感觉到卡顿 xff0c 一
  • Android事件分发

    基本知识 什么是触摸事件 触摸事件 xff0c 是Android用来描述你的手对屏幕做的事情的最小单元 关键词有两个 xff1a 手势 xff08 你的手对屏幕做的事情 xff09 最小单元 所谓手势 xff0c 就是比如按下 移动 抬起
  • HashMap这一篇就够了

    介绍下 HashMap 的底层数据结构 现在用的都是 JDK 1 8 xff0c 底层是由 数组 43 链表 43 红黑树 组成 xff0c 如下图 xff0c 而在 JDK 1 8 之前是由 数组 43 链表 组成 为什么要改成 数组 4
  • Activity的结构分析

    1 结构介绍 大伙儿应该都知道 xff0c Activity的结构分为三层 xff0c 分别是 xff1a Activity Window和View xff0c 不同层承担着不同的责任 上面的图简单的描述了Activity整个结构的构建流程
  • LinkedBlockingQueue

    一 类签名 从类名可知 xff0c LinkedBlockingQueue是基于链表实现的阻塞队列 public class LinkedBlockingQueue lt E gt extends AbstractQueue lt E gt
  • UDP Socket

    UDP的Java支持 UDP协议提供的服务不同于TCP协议的端到端服务 xff0c 它是面向非连接的 xff0c 属不可靠协议 xff0c UDP套接字在使用前不需要进行连接 实际上 xff0c UDP协议只实现了两个功能 xff1a 1
  • volatile

    把代码块声明为 synchronized xff0c 有两个重要后果 xff0c 通常是指该代码具有 原子性 xff08 atomicity xff09 和 可见性 xff08 visibility xff09 原子性意味着个时刻 xff0
  • Android 相机预览方向和拍照方向

    我们知道手机 Camera 的图像数据都是来自于摄像头硬件的图像传感器 xff08 Image Sensor xff09 xff0c 这个 Sensor 被固定到手机之后是有一个默认的取景方向的 xff0c 这个方向如下图所示 xff0c
  • Python无参装饰器

    一 什么是装饰器 定义一个函数 xff0c 该函数可为其他函数添加额外的功能 二 何时用装饰器 需要在不修改被装饰对象源代码及其调用方式时 xff0c 为被装饰对象添加额外的功能 三 如何写一个装饰器 现在我们有如下一个函数help xff
  • typedef的用法

    typedef中声明的类型在变量名的位置出现 什么意思呢 xff0c 我们回头来看 我们是怎么声明int类型变量的 xff1f int Typename 像上面这样 xff0c 对不对 xff1f 那么用typedef之后呢 xff1f 把
  • Activity启动流程(一)

    Launcher进程请求AMSAMS发送创建应用进程请求Zygote进程接受请求并孵化应用进程应用进程启动ActivityThread 一 Launcher进程请求AMS 上面我们提到根Activity的启动流程其实就是桌面上点击一个应用图
  • Activity启动流程(二)

    应用进程绑定到AMSAMS发送启动Activity的请求ActivityThread的Handler处理启动Activity的请求 一 应用进程绑定到AMS 1 时序图 2 详细过程 在前面一篇我们知道当Zygote进程孵化出应用进程后会执
  • AudioRecord

    数字音频 数字音频通常分为三步 xff1a 采样 量化 编码 采样 xff1a 就是将获取的信号给数字化 xff0c 其中有个概念就是采样频率 xff0c 而人耳能听到的频率范围只有20Hz 20kHz xff0c 所以一般设置的都是44
  • GCC编译C/C++程序(一步完成)

    使用 GCC 编译器编译 C 或者 C 43 43 程序 xff0c 也必须要经历这 4 个过程 但考虑在实际使用中 xff0c 用户可能并不关心程序的执行结果 xff0c 只想快速得到最终的可执行程序 xff0c 因此 gcc 和 g 4
  • GCC -E选项:对源程序做预处理操作

    存储在 demo c 文件中 include lt stdio h gt int main puts 34 hello world 34 return 0 通过为 gcc 指令添加 E 选项 xff0c 即可控制 GCC 编译器仅对源代码做
  • GCC -S选项:编译非汇编文件

    root 64 bogon demo cat demo c include lt stdio h gt int main puts 34 Hello World 34 return 0 root 64 bogon demo gcc E de
  • GCC -c选项:生成目标文件

    root 64 bogon demo ls demo c root 64 bogon demo cat demo c include lt stdio h gt int main puts 34 Hello World 34 return

随机推荐

  • GCC -l选项:手动添加链接库

    标准库的大部分函数通常放在文件 libc a 中 xff08 文件名后缀 a代表 achieve xff0c 译为 获取 xff09 xff0c 或者放在用于共享的动态链接文件 libc so 中 xff08 文件名后缀 so代表 shar
  • GCC 编译使用动态链接库和静态链接库

    1 库的分类 根据链接时期的不同 xff0c 库又有静态库和动态库之分 静态库是在链接阶段被链接的 xff08 好像是废话 xff0c 但事实就是这样 xff09 xff0c 所以生成的可执行文件就不受库的影响了 xff0c 即使库被删除了
  • python爬虫——爬取数据导入excel表

    1 导入第三方库 requests库 re html xlwt span class token keyword from span bs4 span class token keyword import span BeautifulSou
  • Makefile call函数

    引用变量的格式为 变量名 xff0c 函数调用的格式如下 xff1a lt function gt lt arguments gt 或者是 lt function gt lt arguments gt 其中 xff0c function 是
  • Glide生命周期绑定

    Glide class和RequestManagerRetriever class xff0c 主要用来获得RequestManager with返回一个RequestManager public static RequestManager
  • Glide缓存机制

    Glide中采用计数的方式统计资源的引用 xff0c 在每个EngineResource内部都设置一个引用计数acquired xff0c 在加载资源时引用 43 43 xff0c 释放资源时引用 xff1a class EngineRes
  • UML类图

    类图 xff08 Class Diagrams xff09 xff1a 用户根据用例图抽象成类 xff0c 描述类的内部结构和类与类之间的关系 xff0c 是一种静态结构图 在UML类图中 xff0c 常见的有以下几种关系 泛化 xff08
  • android源码github

    https github com aosp mirror platform frameworks base
  • jar 包转 java

    jd gui 内 File gt Save All Sources 直接保存到本地
  • DataBinding源码解析

    DataBinding是Google发布的支持库 xff0c 它可以实现UI组件及数据源的双向绑定 使用DataBinding可以轻松实现MVVM模式 xff0c 当数据发生变化时会体现在View界面上 xff0c 反过来界面内容变化也会同
  • LiveData源码分析

    首先还是以一个示例开始 xff1a MutableLiveData lt String gt liveString 61 new MutableLiveData lt gt liveString observe mOwner new Obs
  • ViewModel源码分析

    首先 xff0c 还是先看一个例子 xff1a public class MyViewModel extends ViewModel private MutableLiveData lt List lt User gt gt users p
  • RxJava2源码分析——Map操作符

    本文章用的RxJava和RxAndroid版本如下 xff1a implementation 39 io reactivex rxjava2 rxjava 2 2 6 39 implementation 39 io reactivex rx
  • 交叉编译pytorch的aarch64版本

    提示 xff1a 文章写完后 xff0c 目录可以自动生成 xff0c 如何生成可参考右边的帮助文档 文章目录 前言一 基础环境二 编译流程1 下载源码并配置TOOLCHAIN FILE内容2 预编译出protoc库和sleef库3 ana
  • 使用CSplitterWnd类静态分割的窗口的隐藏[转]

    标题略长 之前百度了很多 xff0c 也看过了很多程序 xff0c 那个时候稍微有点小青涩 xff0c 所以那些东西根本是看不懂什么意思 现在回过头来看 xff0c 其实还是很容易就实现的 当然 xff0c 话题很初级 xff0c 不是面向
  • Rxjava2源码-FlatMap操作符

    先来看一下使用demo Observable create new ObservableOnSubscribe lt String gt 64 Override public void subscribe ObservableEmitter
  • 代理设计模式

    代理模式的结构与实现 代理模式的结构比较简单 xff0c 主要是通过定义一个继承抽象主题的代理来包含真实主题 xff0c 从而实现对真实主题的访问 xff0c 下面来分析其基本结构和实现方法 1 模式的结构 代理模式的主要角色如下 抽象主题
  • 适配器模式

    模式的结构与实现 类适配器模式可采用多重继承方式实现 xff0c 如 C 43 43 可定义一个适配器类来同时继承当前系统的业务接口和现有组件库中已经存在的组件接口 xff1b Java 不支持多继承 xff0c 但可以定义一个适配器类来实
  • 装饰器模式

    装饰器模式的结构与实现 通常情况下 xff0c 扩展一个类的功能会使用继承方式来实现 但继承具有静态特征 xff0c 耦合度高 xff0c 并且随着扩展功能的增多 xff0c 子类会很膨胀 如果使用组合关系来创建一个包装对象 xff08 即
  • 享元设计模式

    享元模式的结构与实现 享元模式的定义提出了两个要求 xff0c 细粒度和共享对象 因为要求细粒度 xff0c 所以不可避免地会使对象数量多且性质相近 xff0c 此时我们就将这些对象的信息分为两个部分 xff1a 内部状态和外部状态 内部状