java设计模式学习之【享元模式】

2023-12-13 17:51:06

引言

想象一下,您正在开发一个游戏,游戏中有成千上万的树木和建筑。如果每个对象都独立存储它的所有数据,将会占用大量的内存资源。享元模式提供了一种优化的解决方案,它通过共享相似对象的共有部分,减少内存的使用,同时保持独立对象的特性。

享元模式简介

定义与用途

享元模式(Flyweight Pattern)是一种结构型设计模式,用于减少创建大量相似对象的内存开销。它通过共享相似对象的共有状态,减少资源消耗,特别是在需要大量对象的情况下。

实现方式

实现享元模式通常包括以下几个关键组件:

  • 享元接口(Flyweight): 定义了享元对象的共有方法。
  • 具体享元(Concrete Flyweight): 实现享元接口,存储内部状态。
  • 享元工厂(Flyweight Factory): 创建并管理享元对象,确保合理地共享对象。

使用场景

享元模式适用于以下场景:

  • 当一个应用程序需要大量的相似对象时,可以使用享元模式来减少内存消耗。
  • 当对象的大多数状态可以被外部化时,享元模式可以有效地共享数据。
  • 当需要细粒度对象来表示数据时,而且这些数据可以被共享。

例如:

  1. 游戏开发中的环境设计:如树木、草、石头等,可以共享相同的模型和纹理。
  2. 文本处理程序中的字符处理:字符实例可以被共享,以减少内存占用。
  3. 用户界面中的控件和图标:在整个应用中可以共享相同的图标或控件实例。

优势与劣势

  • 优势
    减少内存消耗: 通过共享相似对象,显著减少内存占用。
    提高性能: 减少对象创建和销毁的开销,提升应用性能。
  • 劣势
    增加系统复杂性: 需要维护共享对象的状态,可能增加系统的复杂性。
    外部状态管理: 享元对象的外部状态需要由客户端代码维护,可能增加客户端的负担。

在Java中的应用

在Java中,String常量池是享元模式的一个经典例子。Java虚拟机(JVM)中的String常量池存储了所有的字符串字面量。这些字符串字面量是共享的,从而节约了内存。

享元模式在Spring中的应用

在Spring框架中,享元模式的应用并不像一些其他设计模式那样显而易见,但它确实在一些关键部分发挥作用,特别是在优化性能和资源管理方面。以下是一些示例:

Spring Bean的作用域管理:在Spring框架中,Bean的作用域可以被定义为单例(Singleton),这实际上是享元模式的一种应用。在单例模式下,Spring容器为每个Bean定义创建一个唯一的实例,并在整个容器中共享这个实例。这种方式减少了对象的创建,从而节约资源和提高效率。

Spring Security的权限缓存:在Spring Security中,权限信息经常被缓存以提高性能。这些权限对象的实例在需要时被创建,并在多个上下文中共享,这就是一种享元模式的实现。通过共享相同的权限对象实例,Spring Security减少了对象的创建和内存占用。

资源池的实现:在Spring中,资源如数据库连接和线程池常常使用享元模式进行管理。这些资源被创建并存储在池中,当需要时可以被多个客户端共享和重用。这样的资源共享减少了资源的频繁创建和销毁,优化了性能。

缓存机制:Spring框架提供了缓存抽象,可以通过缓存共享经常访问的数据,减少对外部系统(如数据库)的访问。这种缓存策略的背后思想与享元模式相似,即重用已有对象来减少资源消耗和提高效率。

通过这些应用,Spring框架有效地实现了享元模式的核心思想:共享和重用对象,以减少资源消耗和提高应用性能。

画图示例

在这里插入图片描述
步骤 1: 创建图形一个接口。

public interface Shape {
    void draw();
}

步骤 2: 实现具体类,创建了 Circle 类,实现了 Shape 接口。这个类包含圆的属性,如颜色、坐标和半径。

public class Circle implements Shape {
   private String color;
   private int x;
   private int y;
   private int radius;

   public Circle(String color){
      this.color = color;		
   }

   // 省略了设置 x, y, radius 的方法

   @Override
   public void draw() {
      System.out.println("绘制圆形:[颜色 : " + color + ", x : " + x + ", y :" + y + ", 半径 :" + radius);
   }
}

步骤 3: 创建工厂类
ShapeFactory 类用于基于给定的信息(如颜色)生成 Circle 对象。它内部维护了一个 HashMap,用于缓存已经创建的 Circle 对象。

import java.util.HashMap;

public class ShapeFactory {

   private static final HashMap<String, Circle> circleMap = new HashMap<>();

   public static Shape getCircle(String color) {
      Circle circle = circleMap.get(color);

      if(circle == null) {
         circle = new Circle(color);
         circleMap.put(color, circle);
         System.out.println("创建颜色为 " + color + " 的圆形");
      }
      return circle;
   }
}

步骤 4: 使用工厂类
FlyweightPatternDemo 类演示了如何使用 ShapeFactory 来获取特定颜色的 Circle 实例。它演示了如何有效地重用已经创建的对象,而不是每次都创建新对象。

public class FlyweightPatternDemo {
   private static final String[] colors = { "红色", "绿色", "蓝色", "白色", "黑色" };

   public static void main(String[] args) {
      for(int i = 0; i < 20; ++i) {
         Circle circle = (Circle)ShapeFactory.getCircle(getRandomColor());
         circle.setX(getRandomX());
         circle.setY(getRandomY());
         circle.setRadius(100);
         circle.draw();
      }
   }

   private static String getRandomColor() {
      return colors[(int)(Math.random() * colors.length)];
   }

   private static int getRandomX() {
      return (int)(Math.random() * 100);
   }

   private static int getRandomY() {
      return (int)(Math.random() * 100);
   }
}

在这里插入图片描述
这个示例中,ShapeFactory 充当享元工厂,管理 Circle 对象的创建和缓存。当请求特定颜色的圆时,工厂首先检查是否已经创建了该颜色的圆。如果已存在,则重用该对象;如果不存在,才创建新的圆,并将其存储在哈希表中以供将来重用。这种方式显著减少了对象创建的数量,从而优化了内存使用和性能。

代码地址

23种设计模式相关代码后续会逐步提交到github上,方便学习,欢迎指点:
代码地址
https://github.com/RuofeiSun/lf-23Pattern

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