设计模式--享元模式

98 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

享元模式的目的是为了减少不必要的频繁创建对象,从而造成性能和内存的浪费,通过对象共享的方式提高资源利用率。在生活中公共交通、共享经济(共享单车、共享充电保)、合租等等都是享元对象的体现。

享元模式

享元模式(Flyweight Pattern)主要目的是实现对象的共享,减少创建对象的数量,以减少内存占用和提高性能,一般会配合工厂模式使用。这种类型的设计模式属于结构型模式。

和单例模式不同,享元模式强调对象共享而不是唯一。

主要就是将相似的对象放入缓存,在下次需要的时候,如果可以从缓存中拿,则直接拿,如果不可以则新建对象,并将对象放入缓存。

java中享元模式的应用:

  • String 字符串池化技术
  • 基本数据类型池化技术
  • 数据库连接池

简单证明

String的池化技术

在java中使用String str = XXX创建字符串,jvm会检测字符串缓存池存不存在这个“abc”,不存在则创建一个并将创建的 “abc”字符串对象放入字符串缓存池,如果下次还是使用String str = XXX的方式创建相同字符串则不会重新创建而是去字符串缓存区拿。

注意String str = XXX 和 String str = new String(XXX)是不同的。new String(XXX)他会在堆内存空间中创建一个独立的String对象而不会存入缓冲区。

@Test
public void testString(){
​
    String str1 = "abc";
    String str2 = "123";
    String str3 = "abc";
    String str4 = new String("123");
    String str5 = new String("123");
    /**
     * true  
     * str1 = “abc” 
     * str3 = "abc" 检测到存在字符串“abc”,直接将str3引用指向缓存池中的“abc”
     */
    System.out.println(str1 == str3);
    /**
     * false
     * String str4 = new String("123"); str4会在堆内存创建一个String对象,堆内存的和缓存池的肯定不相等
     */
    System.out.println(str2 == str4);
    /**
     * false
     * new String("123") 在堆内存创建了一个新的字符串对象
     */
    System.out.println(str4 == str5);
​
}

基本数据类型的包装类型的池化技术,以Integer为例

integer的缓存范围为 【-128,127】,在这之间的Integer都会放入缓存池,超过这个范围当作普通对象处理。jvm理解这个范围的数据比较常用。

@Test
public void testInteger(){
​
    Integer int1 = Integer.valueOf(1);
    Integer int2 = Integer.valueOf(1);
​
    Integer int7 = Integer.valueOf(128);
    Integer int8 = Integer.valueOf(128);
    Integer int9 = Integer.valueOf(-129);
    Integer int10 = Integer.valueOf(-129);
​
    //存在于缓存范围,返回true
    System.out.println(int1 == int2);
    //不存在于缓存范围,当作普通对象处理,返回false
    System.out.println(int7 == int8);
    System.out.println(int9 == int10);
}

内部状态 & 外部状态

内部状态,就是可以共享的,不可变的属性。 外部状态,会随着环境的变化而变化

比如对于数据库连接池技术来说,一个Connection包含driverClassName、UserName、Password和dbname,这些属性对于连接池来说都是一样的,不会随外部环境变化,也就是内部属性。其他作为外部属性。

实现

image-20220615211845852.png

定义抽象享元类:

public interface IConnection {
    /**
     * 获取用户姓名
     * @return
     */
    String getUserName();
​
    /**
     * 释放资源
     */
    void release();
}

定义具体享元类

@Getter
@Setter
@AllArgsConstructor
public class MyConnection implements IConnection{

    String userName;

    ConnectionPoll connectionPoll;

    @Override
    public String getUserName() {
        return this.userName;
    }

    @Override
    public void release() {
        connectionPoll.add(this);
    }
}

定义享元工厂:

public class ConnectionPoll {

    /**
     * 内部属性
     */
    private static String userName = "RolyFish";

    /**
     * 内部属性
     */
    private static String passWord = "123";

    private Integer pollSiz = 10;

    private ConcurrentLinkedQueue<IConnection> poll = new ConcurrentLinkedQueue<>();
    {
        //初始化连接池
        for (int i = 0; i < 3; i++) {
            poll.add(new MyConnection(10, passWord, userName, this));
        }
    }

    public IConnection getConnection(Integer timeOut) {
        if (poll.size() > 0) {
            return poll.remove();
        } else {
            poll.add(this.createInstance(timeOut));
            return getConnection(timeOut);
        }
    }

    public void add(IConnection connection){
        poll.add(connection);
    }
    public IConnection getConnection() {
        return getConnection(10);
    }

    private IConnection createInstance(Integer timeOut) {
        return new MyConnection(timeOut, ConnectionPoll.userName, ConnectionPoll.passWord, this);
    }

    private IConnection createInstance() {
        return createInstance(10);
    }
}

测试:

@Test
public void test(){
    ConnectionPoll connectionPoll = new ConnectionPoll();
    for (int i = 0; i < 10; i++) {
        IConnection connection = connectionPoll.getConnection();
        System.out.println(connection);
        connection.release();
    }
}

image-20220615215726235.png

可以发现实现了对象重用。

优缺点

优点

  • 享元模式可以极大减少内存中对象的数量,使得需要被重复使用的或相似的对象在内存中进行缓存,避免重复 创建新的对象

缺点

  • 增加系统复杂性,需要将外部对象和内部对象抽离,且外部对象的不会随内部对象的改变而改变,需要对系统进行整体评估和划分,对业务和抽象能力要求较高,成本较大