程序员必知的23种设计模式之享元模式

2023-10-27

1. 模式引出: 展示网站项目需求

小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:

  1. 有客户要求以新闻的形式发布
  2. 有客户人要求以博客的形式发布
  3. 有客户希望以微信公众号的形式发布
  4. 访问量不高
1.1 传统方案解决网站展现项目
  1. 每个网站数据相同
  2. 将数据作为父类,每增加一种表现形式,就增加一个子类
  3. 一个网站可以有多个用户,一个用户可以浏览多个网站

在这里插入图片描述

1.2 传统方案解决网站展现项目-问题分析
  1. 需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费

  2. 解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源

  3. 对于代码来说,由于是一份实例,维护和扩展都更加容易

  4. 上面的解决思路就可以使用 享元模式 来解决

2. 享元模式基本介绍
  1. 享元模式(Flyweight Pattern) 也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象

  2. 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个

  3. 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率

  4. 享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式

String s = "hello";
String s2 = new String("hello");
System.out.println(s == s2); // true
2.1 享元模式的原理类图

在这里插入图片描述

对原理图的说明-即(模式的角色及职责)

  1. FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态(后面介绍) 的接口或实现

  2. ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务

  3. UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂。( 要区分可共享和不可共享的部分)

  4. FlyWeightFactory享元工厂,用于构建一个池容器(集合),同时提供从池中获取对象方法

2.2 内部状态和外部状态 ( 可共享和不可共享 )
  1. 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态了,即将对象的信息分为两个部分:内部状态和外部状态

  2. 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变

  3. 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。

3. 方案修改

修改后的UML类图

在这里插入图片描述

WebSite.java

public abstract class WebSite {
	public abstract void use(User user);

}

ConcreteWebSite.java

// 具体网站
public class ConcreteWebSite extends WebSite {

	// 共享部分(内部状态)
	private String type;

	@Override
	public void use(User user/* User为非共享,外部状态 */) {
		System.out.println("网站形式为 " + type + " " + user.getName() + "在使用");

	}

	public ConcreteWebSite(String type) {
		super();
		this.type = type;
	}

}

WebSiteFactory.java

// 工厂类,返回一个网站
public class WebSiteFactory {

	// 集合,充当池
	private HashMap<String, ConcreteWebSite> pool = new HashMap<String, ConcreteWebSite>();

	public WebSite getWebSiteCategory(String type) {
		if (!pool.containsKey(type)) {
			pool.put(type, new ConcreteWebSite(type));
		}
		return pool.get(type);

	}
	// 获取数量
	public int getWebSiteCount() {
		return pool.size();
	}

}

User.java

// 外部状态 非共享
public class User {
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public User(String name) {
		super();
		this.name = name;
	}
}

测试Main.java

public class Main {
	public static void main(String[] args) {
		WebSiteFactory factory = new WebSiteFactory();
		WebSite webSite = factory.getWebSiteCategory("新闻");
		webSite.use(new User("echo"));
		WebSite webSite2 = factory.getWebSiteCategory("博客");
		webSite2.use(new User("tom"));
		WebSite webSite3 = factory.getWebSiteCategory("博客");
		webSite3.use(new User("geek"));
		System.out.println("网站对象总数为 "+ factory.getWebSiteCount());
		
	}
}

输出结果

在这里插入图片描述

4. 享元模式在JDK1.8中Interger的应用源码分析

首次先看一段代码(非源码)

FlyWeight.java

public class FlyWeight {
	public static void main(String[] args) {
		Integer x = Integer.valueOf(127);
		Integer y = new Integer(127);
		Integer z = Integer.valueOf(127);
		Integer w = new Integer(127);
		System.out.println(x.equals(y)); // true
		System.out.println(x == y); // false
		System.out.println(x == z); // true
		System.out.println(w == x); // false
		System.out.println(w == y); // false
	}
}

第一个比较的是值,所以结果肯定是true

后面都是比较引用,只要带有new的都是新申请的空间,引用肯定不一样

那为什么x == z比较引用是true?

我们可以定位x和z的创建方法

Integer x = Integer.valueOf(127);
Integer z = Integer.valueOf(127);

创建方法是一样的

肯定是这个Integer的valueOf方法里搞了点事情,我们追到valueOf方法里瞧一瞧

public static Integer valueOf(int i) {
	if (i >= IntegerCache.low && i <= IntegerCache.high)
		return IntegerCache.cache[i + (-IntegerCache.low)];
	return new Integer(i);
}

可以看到,当i >= IntegerCache.low && i <= IntegerCache.high 的时候,就会返回IntegerCache.cache[i + (-IntegerCache.low)] ,否则就根据 i的值新建一个Integer对象返回。

那么,IntegerCache.lowIntegerCache.high是啥?

继续追Integer源码,可以看到lowhigh定义的位置,可以发现一个静态内部类,这里定义了lowhigh

private static class IntegerCache {
	static final int low = -128;
	static final int high;
	...
	static {
       int h = 127;
       ...
     }

low的值和high的值默认分别是-128和127,这不是一个字节的大小范围吗,定义这个有什么用处?

可以看到注释

    /**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     * (缓存以支持JLS要求的-128到127(含)之间的值的自动装箱的对象标识语义。---来自google翻译)
     **/

(就是方便支持自动装箱的意思 )这个看看了解就好,文章重点不是这个

回到valueOf方法,如果ilowhigh之间,就返回IntegerCache.cache[i + (-IntegerCache.low)],这个IntegerCache.cache明显是个数组,继续追数组的定义

private static class IntegerCache {
	...
	static final Integer cache[];
	...
    static {
        ...
		cache = new Integer[(high - low) + 1];
		int j = low;
		for(int k = 0; k < cache.length; k++)
		cache[k] = new Integer(j++);
	...
    }
    ...

可以看到cache数组定义在IntegerCache类中,大小可以存放lowhigh的所有值,并且从低位到高位逐位赋值,也就是说,当调用cache数组是,该数组就会被初始化,空间是满的。再看return IntegerCache.cache[i + (-IntegerCache.low)],返回该数组中的对象,也就是说,如果ilowhigh的范围,可以从数组中获取,而不需要创建(可以看作是缓存)

可以看出,主要的思想就是,首相建立一个缓存(数组),如果刚好范围符合,可以直接取数组中的对象,如果不再范围内,那就新建一个。

测试一下在范围之外的200

Integer a = Integer.valueOf(200);
Integer b = Integer.valueOf(200);
System.out.println(a == b);

输出结果

在这里插入图片描述

5. 享元模式的注意事项和细节
  1. 在享元模式这样理解,“享”就表示共享,“元”表示对象

  2. 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式

  3. 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储

  4. 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率

  5. 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.

  6. 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。

  7. 享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池。

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

程序员必知的23种设计模式之享元模式 的相关文章

随机推荐

  • Grafana 安装(Centos7)

    一 联网安装 1 Grafana安装 1 下载grafana wget https dl grafana com oss release grafana 8 0 3 1 x86 64 rpm wget https dl grafana co
  • java stream 多个filter_跟光磊学Java开发-流式编程

    使用Stream API操作集合 假设现在有一批名字数据 需要筛选出姓张的人后来需求又变了 需要筛选出姓张 而且名字长度大于等于3的人 使用传统方式操作集合只要操作集合都不得不需要使用循环遍历元素每次筛选后都需要使用新的集合存储元素 pac
  • jdbc的练习:建立一个连接数据库的类

    import java sql public class DatabaseConnection String driver com mysql jdbc Driver String url jdbc mysql localhost 3306
  • 【Pytorch深度学习实战】(8)双向循环神经网络(BiRNN)

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • c语言:char *c;测试

    include
  • html图片列表选择器,CSS 列表

    CSS 列表 从某种意义上讲 不是描述性的文本的任何内容都可以认为是列表 人口普查 太阳系 家谱 参观菜单 甚至你的所有朋友都可以表示为一个列表或者是列表的列表 由于列表如此多样 这使得列表相当重要 所以说 CSS 中列表样式不太丰富确实是
  • Laravel 图片不显示解决方案

    转自 https blog csdn net qq 18951197 article details 93163149 图片文件存储位置为 项目目录 storage app public 1 在项目目录下执行命令创建过软链接 php art
  • ELF文件查看利器之objdump用法

    Android在NDK开发工具中提供了objdump 用来帮助开发者查看编译后目标文件的组成结构和具体内容 常用的有以下几个功能选项 1 a或者 archive headers 这个参数起到的作用和ar命令相似 用来看一个 a静态库文件中包
  • OpenStack--部署nova控制节点与计算节点

    官方部署文档 https docs openstack org mitaka zh CN install guide rdo common get started compute html 1 安装并配置 nova 控制节点 官方安装文档
  • ISP算法学习之LSC(镜头阴影校正)

    LSC Lens Shading Correction 是ISP算法中的一个重要组成部分 用于校正镜头遮挡 lens shading 现象 镜头遮挡是由于镜头和图像传感器的光学特性导致的图像亮度不均匀的现象 通常 图像的中心部分较亮 而边缘
  • 破解软件的原理是什么(软件被破解公开)

    到底魔高一丈吗 还是路的高度是一丈 破解与破解之间就像矛盾和矛盾 从第一个软件上线之日起 这就成为了永恒的话题 即使是牛群也不可避免地破译了自己的软件 这不是技术问题 而是信息化时代的产物 软件基于二进制文件存储在系统中 运行时系统平台必须
  • rsync推拉复制同步脚本编写(详细)内附jenkins通过rsync脚本部署到tomcat

    本处基于saltstack的配置目录里分解rsync的tar包 倒序讲解 root master rsync ls rsyncd tar gz 后两个是推复制rsyncd tui master tar gz rsync tui slave
  • JDBC连接MySQL数据库

    文章目录 前言 一 怎么链接数据库 二 使用步骤 https img blog csdnimg cn 91945baa8c094d48890a557614f46fd7 png x oss process image watermark ty
  • stm32定时器详解

    目录 一 时基配置 定时器定时周期计算 二 捕获 比较通道配置 定时器捕获比较模式说明 三 中断优先级配置 四 开启中断与定时器相关函数 链接 一 时基配置 定时器时基配置主要是配置定时器周期 即分频系数和自动重载寄存器 比如现在我需要配置
  • 土壤湿度计检测模块 土壤湿度传感器 机器人智能小车

    https item taobao com item htm spm a1z09 2 0 0 67002e8dtYpcae id 522556171397 u rklgtpkf6bb 土壤湿度模块是一个简易的水分传感器可用于检测土壤的水分
  • 组件路由传参【vue3】

    大家对于vue3也已经不太陌生了 但是在路由传参中会遇到一点小问题 不知大家是否也遇到过 一起来看一下 首先是我们熟悉的vue路由传参 无非就是个router来进行的操作 首先就是导入vue router 这里一定要导入4版本的 不然vue
  • 文件和注册表的重定向解决方法

    注册表重定向解决办法 转载http www 2cto com os 201411 350858 html 32位程序如何访问64位的注册表 HKLM Software 在调用函数RegCreateKeyEx创建注册表项时 对其第六个参数RE
  • .NET Core 在程序集中集成Razor视图

    前言 有时候 我们在开发一个程序集供其他项目引用的时候 可能需要对外输出一些HTML的结构数据 还有一些情况我们可能开发的是一个中间件 这个中间件需要提供一些界面来对外展示数据或者是内部的一些程序的运行信息 这个时候我们也需要一个界面来做这
  • 电路原理图中的“NC“是什么意思?

    电路原理图中的 NC 是什么意思 1 在看电路原理图的时候 电路原理图上有 NC 我查了下是表示 此处不贴任何电子器件 我看了下实际的电路板确实没贴 那么不贴的话是不是相当于这个位置空出来了 断路 了 就是说芯片的这个引脚在电路中是 悬空状
  • 程序员必知的23种设计模式之享元模式

    文章目录 1 模式引出 展示网站项目需求 1 1 传统方案解决网站展现项目 1 2 传统方案解决网站展现项目 问题分析 2 享元模式基本介绍 2 1 享元模式的原理类图 2 2 内部状态和外部状态 可共享和不可共享 3 方案修改 4 享元模