Flyweight - 享元设计模式

30 阅读2分钟

什么是享元模式?

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

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。由此可以避免内存的占用以及创建对象的消耗。

优缺点

优点:

1.减少创建大量细粒度对象带来的性能消耗,以及内存的占用。

缺点:

1.需要和工厂模式搭配使用。
2.需要注意划分享元类内外状态,否则容易有线程安全问题。

内部状态指不随着环境,稳定的,可以被所有对象所共享的类成员变量。外部状态是指享元对象在被客户端使用时所传入的数据。

示例

享元模式被广泛应用于资源池类应用场景,JVM 在启动时会加载项目中所有的字面值常量到方法区,另外 Integer 类型(-128 ~ 127)和 Long 类型对应的值也是有被缓存的。在项目中,我们常用的数据库连接池是很好的例子,URL以及用户名密码都是不会发生变化的,因此是内部状态,而每个链接发送的请求就是外部状态。

我们来写一个简单的连接池,首先定义 Connection 和其工厂类,Connection 包含两个成员属性 name 和 client,在构造器中初始化,后续不许再改动。

class Connection {
    // 内部状态,不可修改,不可有setter
    private String name;
    private HttpClient client;
    public Connection(String name, HttpClient client) {
        this.name = name;
        this.client = client;
    }
    // request 为外部状态,随意客户端传入
    public HttpResponse<String> send(HttpRequest request) throws IOException, InterruptedException {
        return client.send(request, HttpResponse.BodyHandlers.ofString());
    }

    public String getName() {
        return name;
    }
}

class ConnectionFactory {
    private static HttpClient client = HttpClient.newBuilder().build();
    public static Connection create() {
        return new Connection("Lucien - " + System.currentTimeMillis(), client);
    }
}

连接池对象,如果内置的 List 中还有可用 Connection 即直接返回,如果没有则创建新的。

class ConnectionPool {
    private List<Connection> pool = new ArrayList<>();
    public ConnectionPool(int initSize) {
        for (int i = 0; i < initSize; i++) {
            pool.add(ConnectionFactory.create());
        }
    }
    public Connection get() {
        if (!pool.isEmpty()) {
            return this.pool.remove(0);
        } else {
            pool.add(ConnectionFactory.create());
            return get();
        }
    }
}

测试代码

public static void main(String[] args) throws IOException, InterruptedException {
    final HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("http://www.baidu.com")).GET().build();
    // 为了方便验证,只初始化1个连接,第2个连接势必需要工厂类初始化新的连接
    final ConnectionPool pool = new ConnectionPool(1);
    final Connection connectionA = pool.get();
    HttpResponse<String> responseA = connectionA.send(request);
    System.out.println(connectionA.getName());
    System.out.println(responseA.body());
    Thread.sleep(1000);
    final Connection connectionB = pool.get();
    HttpResponse<String> responseB = connectionB.send(request);
    System.out.println(connectionB.getName());
    System.out.println(responseB.body());
}

Output

Lucien - 1676985868666
<!DOCTYPE html>
<!--STATUS OK--><html> <head>...

Lucien - 1676985868669
<!DOCTYPE html>
<!--STATUS OK--><html> <head>...