程序员必知!享元模式的实战应用与案例分析

发布时间:2024年01月04日

程序员必知!享元模式的实战应用与案例分析 - 程序员古德

享元模式是一种减少相似对象创建和销毁的设计模式,通过将对象状态分为不变和可变部分,实现内存节省和性能提升。例如,在线游戏中大量玩家角色可共享相同的不变属性,而每人特有的可变属性则单独存储,享元模式使用享元类存储不变属性,非享元类存储可变属性,并通过享元工厂管理对象的复用和共享。

定义

程序员必知!享元模式的实战应用与案例分析 - 程序员古德

享元模式是一种对象设计模式,它用于减少大量相似对象(也称为“细粒度对象”)的创建和销毁,从而节省内存和提高性能。

享元模式的基本思想是将对象的内部状态分为两部分:不变部分和可变部分。不变部分包括所有对象共享的相同状态,而可变部分是每个对象特有的状态。通过将不变部分提取出来并存储在享元对象中,可以避免重复创建相同的对象,从而减少内存占用。

举一个简单的例子来说明享元模式的应用场景:假设当前正在开发一个在线游戏,游戏中有大量的玩家角色,每个角色都有相同的名称、等级和经验值等不变属性,但装备、技能等级等是每个角色特有的可变属性,在这种情况下,可以将玩家的不变属性存储在一个享元对象中,然后为每个角色分配一个可变属性的实例,这样,多个角色可以共享同一个享元对象,从而减少内存占用。

享元模式在实现时通常使用两个类:一个享元类(Flyweight)用于存储不变属性,另一个非享元类(Unshared)用于存储可变属性,通过享元工厂(FlyweightFactory)来管理享元对象的创建和销毁,以确保它们能够被正确地复用和共享。

代码案例

程序员必知!享元模式的实战应用与案例分析 - 程序员古德

以下是一个未使用享元模式的反例代码,这个例子中创建了大量的相似对象,导致消耗大量的内存和性能,如下代码:

// 未使用享元模式的反例代码  
public class Circle {  
    private double x;  
    private double y;  
    private double radius;  
      
    // 构造函数,每次调用都会创建一个新的Circle对象  
    public Circle(double x, double y, double radius) {  
        this.x = x;  
        this.y = y;  
        this.radius = radius;  
    }  
      
    // 绘制圆形的方法  
    public void draw() {  
        System.out.println("Circle: Draw() [x : " + x + ", y :" + y + ", radius :" + radius);  
    }  
}  
  
// 客户端调用案例  
public class Client {  
    public static void main(String[] args) {  
        // 创建大量的Circle对象,这将消耗大量的内存和性能  
        for (int i = 0; i < 10; i++) {  
            Circle circle = new Circle(0, 0, 1); // 所有的圆都有相同的位置和半径  
            circle.draw();  
        }  
    }  
}

上面例子中,尽管所有的Circle对象都有相同的位置和半径,但仍然为每个对象分配了内存,如果需要创建数百万个这样的对象,那么内存消耗将是巨大的,此外,由于每个对象都需要被垃圾收集器处理,这也可能降低程序的性能。

如果使用享元模式,可以共享相同的Circle对象,从而大大减少内存消耗和提高性能,在享元模式中,通常会创建一个享元工厂来管理和重用对象,这样,即使需要大量的相似对象,也只需要存储它们的一个实例。

以下是一个使用享元模式的正例代码,在这个例子中,将使用享元模式来减少具有相同属性的Circle对象的创建,将会创建一个CircleFactory来管理Circle对象的创建和重用,如下代码:

// 享元模式中的抽象享元角色  
interface Shape {  
    void draw();  
}  
  
// 享元模式中的具体享元角色  
class Circle implements Shape {  
    private final String color;  
    private final int x;  
    private final int y;  
    private final int radius;  
  
    // 构造函数私有化,因为外部的类不应该直接实例化这个类,而应该通过工厂来获取实例  
    private Circle(String color, int x, int y, int radius) {  
        this.color = color;  
        this.x = x;  
        this.y = y;  
        this.radius = radius;  
    }  
  
    @Override  
    public void draw() {  
        System.out.println("Circle: Draw() [Color : " + color + ", x : " + x + ", y :" + y + ", radius :" + radius + "]");  
    }  
  
    // 根据属性创建Circle的静态内部类,作为享元工厂的实现  
    private static class CircleFactory {  
        // 使用HashMap存储已经创建的Circle对象,实现重用  
        private static final Map<String, Circle> circleMap = new HashMap<>();  
  
        // 获取Circle对象的方法,如果Map中没有则创建一个新的Circle对象并存入Map中  
        public static synchronized Circle getCircle(String color, int x, int y, int radius) {  
            String key = color + x + y + radius; // 将属性和在一起作为key,确保相同的属性可以得到相同的对象  
            if (!circleMap.containsKey(key)) {  
                circleMap.put(key, new Circle(color, x, y, radius));  
            }  
            return circleMap.get(key); // 返回对应key的Circle对象,如果已经存在则直接重用  
        }  
    }  
  
    // 提供一个静态方法来获取Circle对象,客户端应该通过这个方法来获取Circle对象而不是直接new  
    public static Circle getCircle(String color, int x, int y, int radius) {  
        return CircleFactory.getCircle(color, x, y, radius);  
    }  
}  
  
// 客户端调用案例  
public class Client {  
    public static void main(String[] args) {  
        // 通过享元工厂获取Circle对象,如果具有相同属性的对象已经存在,则直接重用该对象  
        for (int i = 0; i < 5; i++) {  
            Circle circle = Circle.getCircle("Red", 0, 0, 1); // 请求相同属性的圆形对象  
            circle.draw(); // 调用绘制方法,将看到是同一个对象被重用  
        }  
        System.out.println("------------------");  
        // 请求不同属性的圆形对象,将会创建新的对象实例  
        Circle anotherCircle = Circle.getCircle("Blue", 1, 1, 2);   
        anotherCircle.draw(); // 调用绘制方法,将看到是一个新的对象被创建并绘制  
    }  
}

在这个例子中,创建了一个Circle类来实现Shape接口,使用了一个私有的静态内部类CircleFactory来作为享元工厂,它使用一个HashMap来存储已经创建的Circle对象,客户端通过调用Circle.getCircle()方法来获取Circle对象,如果具有相同属性的对象已经存在于Map中,那么该方法将返回这个已经存在的对象,否则,它将创建一个新的对象并将其添加到Map中,这样,就可以重用具有相同属性的对象,从而减少内存消耗并提高性能。

核心总结

程序员必知!享元模式的实战应用与案例分析 - 程序员古德

享元模式是一种用于优化性能的设计模式,它通过共享相同或相似对象来减少系统中对象的数量,从而节省内存和提高效率,其优点在于能够显著减少对象创建和销毁带来的开销,特别适用于需要大量相似对象但状态可外部化的场景。然而,享元模式也有缺点,它增加了系统的复杂性,需要额外的逻辑来管理共享对象池,并可能导致状态同步问题。当存在明确需要优化对象数量和内存占用时再考虑使用,同时要仔细设计和管理共享对象池,确保状态的一致性和正确性。

其它应用场景补充

数据库连接池,数据库连接池是享元模式最经典的使用场景之一,在这个场景中,创建数据库连接对象需要消耗大量的资源,而且这些对象的内部状态大部分都是相同的,因此,可以通过享元模式来共享这些对象,减少对象的创建和销毁,提高系统的性能和可扩展性。

线程池,线程池也是享元模式的一个应用场景,线程的创建和销毁需要消耗大量的资源,而且线程的内部状态大部分都是相同的,因此,可以通过享元模式来共享线程对象,避免频繁地创建和销毁线程。

大数据处理,在处理大量数据时,可能会存在大量的重复对象,如图像处理中的像素点、文本处理中的单词等,这些对象可以通过享元模式来减少内存消耗和提高处理速度。

其它场景?

完!

关注我,每天学习互联网编程技术 - 程序员古德

文章来源:https://blog.csdn.net/qusikao/article/details/135382485
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。