享元模式
1.需求
完成内容的不同展示方案
- 客户A电脑端发布
- 客户B博客发布
- 客户C微信公众号发布
2.解决方案
- 将内容直接复制若干份,然后根据不同的需求进行定制修改
- 分析
- 相似的实例对象出现多份,造成资源的冗余和浪费
- 多份相同或者相似的实例进行维护起来较为麻烦
- 解决方式:使用享元模式
3.享元模式简单介绍
-
享元模式:"享"表示共享,"元"表示对象(字面上的意思就是共享对象)
-
享元模式(Flyweight pattern)也称为蝇量模式:运用共享技术有效复用细粒度对象(避免重复创建对象)
-
享元模式可以解决 重复对象的内存浪费 问题(假设系统中有大量相似对象,不需要总是创建新对象,可以从建立缓冲池中拿取。这样可以降低系统内存,提高效率)
-
享元模式中的角色:
-
FlyWeight: 抽象的享元角色,它是产品的抽象类,同时定义出对象的外部状态和内部状态的方法或者实现
外部状态: 对象得以依赖的标记,随环境改变而改变,不可共享(以下棋为例,类似于棋子的位置)
内部状态: 对象共享的信息,存储在享元对象内部,不随环境改变而改变(以下棋为例,棋子的颜色) -
ConcreteFlyWeight: 具体的享元角色,是具体的产品类。实现抽象角色定义的相关业务
-
UnSharedConcreteFlyWeight:不可共享的角色(一般不会出现在享元工厂中)
-
FlyweightFactory:享元工厂类, 用于构建一个池容器(集合),同时提供从池中获取对象的相关方法(一般使用者从内部进行获取对象实例,内部可以使用表结构实现)
-
-
享元模式的结构图
- 代码详解
package com.liz.GOF23.flyweight;
/**
* FlyWeight:抽象的享元角色
* 将外部状态和内部状态进行糅合
* */
public abstract class WebSite {
//抽象方法
public abstract void use(User user);
}
package com.liz.GOF23.flyweight;
/**
* ConcreteFlyWeight: 具体的享元角色
* 具体的网站(内部存放类的共有部分)
* (共同点)
*/
public class ConcreteWebsite extends WebSite {
//对象共享的信息 内部状态
private String type = "";//网站发布的形式
//构造器 传入具体的type
public ConcreteWebsite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println("网站的用户为"+user.getName()+" 展示类型是" + type);
}
}
package com.liz.GOF23.flyweight;
import java.util.HashMap;
/**
* FlyweightFactory:享元工厂类
*/
public class WebSiteFactory {
//集合,充当池的作用 key: type value: 实例
private HashMap<String, ConcreteWebsite> pool = new HashMap<>();
//根据网站的类型,返回一个网站
public WebSite getWebSiteCategory(String type) {
//如果没有就创建一个实例,并放入到池中
if (!pool.containsKey(type)) {
pool.put(type, new ConcreteWebsite(type));
}
//如果存在 直接返回即可
return pool.get(type);
}
//池中数据
public int getSizeOfPool() {
return pool.size();
}
}
package com.liz.GOF23.flyweight;
//外部需要使用的部分
//外部状态(区分点)
public class User {
private String name;
public User(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
package com.liz.GOF23.flyweight;
public class Client {
public static void main(String[] args) {
//创建一个工厂
WebSiteFactory factory = new WebSiteFactory();
WebSite ByWeChat = factory.getWebSiteCategory("微信");
WebSite ByBlog = factory.getWebSiteCategory("博客");
ByBlog.use(new User("客户1"));
ByWeChat.use(new User("客户2"));
System.out.println("对象个数:"+factory.getSizeOfPool());
//复用
WebSite ByWeChatCopy = factory.getWebSiteCategory("微信");
ByWeChatCopy.use(new User("客户3"));
System.out.println("对象个数:"+factory.getSizeOfPool());
}
}
运行结果:
注: 在第二次检查对象个数的时候,发现得到的值依旧是2,说明对象的确被复用了。这也是享元模式的核心功能: 在享元模式下,可以避免重复创建相同或者相似的对象,这样可以减少内存和提高程序的效率。此处的缓冲池就是在Factory中定义的Hashmap结构(如果满足key值,就直接返回,否则创建新的值),工业上一般使用UUID作为唯一标识符。
享元模式一些细节
- 享元模式使用共享的方式高效支持大量细粒度的对象
- 享元模式分离出了外部状态和内部状态,以上述代码为例,外部状态为User,内部状态为type,内部状态一般处于具体对象内部,且不可变,外部状态定义在外部且可随着环境可变。所以享元对象可以适配不同的环境
- 享元模式的优势:在减少创建对象的同时,减少内存占用和性能的提高
- 享元模式的劣势: 要分离出外部状态和内部状态,使得程序逻辑复杂。同时正确分离内部外部状态也并不容易。
- 享元模式适用于:
- 系统存在大量相似对象
- 系统需要缓冲池时
- 可以参考String,以及线程池的底层实现