【偷学设计模式(结构型模式)】享元模式 Flyweight
享元模式定义
运用共享技术来有效的支持大量细粒度对象的复用。它通过共享已经存在的对象来打幅度减少需要创建的对象、避免大量相似的类的开心,提高系统效率。
ok ,上面的解释很官方了,扯了一堆,所以享元到底是啥?简单来说:共享的单元,朴素的来说:缓存。对的缓存其实就是享元的一种呈现方式,理论上来说只要共享使用同一个类来节省空间,提高其利用率,我们都可称之为享元模式。
比如我们常见的 【单例模式】就可以称之为享元的一种,或者说,享元模式是单例模式的一种增强拓展。
而我们面试经常问的 String,Java 的 String 类型就使用了享元模式的设计思想。
思考一下代码你就了解为什么这么说了:
public class TestJustMain {
public static void main(String[] args) {
String a = "abc";
String b = "abc";
String c= new String("abc");
String d= new String("abc");
System.out.println(a==b);
System.out.println(a==c);
System.out.println(c==d);
System.out.println(a==c.intern());
System.out.println(c.intern()==d.intern());
}
}
true
false
false
true
true
享元模式实现
其实对于设计模式,我们未必就要跟随一个标准的设计模板套到我们的工程中来,而是理解其思想,使用最适合你当前生产环境的编码方式实现。即合适最重要。
但是对于讲解,我们还是需要一个标准的模板来讲解的,我这边会给两个例子,一个标准模板,一个我开发时采用的方式,设计设计并非为了设计而设计。
ok 我们开始,为了通用性我们通常会抽象出接口,但实际开发中需要斟酌是否有必要这么做。
享元模式的一种典型结构
- Flyweight 抽象享元角色 用于抽象出具体享元类的公共接口,非享元的外部状态以参数形式传入。
- FlyweightImpl 具体享元类实现 真正需要共享的单元,实现 FlyWeight 接口
- UnFlyweight 非享元角色 该角色扮演享元类的非共享部分,即需要单独定制的部分
- **FlyweightFactory 享元工厂 **统一生产享元的工厂
Show the Code
public interface Flyweight {
void process(UnFlyweight unFlyweight);
}
public class FlyweightImpl implements Flyweight{
private String chess;
@Override
public void process(UnFlyweight unFlyweight) {
System.out.println("落子:"+this.chess+" 于 "+unFlyweight.getLocation());
}
public FlyweightImpl(String chess) {
this.chess = chess;
}
public String getChess() {
return chess;
}
public void setChess(String chess) {
this.chess = chess;
}
}
public class UnFlyweight {
String location;
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public UnFlyweight(String location) {
this.location = location;
}
}
import java.util.HashMap;
import java.util.Map;
public class FlyweightFactory {
private final Map<String, FlyweightImpl> cache = new HashMap<>();
public FlyweightImpl getFlyweight(String key) {
if (cache.containsKey(key)) {
System.out.println(key + " 已存在直接使用 ");
return cache.get(key);
} else {
System.out.println("创建 " + key);
FlyweightImpl flyweight = new FlyweightImpl(key);
cache.put(key, flyweight);
return flyweight;
}
}
}
public class Test {
public static void main(String[] args) {
FlyweightFactory flyweightFactory=new FlyweightFactory();
flyweightFactory.getFlyweight("黑棋").process(new UnFlyweight("天元"));
flyweightFactory.getFlyweight("白棋").process(new UnFlyweight("地方"));
flyweightFactory.getFlyweight("白棋").process(new UnFlyweight("未知"));
flyweightFactory.getFlyweight("白棋").process(new UnFlyweight("星光"));
flyweightFactory.getFlyweight("黑棋").process(new UnFlyweight("山海"));
}
}
运行结果:
创建 黑棋
落子:黑棋 于 天元
创建 白棋
落子:白棋 于 地方
白棋 已存在直接使用
落子:白棋 于 未知
白棋 已存在直接使用
落子:白棋 于 星光
黑棋 已存在直接使用
落子:黑棋 于 山海
这里我们的 “黑棋” 对象 和 “白棋” 对象只创建了一次,其他的落子都是复用之前的对象,这样大大节省了空间。
最关键的其实就是 FlyweightFactory 中的 Map<String, FlyweightImpl> cache 这样一个 HashMap 。
我们在每次获取不到对象的时候都进行了创建并放到了缓存池 即 HashMap 中以便后期的复用,当然生产中这样做会存在隐患,因为你无法控制 HashMap 的膨胀,最终可能导致 OutOfMemoryError。 所以你需要对你的缓存池进行优化加入缓存淘汰策略如 LRU 或者 FIFO。
幸运的是我们可以直接用写好的轮子来避免这些重复的工作如 google 的 Cache
class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>
static class LocalManualCache<K, V> implements Cache<K, V>, Serializable
相比于这样的模板设计模式的编码方式,这里我提供一个我在生产中写的享元,其要简单的多,即满足需求即可
@Slf4j
@Component
public class LocalCacheFactory {
private static final Cache<String, Set<String>> localCache = CacheBuilder.newBuilder().maximumSize(50000)
.initialCapacity(10000)
.expireAfterWrite(CommonConst.TWO_DAY_SECONDS, TimeUnit.SECONDS).build();
public Set<String> getLocalCache(String name) {
if (StringUtils.isBlank(name)) {
return null;
}
Set<String> result = localCache.getIfPresent(name);
if (result == null) {
result = PinyinUtil.convert2PinyinMulti(name);
localCache.put(name, result);
}
return result;
}
}