【偷学设计模式(结构型模式)】享元模式 Flyweight

235 阅读3分钟

【偷学设计模式(结构型模式)】享元模式 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 我们开始,为了通用性我们通常会抽象出接口,但实际开发中需要斟酌是否有必要这么做。

享元模式的一种典型结构

  1. Flyweight 抽象享元角色 用于抽象出具体享元类的公共接口,非享元的外部状态以参数形式传入。
  2. FlyweightImpl 具体享元类实现 真正需要共享的单元,实现 FlyWeight 接口
  3. UnFlyweight 非享元角色 该角色扮演享元类的非共享部分,即需要单独定制的部分
  4. **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;
    }

}