Java代码审计-设计模式-享元模式

Java设计模式-享元模式(FlyWeight Pattern)

目录

  • 什么是享元模式
  • 享元模式实现方式
  • JavaSE享元模式的应用
  • Struts2享元模式的应用

享元模式有点类似单例模式,都是为了降低内存消耗,提高执行效率。

一、什么是享元模式

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。UML图如下:

image-20230328191305486

  1. Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  2. ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  3. UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
  4. FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。

享元模式将对象的状态分为内部状态和外部状态:

  • 内部状态

内部状态是对象可共享出来的信息,存储在享元对象内部并且不会随环境改变而改变,如用户的ID、Name,它们可以作为一个对象的动态附加信息,不必直接储存在具体某个对象中,属于可以共享的部分。

翻译为白话:类的属性,在生成对象后,该属性每个对象都不一样,那他就是内部状态,用户的ID是每个用户都不一样的。

  • 外部状态

外部状态是对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态,它是一批对象的统一标识,是唯一的一个索引值。

翻译为白话:类的属性,在生成对象后,该属性大部分对象或部分的值相同,那么这个属性就是外部状态,例如用户属于VIP用户,很多用户都属于VIP用户。

二、享元模式实现方式

写个坦克大战的案例,地图上有非常多的坦克,比如金色、银色、灰色等等,每个坦克的速度不同,但跑不出3个速度:1KM/s、5KM/s、10KM/s;无论坦克如何生成都是3个颜色乘以3个速度的种类的一种。

坦克会被创建、消灭,我们使用享元模式来模拟这个案例,将9类坦克放入工厂缓存,每次坦克被消灭后,直接从工厂取一个,就不需要再创建了。

坦克的属性有:ID、Name、Color、Speed、Gun;

外部属性:Color、Speed

内部属性:ID、Name、Gun

为什么Name不是外部属性?

因为我们需要从工厂缓存中拿到一个缓存对象,如果根据Name查找,那么需要100个坦克,缓存中便会有100个对象,这样便失去了享元模式的作用。所以从缓存中获取对象时使用的key,一定是外部属性,这个案例中,我们使用color+Speed,那么缓存中最多就只有9个对象,每次取出来修改下ID、Name、Gun就可以了。

首先分析下这个案例的UML图,如下图:

image-20230421155829938

GameRole是抽象的享元类,里面放了内部状态,Tank类是具体的享元类,外部属性被封装为TankType,由于基地每次游戏就生成一次,所以是一个非享元类。接下来看下代码实现

package org.flyweight.version1;

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

/**
 * 实现一个坦克大战中坦克角色的创建
 * 分析下:
 *     游戏角色有共同的属性:颜色、速度、id、name、存活状态等
 *     我们将颜色、速度作为外部属性;id、name、存活作为内部属性;
 *     颜色往往代表了战力,黄色代表高战力、白色代表中等战力、灰色代表一般战力
 *     享元模式:被共享的单元
 */

class TankType{
    private String color;
    private String speed;

    public TankType(String color, String speed){
        this.color = color;
        this.speed = speed;
    }
    // 重写hashcode的生成方式,因为map的get方法获取key对应的value,会对key进行hashcode计算
    @Override
    public int hashCode() {
        return this.color.hashCode() + this.speed.hashCode();
    }
    // 重写两个对象的比较方法
    @Override
    public boolean equals(Object obj) {
        TankType t = (TankType) obj;
        return getSpeed() == t.getSpeed() && getColor() == t.getColor();
    }
    // 省略了些get/set方法
}

/**
 * 游戏角色:抽象的享元对象
 */
abstract class GameRole {
    public String id;
    public String name;
    public boolean isLive = true;

    public abstract void move();

    // 省略了些get/set方法
}

class Tank extends GameRole{
    private String color;
    private String speed;
    private String gun;

    public Tank(TankType tankType){
        setColor(tankType.getColor());
        setSpeed(tankType.getSpeed());
    }

    @Override
    public void move() {
        System.out.println("坦克在移动");
    }

    public void openFire(){
        System.out.println("坦克在开火");
    }
    // 私有的,不能被手动设置颜色
    private void setColor(String color) {
        this.color = color;
    }
    // 私有的,不能被手动设置速度
    private void setSpeed(String speed) {
        this.speed = speed;
    }

    // 省略了些get/set方法
}

/**
 * 非享元对象:游戏中基地只有2个无需共享
 */
class Home extends GameRole{
    public Home(String id,String name){
        super();
        super.id = id;
        super.name = name;
    }
    @Override
    public void move() {
        System.out.println("基地不会移动");
    }
}

class GameFactory{
    private static Map<TankType,Tank> tanks = new HashMap<>();
    public static Tank getTank(TankType tankType){

        Tank tank = tanks.get(tankType);
        if (tanks.get(tankType) == null){
            System.out.println("【新创建】一个坦克,颜色为:" + tankType.getColor() + "速度为:" + tankType.getSpeed());
            tank = new Tank(tankType);
            tanks.put(tankType,tank);
            return tank;
        }
        System.out.println("【获取】一个坦克,颜色为:" + tank.getColor() + ",速度为:" + tankType.getSpeed());
        return tank;
    }
    public static Tank getTankWithOutFlyWeight(TankType tankType){
        return new Tank(tankType);
    }
    // 基地只有2个没必要用享元模式,直接new对象即可
    public static Home getHome(String team){
        if (team == "红队"){
            return new Home("1","红队基地");
        }
        return new Home("2","蓝队基地");

    }
}

public class Test {
    public static void main(String[] args) {
        int times = 10;
        // 不使用享元模式
        long starTime = System.currentTimeMillis();
        for (int i = 0; i < times; i++){
            GameFactory.getTankWithOutFlyWeight(randomTankType());
        }
        long endTime = System.currentTimeMillis();
        System.out.printf("不使用享元模式的速度:%d毫秒.\n",(endTime-starTime));

        // 使用享元模式
        // 初始化游戏
        init();
        long starTime1 = System.currentTimeMillis();
        for (int i = 0; i < times; i++){
            // 从工厂拿一个坦克
            GameFactory.getTank(randomTankType());
            //拿到坦克后应该设置ID、name等属性,然后放入地图,这里就不再增加这段代码了。
        }
        long endTime1 = System.currentTimeMillis();
        System.out.printf("使用享元模式的速度:%d毫秒",(endTime1-starTime1));



    }
    // 初始化游戏角色,放入工厂缓存
    public static void init(){
        String colors[] = {"黄色","银色","灰色"};
        String speeds[] = {"10M/S","5M/S","1M/S"};
        String speed,color;
        TankType tankType;
        // 循环生成9个坦克对象
        for (int i = 0; i < colors.length; i++){
            color = colors[i];
            for (int j = 0; j < speeds.length; j++){
                speed = speeds[j];
                tankType = new TankType(color,speed);
                GameFactory.getTank(tankType);
            }
        }
        // 生成两个的基地
        GameFactory.getHome("红队");
        GameFactory.getHome("蓝队");
    }
    // 模拟随机生成坦克类型,提供给工厂使用
    public static TankType randomTankType(){

        String colors[] = {"黄色","银色","灰色"};
        String speeds[] = {"10M/S","5M/S","1M/S"};
        Random random = new Random();
        int randomColor = random.nextInt(3);
        String color = colors[randomColor];
        int randomSpeed = random.nextInt(3);
        String speed = speeds[randomSpeed];

        TankType tankType = new TankType(color,speed);
        return tankType;
    }
}
// 运行结果
不使用享元模式的速度:6毫秒.
【新创建】一个坦克,颜色为:黄色速度为:10M/S
【新创建】一个坦克,颜色为:黄色速度为:5M/S
【新创建】一个坦克,颜色为:黄色速度为:1M/S
【新创建】一个坦克,颜色为:银色速度为:10M/S
【新创建】一个坦克,颜色为:银色速度为:5M/S
【新创建】一个坦克,颜色为:银色速度为:1M/S
【新创建】一个坦克,颜色为:灰色速度为:10M/S
【新创建】一个坦克,颜色为:灰色速度为:5M/S
【新创建】一个坦克,颜色为:灰色速度为:1M/S
【获取】一个坦克,颜色为:银色,速度为:5M/S
【获取】一个坦克,颜色为:灰色,速度为:1M/S
【获取】一个坦克,颜色为:黄色,速度为:1M/S
【获取】一个坦克,颜色为:灰色,速度为:1M/S
【获取】一个坦克,颜色为:灰色,速度为:10M/S
【获取】一个坦克,颜色为:灰色,速度为:5M/S
【获取】一个坦克,颜色为:银色,速度为:1M/S
【获取】一个坦克,颜色为:灰色,速度为:10M/S
【获取】一个坦克,颜色为:银色,速度为:1M/S
【获取】一个坦克,颜色为:黄色,速度为:10M/S
使用享元模式的速度:1毫秒

具体的代码就不在细讲,都是比较简单的代码,值得注意的是两种模式的运行速度是完全不一样的,很显然享元模式的速度更加快,当创建的对象越多时,差距会更明显。

三、JavaSE享元模式的应用

JDK中Integer使用了享元模式,在此之前,先了解下Integer类,下面代码,运行结果是什么?

Integer i1 = 127;
Integer i2 = 127;
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i1 == i2);
System.out.println(i3 == i4);
System.out.println(i3.equals(i4));

int ii1 = 127;
int ii2 = 127;
int ii3 = 128;
int ii4 = 128;
System.out.println(ii1 == ii2);
System.out.println(ii3 == ii4);
// 运行结果
true
false
true
true
true

在Java中有3类比较方法:

  1. ==:判断值是否相等
  2. ===:是判断的是值及类型是否完全相等
  3. equals:如果没有重写Object类的equals,则equals就是使用==比较,如果子类重写了,那就需要看子类如何定义的equals,在前面的例子中我们重写了TankType类的equals方法,如下面代码

image-20230328222754456

// 比对速度和颜色是否相同
@Override
public boolean equals(Object obj) {
    TankType t = (TankType) obj;
    return getSpeed() == t.getSpeed() && getColor() == t.getColor();
}

前面的代码中为什么i1 == i2为true、i3 == i4为false,i3.equals(i4)为true呢?需要理解2个概念

  • ==如何比较两个Java对象?
  • 什么是自动装箱(Autoboxing)和自动拆箱(Unboxing)?

在Java中表示一个int值,有2种方式

int i = 10;// 基本类型,属于八大数据类型,基本类型,也叫内置类型
Integer i = 10;// 包装器类型,非八大数据类型,Java对象

装箱:基本类型->包装器类型; 拆箱:包装器类型->基本类型;

装箱是怎么做的?

当Integer i = 10执行时,10本质上是int基本类型,现在要将它封装为包装器类型,这就将一个基本类型变为了复杂的Java对象;Integer i = 10执行时,底层执行了Integer i = Integer.valueOf(10);可以在Integer类的valueOf(int i)方法上断点即可观察到,valueOf(int i)函数代码如下

public static Integer valueOf(int i) {
    // 如果i大于-128,小于等于127
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        // 从缓存中取出对应的Integer对象,这里用的就是享元模式
        return IntegerCache.cache[i + (-IntegerCache.low)];
    // 封装为一个Integer对象
    return new Integer(i);
}
// Integer的构造函数,内部维护了一个int类型变量,保存真实的int值
public Integer(int value) {
    this.value = value;
}
// Integer的equals方法
public boolean equals(Object obj) {
    // 如果是一个Integer对象,则比较
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    //如果根本就不是Integer对象,直接返回false
    return false;
}
// intValue其实就是返回Integer对象内部的int类型的value
// 所以Integer的equals方法其实就是将内部的int类型的value进行了==比较
public int intValue() {
    return value;
}

Java为基本数据类型提供了对应的包装器类型。如下:

基本数据类型对应包装器类型
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
shortShort
byteByte
charCharacter

拆箱是怎么做的?

Integer i = 10;// 包装器类型,非八大数据类型,Java对象
int j = i; // 底层执行了i.intValue();

intValue方法前面已经展示过,其实就是返回Integer对象内部的int类型的value

现在回到前面的问题:==如何比较两个Java对象?举个例子

User user = new User("张三", 23); // name=张三, age=23

Java把内存分成两种,一种叫做栈内存,一种叫做堆内存

  • 栈内存:一些基本类型的变量和对象的引用变量

  • 堆内存:由new创建的对象和数组

image-20230328230332987

当我们通过“==”来判定两个对象是否相等的时候,实际上是在判断两个局部变量存储的地址是否相同,换句话说,是在判断两个局部变量是否指向相同的对象。

在回头看下前面的代码

Integer i1 = 127;
Integer i2 = 127;
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i1 == i2);// true
System.out.println(i3 == i4);// false
System.out.println(i3.equals(i4)); //true

i3 == i4为false这个理解了,因为执行了 Integer.valueOf()方法,该方法通过new关键字返回了对象,而且==是对比的是是否指向相同对象,所以为false;

i3.equals(i4)本质上调用的是Integer的equals方法,方法内部调用intValue方法返回int型的值,相当于两个int值做==比较,所以为true;

i1 == i2为什么为true呢?前面代码注释中已经讲过了,在看一遍,如下代码

public static Integer valueOf(int i) {
    // 如果i大于-128,小于等于127
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        // 从缓存中取出对应的Integer对象,这里用的就是享元模式
        // IntegerCache是Integer的内部类,会初始化cache数组
        return IntegerCache.cache[i + (-IntegerCache.low)];
    // 封装为一个Integer对象
    return new Integer(i);
}

如果valueOf方法的参数i大于-128,小于等于127取的是缓存中的Integer对象,所以i1与i2取的是一个对象,==是对比的是是否指向相同对象,所以为true;

这就是Integer类的享元模式。

四、Struts2享元模式的应用

没发现使用享元模式,如果读者发现确实使用了原型模式,还请私信我修改,谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Github-Seay源代码审计系统是一种开源的、基于Web代码审计工具,旨在帮助开发人员发现和解决安全漏洞,提高软件安全性。 该系统可以分析Java、C++、C#等多种编程语言的代码,并对其中的安全漏洞进行识别和标记,包括SQL注入、跨站脚本攻击等常见的Web安全问题。还可以自定义规则和规则集,根据自己的需求进行代码分析和漏洞检测。 Github-Seay源代码审计系统的特点包括: 1.开源免费。该系统完全开源,任何人都可以免费使用和修改源代码。 2.支持多种编程语言。可以对多种编程语言的代码进行分析,极大方便了企业的使用。 3.操作简便。系统的界面非常简单,使用起来也非常容易,减少了用户对于操作流程的了解和资质。 4.支持自定义规则。开发人员可以根据自己的需求和项目特点,自定义规则和模板,更好的定制化。 5.漏洞检测准确。该系统内置了丰富的漏洞检测规则,可以快速检测和识别代码中存在的安全问题。同时,Github-Seay持续更新漏洞库与规则,并推动维护人员第一时间修复其中的漏洞。 总的来说,Github-Seay源代码审计系统是一款非常优秀的开源安全审计工具,可以帮助开发人员协助侦测代码中的漏洞并提升软件安全性。 ### 回答2: Github-seay源代码审计系统是一套基于GitHub的源代码安全审计系统,它能够帮助用户以高效、快速、准确的方式对开源项目进行安全审计。该系统利用了GitHub上广泛的项目资源和社区力量,通过代码自动化分析和漏洞扫描等功能,对代码中的安全漏洞进行检测和修复,以帮助用户有效保护自己的项目。 该系统有以下几个主要特点:一是开源免费,且使用自由方便;二是在GitHub上进行,使得项目的审计和修复流程更加高效、方便;三是支持自动化分析和漏洞扫描,可以快速发现代码漏洞;四是利用了社区力量,形成了审核互助的积极氛围;五是提供了专业的代码审计服务,可以对用户提交的代码进行专业安全审计,以提高代码质量和安全性。 使用Github-seay源代码审计系统可以帮助用户更好地进行代码安全审计工作,尽快发现并修补代码中的漏洞,提高项目的安全性和稳定性。同时,该系统对于代码审计爱好者和安全研究人员也提供了学习和交流的平台,可以借此共同促进代码安全事业的发展。 ### 回答3: GitHub-Seay源代码审计系统是一款高效、可靠的源代码审计工具。 该系统能够对代码进行全面的检查和分析,探测出代码中的漏洞和安全隐患,提高代码的安全性和稳定性。它能够充分利用GitHub平台的资源,从源代码仓库中获取代码进行分析,并直接在GitHub上生成审计结果报告。 该系统采用模块化设计,包含语法分析器、符号执行器、数据流分析器、模式匹配器等多个模块。同时,该系统还支持多种编程语言,如Java、C++、JavaScript、Python等,能够适应不同语言的编码规范和特点。 该系统具有以下特点: 1. 高效可靠:系统采用自动化工具进行代码审计,检查范围广泛,及时反馈审计结果,有效减少人为因素干扰。 2. 多语言支持:该系统支持多种编程语言,适应不同语言的审计需求。 3. 模块化设计:该系统采用模块化设计,每个模块都具有独立的功能,协同工作有效提高代码审计效率。 4. 安全稳定:该系统能够发现和修复代码中的安全隐患,提高代码的安全性和稳定性。 总之,GitHub-Seay源代码审计系统是一款高效、可靠、安全、稳定的代码审计工具,适用于各类企业和组织的代码审计需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MarginSelf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值