目录
享元模式【Flyweight Pattern】,什么是享元模式?核心思想?角色组成?优点和缺点?享元模式应用场景?实现案例?
什么是享元模式?
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享技术有效地支持大量细粒度对象的复用,以减少内存使用量并分享资讯给尽可能多的相似物件。 这种模式通过将对象的状态分为内部状态和外部状态,内部状态是可以共享的,而外部状态是随环境改变的,不可共享。
享元模式适用于那些因重复而导致大量内存使用的情况,通过共享部分状态来减少对象的数量,从而优化内存使用和性能。享元模式可以帮助应用程序避免频繁创建重复的对象,从而节省系统资源。
享元模式的核心思想
享元模式的核心思想是将对象的可共享部分提取出来,作为内部状态进行共享,而将不可共享的部分作为外部状态,通过参数传递。这种模式特别适合那些需要频繁创建大量细粒度对象的场景,通过共享这些对象可以大大减少内存占用。
内部状态与外部状态
内部状态:指的是享元对象中不变、可以共享的部分。例如,颜色、大小、形状等。
外部状态:指的是根据具体场景或实例而变化的部分,不会被共享。例如,位置、速度等。
享元模式的角色组成
(1)抽象享元(Flyweight)
定义了享元对象的内部状态和外部状态,以及需要实现的公共接口。
(2)具体享元(Concrete Flyweight)
实现了抽象享元中定义的接口,包含实现细节,共享相同的内部状态。
(3)非享元(Unsharable Flyweight)
代表不可以共享的外部状态,这些状态是特定于上下文的。
(4)享元工厂(Flyweight Factory)
负责创建和管理享元对象,当请求一个享元对象时,工厂会检查系统中是否已存在符合要求的享元对象,如果存在则提供给客户;如果不存在,则创建一个新的享元对象。
享元模式的优点和缺点
优点
(1)减少内存使用
通过共享对象来减少系统中对象的数量,从而降低内存消耗。
(2)提高性能
避免频繁创建和销毁对象,减少垃圾回收的压力。
(3)线程安全
共享对象可以在多个线程中安全地共享并发访问。
缺点
(1)实现复杂
需要对内部状态和外部状态进行区分和管理。
(2)过度共享
如果内部和外部状态没有合理设计,可能导致系统过度共享。
(3)增加代码复杂度
程序员需要时刻根据系统实际情况选择使用对象池或享元工厂来管理内存和共享对象。
享元模式应用场景
通过共享尽可能多的相同对象来有效地支持大量细粒度对象,从而减少内存使用。它的核心思想是共享相同的对象,避免重复创建,从而节省内存和计算资源。
(1)图形界面元素的重复显示
在图形用户界面中,许多元素可能是相同的,例如按钮、图标、字符等。每次创建相同的元素实例可能浪费大量的内存资源。通过享元模式,可以将相同的元素共享,节省内存。
应用示例:文字处理软件中,一个文档中可能会有成千上万的相同字符。如果为每个字符都创建一个对象,会占用大量内存。通过享元模式,只需为每个字符创建一个共享对象,节省了内存空间。
(2)游戏中的对象管理
在大型多人在线游戏(MMORPG)或策略类游戏中,游戏场景中会有大量相似的物体或角色(如树木、士兵等)。这些物体和角色通常可以使用享元模式进行共享,以减少内存和处理负担。
应用示例:例如在一个森林场景中,树木的外观和行为可能是相似的。享元模式可以将这些树木的相同部分共享,如树的形状、颜色等,只为不同的状态创建独立对象。
(3)网络连接和数据库连接池
在某些应用程序中,频繁创建网络连接或数据库连接会消耗大量系统资源。享元模式可以将这些连接对象作为共享对象管理,避免频繁创建和销毁连接。
应用示例:数据库连接池会复用相同的数据库连接,减少创建和销毁连接的开销。
(4)缓存系统
在缓存系统中,通过享元模式,可以将经常访问的对象存入缓存,避免重复创建,提高系统的性能。
应用示例:Web缓存中的静态资源(如图片、CSS、JavaScript文件)可以作为享元共享,以减少服务器的负担和带宽消耗。
(5)编译器中的抽象语法树
编译器在处理代码时,会将代码转换为抽象语法树(AST)。对于相同的语法结构和关键字,使用享元模式可以节省存储这些结构所需的内存。
应用示例:在编译器中,相同的变量类型、操作符等可以通过享元模式进行共享,避免为每个实例创建新的对象。
享元模式适用于以下条件
(1)系统中有大量的相似对象。
(2)这些对象的状态可以分为内部状态(可共享)和外部状态(不可共享)。
(3)需要通过共享来减少内存开销。
享元模式的关键是减少对象的数量,同时确保性能不受影响。通过这种方式,程序能够有效处理大量细粒度对象的创建和管理。
享元模式实现案例
Java实现案例,以一个使用不同颜色绘制相同类型的图形(如圆形)为例,展示如何利用享元模式来共享相同类型的对象。
(1)定义享元对象接口
首先,定义一个接口 Shape,表示享元对象的抽象。
package com.uhhe.common.design.flyweight;
/**
* 抽象形状接口
*
* @author nizhihao
* @version 1.0.0
* @date 2024/9/6 11:14
*/
public interface Shape {
/**
* 画
*/
void draw();
}
(2)实现享元对象(具体享元)
创建一个 Circle 类,表示具体的享元对象。这个类包含内在状态(例如半径、位置等)和外在状态(例如颜色,传入的不同值)。
package com.uhhe.common.design.flyweight;
/**
* 圆
*
* @author nizhihao
* @version 1.0.0
* @date 2024/9/6 11:15
*/
public class Circle implements Shape {
/**
* 外在状态,不同的对象有不同的颜色
*/
private String color;
private int x;
private int y;
private int radius;
public Circle(String color) {
this.color = color;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setRadius(int radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Drawing Circle [Color: " + color + ", x: " + x + ", y: " + y + ", radius: " + radius + "]");
}
}
下面是一个关于享元模式的Java实现案例,以一个使用不同颜色绘制相同类型的图形(如圆形)为例,展示如何利用享元模式来共享相同类型的对象。
- 定义享元对象接口
首先,我们定义一个接口 Shape,表示享元对象的抽象。
java
复制代码
public interface Shape {
void draw();
}
2. 实现享元对象(具体享元)
我们创建一个 Circle 类,表示具体的享元对象。这个类包含内在状态(例如半径、位置等)和外在状态(例如颜色,传入的不同值)。
java
复制代码
public class Circle implements Shape {
private String color; // 外在状态,不同的对象有不同的颜色
private int x;
private int y;
private int radius;
public Circle(String color) {
this.color = color;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setRadius(int radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Drawing Circle [Color: " + color + ", x: " + x + ", y: " + y + ", radius: " + radius + "]");
}
}
(3)创建享元工厂
享元模式的核心是工厂类,用于管理和复用享元对象。创建一个 ShapeFactory 来管理不同颜色的 Circle 对象。
package com.uhhe.common.design.flyweight;
import java.util.HashMap;
/**
* 管理不同形状
*
* @author nizhihao
* @version 1.0.0
* @date 2024/9/6 11:17
*/
public class ShapeFactory {
private static final HashMap<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
Circle circle = (Circle) circleMap.get(color);
if (circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("Creating circle of color: " + color);
}
return circle;
}
}
(4)客户端代码(享元模式使用DEMO)
客户端代码使用 ShapeFactory 来获取 Circle 对象,并为其设置外在状态(位置和半径等)。每种颜色的 Circle 对象只会创建一次,后续相同颜色的 Circle 都是共享的。
package com.uhhe.common.design.flyweight;
/**
* 享元模式使用DEMO
*
* @author nizhihao
* @version 1.0.0
* @date 2024/9/6 11:18
*/
public class FlyweightPatternDemo {
private static final String[] colors = {"Red", "Green", "Blue", "White", "Black"};
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);
}
}
输出结果:
会看到,对于每种颜色的 Circle,只会创建一次,并且不同的 Circle 对象共享了同样的颜色(内部状态),但可以有不同的位置和半径(外在状态)。
总结:
在这个案例中,通过使用享元模式:
(1)减少内存消耗:颜色相同的圆形只创建一次,从而避免了重复创建对象,节省了内存。
(2)外在状态和内在状态分离:共享对象的内在状态(颜色)保持不变,而外在状态(位置和半径)由客户端动态设置。