享元模式属于结构型模式,主要解决系统需要使用大量相似对象(细粒度对象)而消耗大量内存资源的问题。享元模式运用共享技术有效地支持大量细粒度的对象,其通过提取对象共同的信息抽象出享元对象,实现共享功能,以此进行一个对象的多次复用,本质是缓存共享对象,降低内存消耗。
享元模式(Flyweight Pattern)又被称作轻量级模式、蝇量模式,它经典的体现就是对象池。享元模式的根本目的就是"共享单元",让对象能够共享复用,就像共享单车一样,路人甲(客户程序A)骑完路人丙(客户程序B)用,以此减少内存占用。
享元模式将对象的信息分为内部状态与外部状态:内部状态是存在于享元对象内部的,不会随环境变化而改变的共享信息;外部状态是无法共享的信息,随环境改变而改变,是由调用者传入享元对象中的信息。使用享元模式时需要创建一个工厂(池)对象并持有一个HashMap
集合来管理这些享元对象。不同的调用者通过唯一标识(key)获取指定的享元对象来达到共享的功能。
举例:现在我要在屏幕上画圆,圆有红、黄、蓝、绿、黑五种颜色,大概要画三十个,那就要建三十个对象,但此时可以使用享元模式设计程序结构,我们将圆的颜色抽取为内部属性,而坐标则是外部属性,由客户程序决定,随后通过图形工厂管理享元对象,相同颜色的圆可以进行复用。
抽象享元(Flyweight)角色:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
此类图中存在一个线程安全问题,也就是
半径
属性,此属性需要通过客户程序给出,但又属于享元对象的内部属性,当在多线程下,一个线程修改了半径
属性,其他线程中的享元对象的该属性也会改变。这也就是上文所说的注意划分外部状态和内部状态,否则可能会引起线程安全问题
。解决方法:可以将半径
属性去除,在绘画
方法中添加面积
参数,这样就得到了外部属性面积
,而半径可以通过面积与π得出。
package 设计模式.结构型模式.享元模式;
/**
* 非享元角色,它以参数的形式注入具体享元的相关方法中
*/
public class 坐标类 {
private Double 坐标x, 坐标y;
public 坐标类(Double 坐标x, Double 坐标y) {
this.坐标x = 坐标x;
this.坐标y = 坐标y;
}
public Double 获取坐标x() {
return 坐标x;
}
public Double 获取坐标y() {
return 坐标y;
}
}
package 设计模式.结构型模式.享元模式;
/**
* 抽象享元角色,是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入
*/
public interface 图形类 {
void 绘画(坐标类 坐标);
}
package 设计模式.结构型模式.享元模式;
/**
* 具体享元角色,实现抽象享元角色中所规定的接口
*/
public class 圆形类 implements 图形类{
private String 颜色;
private Double 半径; // 此内部属性在多线程中会引起线程安全问题
public 圆形类(String 颜色) {
this.颜色 = 颜色;
}
/*
此属性需要通过客户程序给出,但又属于享元对象的内部属性,当在多线程下,一个线程修改了`半径`属性,其他线程中的享元对象的该属性也会改变
解决办法:可以将"半径"属性去除,在绘画方法中添加"面积"参数,这样就得到了外部属性"面积",而半径可以通过面积与π得出
*/
public void 设置半径(Double 半径) {
this.半径 = 半径;;
}
@Override
public void 绘画(坐标类 坐标) {
// .2f 保留小数点后两位
System.out.printf("【%s】圆形:坐标(%.2f,%.2f) 半径%.2f \n",颜色,坐标.获取坐标x(),坐标.获取坐标y(),半径);
}
}
package 设计模式.结构型模式.享元模式;
import java.util.HashMap;
public class 图形工厂类 {
private static HashMap<String, 图形类> 图形集合 = new HashMap<>() ;
private 图形工厂类(){}
public static 图形类 获取图形(String 颜色){
图形类 图形 = 图形集合.get(颜色);
// 如果图形集合中没有该共享对象的原型,则创建一个
if (图形 == null){
图形 = new 圆形类(颜色);
图形集合.put(颜色, 图形);
}
return 图形;
}
}
package 设计模式.结构型模式.享元模式;
public class 客户程序类 {
private static final String[] 颜色列表 = new String[]{"红","黄","蓝","绿","黑"};
public static void main(String[] args) {
// 创建30
for (int i = 0; i < 30; i++) {
坐标类 随机坐标 = new 坐标类(Math.random()*100, Math.random() * 100);
圆形类 圆形 = (圆形类) 图形工厂类.获取图形(获取随机颜色());
圆形.设置半径(Math.random()*100);
圆形.绘画(随机坐标);
}
}
private static String 获取随机颜色(){
return 颜色列表[(int) (Math.random()*颜色列表.length)];
}
}
【绿】圆形:坐标(83.22,92.62) 半径38.38
【红】圆形:坐标(72.57,95.39) 半径96.26
【黑】圆形:坐标(18.29,22.11) 半径33.26
【黑】圆形:坐标(48.28,32.95) 半径9.79
【黑】圆形:坐标(25.65,91.99) 半径21.51
【黄】圆形:坐标(92.08,18.76) 半径48.55
【黑】圆形:坐标(35.10,20.12) 半径0.53
【黄】圆形:坐标(57.73,99.47) 半径90.03
【黑】圆形:坐标(3.87,75.13) 半径41.67
【蓝】圆形:坐标(46.09,78.58) 半径71.46
【黑】圆形:坐标(73.91,44.40) 半径51.43
【黄】圆形:坐标(30.00,48.53) 半径8.46
【黑】圆形:坐标(29.40,99.16) 半径93.00
【红】圆形:坐标(62.56,35.63) 半径93.06
【蓝】圆形:坐标(26.88,44.36) 半径62.43
【黑】圆形:坐标(53.38,58.30) 半径77.71
【黄】圆形:坐标(95.18,66.50) 半径38.43
【黄】圆形:坐标(22.78,23.79) 半径76.93
【黑】圆形:坐标(27.47,17.04) 半径50.00
【黄】圆形:坐标(68.51,14.11) 半径43.27
【黑】圆形:坐标(12.65,94.49) 半径31.03
【红】圆形:坐标(4.87,36.21) 半径99.06
【绿】圆形:坐标(78.40,12.93) 半径56.09
【黄】圆形:坐标(58.96,37.03) 半径30.91
【黑】圆形:坐标(43.07,69.88) 半径76.29
【黄】圆形:坐标(33.85,8.76) 半径78.20
【蓝】圆形:坐标(98.77,51.53) 半径99.75
【绿】圆形:坐标(73.40,34.64) 半径72.56
【绿】圆形:坐标(72.90,37.99) 半径85.42
【蓝】圆形:坐标(88.49,50.77) 半径9.74
Process finished with exit code 0
享元模式 (菜鸟教程)
26 设计模式——享元模式(详解版)(知乎)
享元模式详解 (csdn)
享元模式 (博客园)
五分钟学设计模式.14.享元模式 (哔哩哔哩)