软件设计模式系列之十三——享元模式

news2024/11/19 7:28:55

1 模式的定义

享元模式(Flyweight Pattern)是一种结构型设计模式,它旨在减少内存占用或计算开销,通过共享大量细粒度对象来提高系统的性能。这种模式适用于存在大量相似对象实例,但它们的状态可以外部化(extrinsic),并且可以在多个对象之间共享的情况。

2 举例说明

为了更好地理解享元模式,让我们举一些现实生活中的例子。

咖啡店的咖啡杯和碟子的例子。在咖啡店中,咖啡杯和碟子通常具有相同的设计和形状,但它们可能具有不同的颜色或图案。咖啡店可以使用享元模式来共享相同设计的杯子和碟子,以减少存储和管理的成本。
在这里插入图片描述

公共交通卡的例子。城市中的公共交通卡(如地铁卡、公共汽车卡)通常具有相同的功能和外观,但每张卡可能包含不同的余额和个人信息。这些卡可以被视为享元对象,公共交通系统可以共享卡的通用功能。

电子书阅读器的字体和样式的例子。电子书阅读器可以使用享元模式来管理字体、字号和样式。多本电子书可以共享相同的字体和样式设置,以提供一致的阅读体验。

这些例子都涉及到具有相似属性和功能的对象,它们可以通过享元模式来共享通用部分,从而减少资源消耗并提高效率。这在设计和生产中可以节省时间和成本。

3 结构

享元模式的结构包括以下主要组件:
在这里插入图片描述

享元工厂(Flyweight Factory):享元工厂负责创建和管理享元对象。它维护一个享元池,其中包含已经创建的享元对象,并根据客户端请求共享已经存在的对象或创建新的享元对象。

享元接口(Flyweight Interface):享元接口是享元对象的抽象,通常声明了享元对象的公共方法,以便客户端能够访问和操作享元对象。

具体享元(Concrete Flyweight):具体享元是享元接口的实现,包含了内部状态和外部状态。内部状态是可以被共享的,而外部状态是不可共享的,它在运行时传递给享元对象。

客户端(Client):客户端是使用享元模式的应用程序或模块,它通过享元工厂来获取或共享享元对象,并根据需要传递外部状态。

4 实现步骤

要实现享元模式,可以按照以下步骤进行操作:

确定内部状态和外部状态:首先,确定对象的内部状态和外部状态。内部状态是可以被多个对象共享的部分,而外部状态是不可共享的。

创建享元接口:定义享元接口,声明享元对象的公共方法,包括操作内部状态和外部状态的方法。

创建具体享元类:实现具体享元类,它包含了内部状态和外部状态的具体实现。内部状态可以在多个对象之间共享,而外部状态需要在运行时传递。

创建享元工厂:创建享元工厂,负责创建和管理享元对象。享元工厂可以维护一个享元池,用于存储已经创建的享元对象。

客户端使用享元对象:在客户端中,通过享元工厂来获取或共享享元对象。客户端需要提供外部状态作为参数,并根据需要操作享元对象。

5 代码实现

以下是一个简单的 Java 代码示例,演示了如何使用享元模式来实现公共交通卡的共享功能。在这个示例中,我们创建了一个 TransportCardFactory 工厂类来管理交通卡对象,以及一个 TransportCard 接口表示交通卡。

// 1. 定义交通卡接口
interface TransportCard {
    void swipe();
}

// 2. 创建具体的交通卡类
class SubwayCard implements TransportCard {
    private String ownerName;
    private int balance;

    public SubwayCard(String ownerName) {
        this.ownerName = ownerName;
        this.balance = 0;
    }

    public void swipe() {
        System.out.println("刷地铁卡,扣除票价,余额:" + balance);
    }
}

// 3. 创建享元工厂类
class TransportCardFactory {
    private Map<String, TransportCard> cards = new HashMap<>();

    public TransportCard getCard(String ownerName) {
        if (cards.containsKey(ownerName)) {
            System.out.println("使用现有的交通卡:" + ownerName);
            return cards.get(ownerName);
        } else {
            System.out.println("创建新的交通卡:" + ownerName);
            TransportCard card = new SubwayCard(ownerName);
            cards.put(ownerName, card);
            return card;
        }
    }
}

// 4. 客户端代码
public class Client {
    public static void main(String[] args) {
        TransportCardFactory cardFactory = new TransportCardFactory();

        // 乘客1刷卡
        TransportCard card1 = cardFactory.getCard("zhanngsan");
        card1.swipe();

        // 乘客2刷卡
        TransportCard card2 = cardFactory.getCard("lisi");
        card2.swipe();

        // 再次刷卡
        TransportCard card3 = cardFactory.getCard("zhanngsan");
        card3.swipe();
    }
}

在这个示例中,我们首先定义了 TransportCard 接口,表示交通卡的通用功能。然后,我们创建了一个具体的交通卡类 SubwayCard,它实现了 TransportCard 接口,并包含了特定于地铁卡的属性。

接下来,我们创建了享元工厂类 TransportCardFactory,它负责管理和共享交通卡对象。当客户端需要一个交通卡时,工厂类会首先检查是否已经存在具有相同拥有者姓名的卡,如果存在则返回现有的卡,否则创建一个新的卡对象。

最后,我们在客户端代码中演示了如何使用享元模式,创建并刷卡,观察到当两位乘客使用相同姓名刷卡时,会共享同一个交通卡对象,从而减少了卡对象的创建和内存占用。

6 典型应用场景

6.1 享元模式通常在以下情况下得到广泛应用

在这里插入图片描述

  • 大量对象。当系统中存在大量相似对象实例时,使用享元模式可以显著减少内存占用,因为相似对象的内部状态可以共享。

  • 内部状态与外部状态。当对象可以分为内部状态和外部状态时,享元模式特别有用。内部状态是对象的固定部分,可以被多个对象共享,而外部状态是对象的可变部分,每个对象可以根据需要个性化。

  • 性能优化。在需要高性能和低内存消耗的情况下,享元模式可以用于共享重复使用的对象,从而提高系统的性能。

  • 缓存管理。在需要缓存大量对象以提高系统响应时间的情况下,可以使用享元模式来管理缓存对象。

  • 资源池管理。当需要管理共享资源池(如数据库连接池、线程池)中的资源对象时,享元模式可以用于有效地共享和重用资源。

享元模式在需要管理大量相似对象、共享内部状态、提高性能和减少内存占用的情况下非常有用。它允许对象在不同上下文中共享内部状态,而外部状态可以根据需要进行个性化定制。通过合理使用享元模式,可以改善系统的效率和资源利用率。

6.2 java中的字符串应用享元模式场景

在Java中,字符串是使用享元模式的经典示例。享元模式的核心思想是共享相似对象的内部状态,以减少内存占用。字符串的使用正是基于这个思想。

下面是Java中字符串如何使用享元模式的一些关键特点:

不可变性:Java中的字符串是不可变的,也就是说一旦创建了一个字符串对象,它的值就不能被修改。这意味着如果两个字符串具有相同的字符序列,它们可以共享相同的内部字符数组。

字符串常量池:Java维护了一个字符串常量池(String Pool),用于存储字符串字面量。当你创建一个字符串字面量时,Java会首先检查常量池中是否已经存在相同值的字符串。如果存在,它将返回常量池中的字符串引用,而不会创建新的对象。

共享相同的字符串对象:由于字符串的不可变性和字符串常量池的存在,多个字符串变量可以共享相同的字符串对象。这意味着如果你有多个字符串变量引用相同的字符串值,它们实际上共享同一个字符串对象。

下面是一个示例,演示了字符串如何使用享元模式:

String s1 = "Hello"; // 创建一个字符串字面量,存储在常量池中
String s2 = "Hello"; // 与s1共享相同的字符串对象

String s3 = new String("Hello"); // 创建一个新的字符串对象,不存储在常量池中
String s4 = new String("Hello"); // 创建另一个新的字符串对象,也不存储在常量池中

System.out.println(s1 == s2); // true,s1和s2共享相同的字符串对象
System.out.println(s1 == s3); // false,s1和s3引用不同的字符串对象

在上面的示例中,s1 和 s2 共享相同的字符串对象,因为它们引用相同的字符串字面量,而 s3 和 s4 创建了新的字符串对象,因为它们使用了 new 操作符。这种共享内部状态的方式减少了内存占用,并提高了性能,特别是当处理大量字符串时。

Java中的字符串是一个典型的享元模式的例子,通过不可变性和字符串常量池,它实现了字符串对象的共享,以减少内存占用和提高性能。这种设计对于处理字符串操作非常高效,并且保证了字符串值的安全性,因为它们不可被修改。

7 优缺点

享元模式具有一些优点和缺点,让我们来看看:

优点:

减少内存占用,享元模式通过共享相似对象的内部状态,可以大大减少内存占用,提高系统的性能和效率。提高性能,通过共享对象,减少了对象的创建和销毁,从而提高了系统的性能。分离内部状态和外部状态,享元模式允许将内部状态和外部状态分开,外部状态可以在运行时传递给享元对象,使系统更灵活。

缺点:

增加复杂性,享元模式引入了共享对象和外部状态的概念,可能增加了系统的复杂性。可能导致线程安全问题,如果多个线程同时访问共享对象并修改其外部状态,可能会导致线程安全问题。

8 类似模式

享元模式通常与其他设计模式一起使用,以解决更复杂的问题或实现更全面的系统。以下是一些常见的设计模式,以及如何与享元模式一起使用它们。

工厂模式。享元模式通常需要一个工厂来创建和管理共享对象。你可以使用工厂模式来创建享元对象,确保对象的创建和初始化过程是封装的,并且客户端不需要直接创建对象。

单例模式。在享元模式中,享元工厂可以是一个单例,以确保只有一个享元工厂实例用于管理共享对象。这样可以确保对象的唯一性和一致性。

装饰者模式:装饰者模式可以与享元模式一起使用,以动态地添加功能或状态到享元对象。装饰者模式允许你在不改变对象结构的情况下,为对象添加额外的行为。

代理模式:代理模式可以与享元模式一起使用,以提供对享元对象的访问控制或延迟加载。代理可以用于监控或限制对共享对象的访问。

组合模式:组合模式用于将对象组织成树形结构,享元模式可以用于共享组合中的相似对象,以减少内存占用。这在图形和图像处理应用中特别有用。

享元模式可以与许多其他设计模式一起使用,具体取决于系统的需求。它通常与创建型模式(如工厂模式、单例模式)和结构型模式(如装饰者模式、代理模式)结合使用,以实现更灵活、高效和可维护的系统。在实际应用中,将多种模式结合使用可以更好地满足复杂系统的需求。

9 小结

享元模式是一种有助于减少内存占用和提高系统性能的结构型设计模式。通过共享大量细粒度的对象,它可以有效地降低系统的资源消耗,特别适用于存在大量相似对象的场景。在设计和开发中,当需要创建大量相似对象时,可以考虑使用享元模式以提高系统的效率和性能。这种模式的核心思想是将对象的内部状态与外部状态分离,从而实现对象的共享和复用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1035773.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人工神经网络ANN:数学总结

一、内容 径向基函数&#xff08;Radial basis function&#xff0c;RBF&#xff09;&#xff1a;一个取值仅依赖于到原点距离的实值函数&#xff0c;即。此外&#xff0c;也可以按到某一中心点c的距离来定义&#xff0c;即。 可以用于许多向函基数的和来逼近某一给定的函数&a…

SpringBoot使用@Async异步注解

首先&#xff0c;想一想为什么使用异步线程? 举个例子: 当我们请求这个接口的时候,在接口调用了method这个方法 然而被调用的方法执行了一个线程睡眠三秒 因为method方法睡眠了三秒钟,所以这个接口响应的时间肯定是大于三秒。因为接口是从上往下执行的,首先会在控制台输出一…

微信小程序python+nodejs+php+springboot+vue 讲座预约系统

讲座预约管理系统的用户是系统最根本使用者&#xff0c;按需要分析系统包括用户&#xff1a;学生、管理员。 管理员通过后台的登录页面&#xff0c;选择管理员权限后进行登录&#xff0c;管理员的权限包括学生信息管理和文章公告管理。讲座公告管理&#xff0c;添加讲座公告信息…

Swift SwiftUI 隐藏键盘

如果仅支持 iOS 15 及更高版本&#xff0c;则可以通过聚焦和取消聚焦来激活和关闭文本字段的键盘。 在最简单的形式中&#xff0c;这是使用 FocusState 属性包装器和 focusable() 修饰符完成的-第一个存储一个布尔值&#xff0c;用于跟踪第二个当前是否被聚焦。 Code struct C…

洛谷P8815:逻辑表达式 ← CSP-J 2022 复赛第3题

【题目来源】https://www.luogu.com.cn/problem/P8815https://www.acwing.com/problem/content/4733/【题目描述】 逻辑表达式是计算机科学中的重要概念和工具&#xff0c;包含逻辑值、逻辑运算、逻辑运算优先级等内容。 在一个逻辑表达式中&#xff0c;元素的值只有两种可能&a…

JDBC MySQL任意文件读取分析

JDBC MySQL任意文件读取分析 文章首发于知识星球-赛博回忆录。给主管打个广告&#xff0c;嘿嘿。 在渗透测试中&#xff0c;有些发起mysql测试流程(或者说mysql探针)的地方&#xff0c;可能会存在漏洞。在连接测试的时候通过添加allowLoadLocalInfileInPath,allowLoadLocalInf…

分类预测 | MATLAB实现WOA-CNN-GRU-Attention数据分类预测

分类预测 | MATLAB实现WOA-CNN-GRU-Attention数据分类预测 目录 分类预测 | MATLAB实现WOA-CNN-GRU-Attention数据分类预测分类效果基本描述模型描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现WOA-CNN-GRU-Attention数据分类预测&#xff0c;运行环境Matlab2021b及以上&…

git学习使用

git使用 1、cmd #查看版本 git version2、初识 Git GUI: Git提供的图形界面工具 Git Bash: Git提供的命令行工具 1.打开Git Bash2.设置自己的用户名和邮箱地址git config --global user.name "xxx"git config --global user.email "123456789163.com"查…

zemaxMIF曲线图

调制传递函数&#xff08; Modulation Transfer Function&#xff0c;MTF &#xff09;是用来形容光学系统成像质量的重要指标。 通过对光学系统像空间进行傅里叶变换&#xff0c;可以得到一张分析图表&#xff0c;来描述像面上对比度和空间频率之间的对应关系。 对比度&…

DALL·E 3:大语言模型和文本生图模型的强强联合

1. 概要 就在不久之前&#xff0c;openAI官网发布了DALLE3相关内容&#xff0c;虽然现在还没有开放直接体验DALLE3的途径&#xff0c;但是我们可以先来一览DALLE3的牛逼之处。 相比之前的DALL.E2&#xff0c;DALL.E3对细节方面把握的更好。此外之前的文生图模型对prompt要求比…

Leetcode 2325.解密消息

给你字符串 key 和 message &#xff0c;分别表示一个加密密钥和一段加密消息。解密 message 的步骤如下&#xff1a; 使用 key 中 26 个英文小写字母第一次出现的顺序作为替换表中的字母 顺序 。将替换表与普通英文字母表对齐&#xff0c;形成对照表。按照对照表 替换 messag…

golang学习笔记(一):基础入门

基础入门 菜鸟教程Go语言环境安装 GoLand开发工具下载 Gin web开发框架 Go 语言流行 ORM 框架 GORM 使用介绍 如何使用Go语言连接分布式MySQL数据库 Go语言依赖搜索网站&#xff0c;类似Maven 添加依赖&#xff1a; 基础知识 1.关键字 go 开启协程执行调用语句/方法。 def…

Spring面试题9:Spring的BeanFactory和FactoryBean的区别和联系

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:说一说Spring的BeanFactory和FactoryBean的区别和联系 区别:BeanFactory是一个工厂接口,主要负责管理和创建Bean实例。它是Spring提供的最底层的…

Centos7虚拟机硬盘扩容 + 修改Docker默认存储位置

文章目录 前言一、Centos7虚拟机硬盘扩容1.1 具体步骤二、修改Docker默认存储位置2.1 挂载目录2.2 修改路径 前言 Vmware 虚拟机开机时提示&#xff1a;虚拟机根目录系统内存不足&#xff0c;可能原因是 /var/lib/docker 目录占用的磁盘空间不够了。本文记录虚拟机磁盘扩容过程…

Doris数据库FE——SQL handleQuery

SQL解析在下文中指的是将一条sql语句经过一系列的解析最后生成一个完整的物理执行计划的过程。这个过程包括以下四个步骤&#xff1a;词法分析、语法分析、生成逻辑计划、生成物理计划。Doris SQL解析具体包括了六个步骤&#xff1a;词法分析&#xff0c;语法分析、语义分析&am…

文件系统详解

目录 文件系统&#xff08;1&#xff09; 第一节文件系统的基本概念 一、文件系统的任务 二、文件的存储介质及存储方式 三、文件的分类 第二节 文件的逻辑结构和物理结构 一、文件的逻辑结构 二、文件的物理结构 文件系统&#xff08;2&#xff09; 第三节 文件目…

Oracle for Windows安装和配置——Oracle for Windows数据库创建及测试

2.2. Oracle for Windows数据库创建及测试 2.2.1. 创建数据库 1&#xff09;启动数据库创建助手&#xff08;DBCA&#xff09; 进入%ORACLE_HOME%\bin\目录并找到“dbca”批处理程序&#xff0c;双击该程序。具体如图2.1.3-1所示。 图2.1.3-1 双击“%ORACLE_HOME%\bin\dbca”…

mac 配置 httpd nginx php-fpm 详细记录 已解决

在日常mac电脑 开发php项目一直是 httpd 方式 运行&#xff0c;由于有 多版本 运行的需求&#xff0c;docker不想用&#xff0c;索性用 php-fpm进行 功能处理。上次配置 是好的&#xff0c;但是感觉马马虎虎&#xff0c;这次 配置底朝天。因为配置服务器&#xff0c;几乎也都是…

9. Java字符串支持正则表达式的方法

Java —— String字符串 1. 正则表达式2. String正则API3. Object类和toString方法4. equals方法5. 包装类及Number 1. 正则表达式 正则表达式&#xff08;Regular Expression&#xff09;&#xff1a;简称为Regex或RegExp&#xff0c;是一种用于描述字符串模式的工具 作用&…

Jmeter性能测试步骤

Jmeter多用户并发测试 第1步&#xff0c; 在安装目录下的bin文件夹下打开Jmeter&#xff0c;如下图 第2步&#xff0c;新建一个线程组 第3步&#xff0c;新建一个HTTP请求&#xff0c;这里给这个请求重命名为getMsgSum 设置HTTP请求的IP端口&#xff0c;以及路径等 第4步&#…