一,概要
享元模式(Flyweight Pattern)是一种结构型设计模式,它主要通过共享对象来降低系统中对象的数量,从而减少内存占用和提高程序性能。这听起来有点像单例模式,但它们在实现和用途上有很大的区别。享元模式的核心是把一个对象的状态分成内部状态和外部状态,内部状态即是不变的,外部状态是变化的;然后通过共享不变的部分,达到减少对象数量并节约内存的目的。享元模式的本质是缓存共享对象,降低内存消耗。
- 内部状态:内部状态是对象可共享的部分,它存储在享元对象内部,并且不随外部环境的变化而变化。内部状态可以被多个具体享元对象共享。
- 外部状态:外部状态是对象的上下文相关部分,它在使用时动态传入享元对象,并且随外部环境的变化而变化。外部状态不可共享,每个具体享元对象都需要接收外部状态作为参数来完成其操作。
享元模式三大角色
-
享元接口:定义了享元对象的公共方法,这些方法可以操作享元对象的外部状态,外部状态一般作为方法参数传入。
-
具体享元类:实现享元接口,完成具体的对象操作。内部状态作为成员属性存在,一旦初始化完成就不再改变,不对外提供setter方法。
-
享元工厂:负责创建和管理享元对象。当客户端请求一个享元对象时,享元工厂会检查是否有已经创建的享元对象,如果有,则直接返回;如果没有,则创建一个新的享元对象并加入到享元池中。
优点
- 减少内存占用:相同对象只要保存一份,大大降低了系统中对象的数量。
- 提高性能:享元模式减少了对象的创建和销毁,降低了垃圾回收的开销,从而提高了程序性能。
- 提高可扩展性:通过外部状态的引入,享元模式可以灵活地处理不同的上下文,使得系统更具可扩展性。
缺点
-
复杂度增加:享元模式需要将对象的状态进行拆分,引入了内部状态和外部状态的管理,增加了系统的复杂性。
-
线程安全问题:由于享元对象是共享的,因此在多线程环境下,对享元对象的操作需要考虑线程安全。
适用场景
- 系统中存在大量细粒度的对象,且这些对象的状态可以分为内部状态和外部状态时,可以考虑使用享元模式。
- 对象的大部分状态可以共享,而一小部分状态需要外部环境来改变时,可以使用享元模式。
- 需要缓存对象以提高系统性能,并且可以接受一定的对象复用时,可以使用享元模式。
- 需要对对象进行池化管理,以便于统一控制和管理对象的创建、销毁和状态维护时,可以考虑使用享元模式。
二,实现案例
假设我们需要在一个坐标图纸上,绘制100个固定半径的圆,圆分为三种颜色。常规方法,我们定义一个圆,里面包含半径,颜色,横坐标,纵坐标四个属性,再定义一个绘制的方法draw(),然后我们创建100个对象,调用draw()方法就可以实现。但是如果我们用享元模式实现,仅需创建3个对象即可,其关键就是将颜色和半径作为内部状态共享,将坐标作为外部状态分离出来。
步骤1:创建享元接口Shape
public interface Shape {
//x,y表示坐标
void draw(int x,int y);
}
步骤2:创建具体享元类Circle
public class Circle implements Shape{
private String color;
private int radius;
public Circle(String color){
this.color = color;
this.radius = 10;
}
@Override
public void draw(int x,int y) {
System.out.println("--在坐标("+x+","+y+")处画圆: [颜色 : " + color
+", 半径 :" + radius+"]");
}
}
步骤3:创建享元工厂ShapeFactory
public class ShapeFactory {
private static final HashMap<String, Shape> circleMap = new HashMap<>();
public static Shape getCircle(String color) {
Circle circle = (Circle)circleMap.get(color);
System.out.println("从缓存中获取"+color+"色的圆");
if(circle == null) {
circle = new Circle(color);
circleMap.put(color, circle);
System.out.println("缓存中不存在,先创建"+color+"色的圆,并放入缓存中");
}
return circle;
}
}
步骤4:客户端测试
public class Client {
private static final String colors[] = { "Red", "Green", "Blue" };
public static void main(String[] args) {
for(int i=0; i < 10; ++i) {
Circle circle = (Circle)ShapeFactory.getCircle(getRandomColor());
circle.draw(getRandomX(),getRandomY());
}
}
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);
}
}
测试结果
三,总结
享元模式是一种非常实用的设计模式,它的思想很简单,就是把一些可以共享的对象只创建一份,放入到缓存池中,供业务方引用。这样做可以大大减少对象的创建开销,减少内存中相似或相同对象的数量,减少内存占用。在java源码中也有很多享元模式的思想体现,如String的字符串常量池,Integer包装类中的IntegerCach,以及各种连接池,线程池等技术。我们在日常开发过程中,可以结合上下文场景,灵活运用享元模式,但需要注意线程安全性和共享池的管理。
最后,希望这篇文章能对大家有所帮助,如果你喜欢这篇文章,不妨点个赞和分享给你的朋友们。欢迎大家在评论区留言交流,我们下次再见!👋