首页 > 编程语言 >程序员必知!享元模式的实战应用与案例分析

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

时间:2024-01-06 22:00:59浏览次数:31  
标签:享元 程序员 必知 创建 对象 int radius Circle

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

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

定义

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

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

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

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

享元模式在实现时通常使用两个类:一个享元类(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中,这样,就可以重用具有相同属性的对象,从而减少内存消耗并提高性能。

核心总结

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

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

其它应用场景补充

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

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

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

其它场景?

完!

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

标签:享元,程序员,必知,创建,对象,int,radius,Circle
From: https://blog.51cto.com/bytegood/9128034

相关文章

  • #yyds干货盘点# LeetCode程序员面试金典:找不同
    题目给定两个字符串s和t,它们只包含小写字母。字符串t由字符串s随机重排,然后在随机位置添加一个字母。请找出在t中被添加的字母。 示例1:输入:s="abcd",t="abcde"输出:"e"解释:'e'是那个被添加的字母。示例2:输入:s="",t="y"输出:"y"代码实现classSo......
  • #yyds干货盘点# LeetCode程序员面试金典:复数乘法
    题目复数可以用字符串表示,遵循"实部+虚部i"的形式,并满足下述条件:实部是一个整数,取值范围是[-100,100]虚部也是一个整数,取值范围是[-100,100]i2==-1给你两个字符串表示的复数num1和num2,请你遵循复数表示形式,返回表示它们乘积的字符串。 示例1:输入:num1="1......
  • 支付行业黑话:支付相关必知术语一网打尽
    每个行业都自己的行业黑话,官方名词叫“术语”。如果你是支付行业的新手或正尝试深入了解这个复杂但又充满机遇的领域,那么掌握行业术语是打开这扇门的钥匙。支付系统无论需求文档还是技术方案文档,都充斥着专业词汇和行业黑话,这些术语有的直观易懂,有的则晦涩难解。在这篇文章中,我们将......
  • 黑客必知的软件
    黑客必知的软件有很多,以下是一些常用的:Wireshark:网络封包分析软件,可以截取网络数据包并进行分析,用于网络故障排查、网络安全监控等。Nmap:网络探测和安全审核的工具,可以用来扫描网络中的主机和服务,以发现潜在的安全漏洞。Metasploit:渗透测试工具,可以帮助黑客进行漏洞利用、代码执行......
  • 软件体系结构与设计模式之享元模式和代理模式
    一.单选题(共6题)(单选题)在享元模式中,外部状态是指()。A.享元对象可共享的所有状态B.享元对象可共享的部分状态C.由享元对象自己保存和维护的状态D.由客户端保存和维护的状态我的答案:D:由客户端保存和维护的状态;正确答案:D:由客户端保存和维护的状态;(单选......
  • 程序员的25大Tomcat面试问题及答案
    文章目录1.Tomcat的缺省端口是多少,怎么修改?2.tomcat有哪几种Connector运行模式(优化)?3.Tomcat有几种部署方式?4.tomcat容器是如何创建servlet类实例?用到了什么原理?5.tomcat如何优化?6.内存调优7.垃圾回收策略调优8.添加JMS远程监控9.专业点的分析工具有10.关于Tomcat的session数......
  • 程序员的50大Spring面试问题及答案
    文章目录1.Spring框架?2.Spring的整体架构?3.Spring可以做什么?4.Spring的优点?缺点?5.你能说几个Spring5的新特性吗?6.IOC?7.什么是依赖注入?8.IOC注入哪几种方式?9.IOC优点?缺点?10.bean的生命周期?11.Spring有几种配置方式?12.Spring中的bean有几种scope?13.什么是AOP(面向切面编程)?14.......
  • 程序员的30大SpringBoot面试问题及答案
    文章目录1.什么是SpringBoot?2.SpringBoot的特征?3.如何快速构建一个SpringBoot项目?4.SpringBoot启动类注解?它是由哪些注解组成?5.什么是yaml?6.SpringBoot支持配置文件的格式?7.SpringBoot启动方式?8.SpringBoot需要独立的容器运行?9.SpringBoot配置途径?10.application.properties和......
  • 程序员的20大Servlet面试问题及答案
    文章目录1.Servlet生命周期2.什么是jsp?jsp和Servlet有什么区别?3.Servlet接口中有哪些方法?4.Servlet3.0中的异步处理指的是什么?5.Servlet中如何获取用户提交的查询参数或表单数据?6.区别请求的转发与重定向?7.比较一下Servlet与Filter8.我们在web应用开发过程中经常遇到输出某......
  • 程序员的38大Redis面试问题及答案-下
    文章目录1.查看配置语法2.获取所有配置项3.设置字符串4.获取字符串5.获取随机key6.获取key存储的类型7.判断key是否存在8.修改key的名称9.返回key存储的字符串的长度10.同时设置多个kv对11.获取多个key的值12.设置key10秒后过期13.查看当前还剩几秒过期14.过期后获取key15.列表最......