设计模式之享元模式(Flyweight)
写在前面
我们先来看一个程序例子
package com.zl.flyweight;
public class StringTest {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s1 == s3.intern());
}
}
最终输出的结果是true false true
为什么s1==s2?
因为String有常量池的概念。“abc”这个字符串存在于常量池中,所以s1和s2指向的是同一块内存空间,即共享。
为什么s1!=s3?
s3是new出的对象,存在于内存堆中,和常量池中不共享。
s3.intern()是什么?
s3中new出的对象所包含的内容也是一个字符串常量“abc”,调用intern方法代表把s3指向和对象s3所包含内容相同的常量“abc”
享元模式:顾名思义,共享经济。
实际运用
我们平时遇到的数据库连接池,线程池,这种池化思想就是享元模式的运用。
“四人帮”的设计模式一书中对享元模式的举例是“字处理软件”,比如说word文档,我们输入一个字母A和输入很多个字母A,这些字母A一模一样,我们不用每次都去new一个这样的对象,直接去一个池化的“文字池”里面拿即可。
另外,像我们常玩的射击类3A大作,每一个用户在一局游戏中会打出无数个子弹,每天又有无数个用户玩很多局游戏。如果每一个子弹都去new的话,会在服务器端产生很多垃圾,后果就是垃圾回收机制频繁GC,占用系统资源,影响用户体验。
代码示例
子弹类(bullet)
每一个子弹有唯一有id,用来做标识;有属性living,代表子弹是否活着,如果子弹在发射运行中即活着,如果碰到障碍物或击中目标即死亡。
默认子弹都是死亡,即在子弹池中可用。
package com.zl.flyweight;
import java.util.UUID;
public class Bullet {
public boolean living;
public String id;
public Bullet(){
this.living = false;
this.id = UUID.randomUUID().toString().replace("-", "");
}
public boolean isLiving() {
return living;
}
public void setLiving(boolean living) {
this.living = living;
}
@Override
public String toString() {
return "Bullet{" +
"id='" + id + '\'' +
'}';
}
}
子弹池类(BulletPool)
提供get方法,如果当前子弹死亡,代表可用,获取子弹
package com.zl.flyweight;
import java.util.ArrayList;
import java.util.List;
public class BulletPool {
List<Bullet> bulletPool = new ArrayList<>();
{
for (int i = 0; i < 5; i++) {
bulletPool.add(new Bullet());
}
}
public Bullet getBullet(){
for (int i = 0; i < bulletPool.size(); i++) {
if (!bulletPool.get(i).isLiving()){
bulletPool.get(i).setLiving(true);
return bulletPool.get(i);
}
}
return new Bullet();
}
}
BulletTest类,发射子弹
package com.zl.flyweight;
public class BulletTest {
public static void main(String[] args) {
BulletPool bp = new BulletPool();
for (int i = 0; i < 10; i++) {
Bullet bullet = bp.getBullet();
System.out.println(bullet);
//模拟子弹击中物体或碰到障碍物死亡
if (i%2==0){
bullet.setLiving(false);
}
}
}
}
输出:
这就是一个简单共享的实现。
注意:像这些共享池设计,也不能设计太多共享类,这样也会占用一些资源。像射击子弹,我们可用取一天中子弹在固定一个很短时间段的平均值来作为池的容量,如果子弹池中子弹用完了,那再去new呗,只要我们覆盖了大多数的共享即可。所以根据情况,在两者之间取平衡值。