享元模式的整体思路就是分离和共享,有些地方直接将享元模式理解成了缓存,其实缓存是享元模式的其中一种应用而已,其并不能完全代替享元模式的思想:享元模式是为了解决大量细粒度的对象占用内存而设计的,将大量细粒度的对象进行共享使用,这样能够在很大的程度上解决内存的问题,但是缓存其实是为了提高访问速度的一种空间换时间的方法,它并不强调内存占用少,事实上很多的缓存设计还增大了内存的占用;但是他们的实现方式上都会有一个缓存的位置,不管是享元的缓存Map还是缓存的服务器,都可以理解成一个放置共享对象的map。
享元模式的结构图和类关系
- Flyweight : 享元的接口,是享元的顶层API实现
- SharedFlyweight : 共享的享元实现类
- UnsharedFlyweight : 非共享的享元实现类,并不是所有的享元对象都是需要共享的,这里可以将非共享的抽离出来
- FlyweightFactory : 享元工厂,享元工厂是缓存享元类的实现类,里面缓存和新建了共享的享元对象,当客户端需要享元对象的时候,可以直接去工厂中获取。这个类一版会设计成单例的。
享元模式应用——权限校验示例
在一般的权限校验中,我们可以将其抽象成【人员】对【安全实体】拥有【权限】,比如下面的权限逻辑:
张三 对 文件列表 有 查看的权限
李四 对 文件列表 有修改的权限
这个时候,【张三】和【李四】就是人员,【文件列表】是安全实体,【查看】和【修改】就是容许的权限
在实际的应用中,人员会非常的多,但是安全实体和权限是可以枚举的,如果我们对每一个人拥有的权限和安全实体都生成一个对象,这个对象就会非常的多,所以,我们可以利用享元模式,将安全实体和权限做一个共享,这样的话,既能够提高匹配效率,也能减少内存的消耗。
- 示例代码:
- 新建一个接口AuthFlyweight,里面新增一个match方法,用来匹配是否对某个安全实体有某个权限
/**
* 鉴权的享元对象
*/
public interface AuthFlyweight {
/**
* 安全实体和对象是否匹配
*
* @param securityEntity :安全实体
* @param permit :权限
* @return :
*/
boolean match(String securityEntity, String permit);
}
- 新建一个享元的具体实现类SecurityFlyweight,实现享元接口
/**
* 具体的安全享元类
*/
public class SecurityFlyweight implements AuthFlyweight {
private String securityEntity;
private String permit;
public SecurityFlyweight(String securityEntity, String permit) {
this.securityEntity = securityEntity;
this.permit = permit;
}
@Override
public boolean match(String securityEntity, String permit) {
return null != this.securityEntity && null != this.permit && this.securityEntity.equals(securityEntity)
&& this.permit.equals(permit);
}
@Override
public String toString() {
return "SecurityFlyweight{" + "securityEntity='" + securityEntity + '\'' + ", permit='" + permit + '\'' + '}';
}
}
- 新建单例享元工厂AuthFlyweightFactory,在这个工厂中,缓存了用过的享元对象,如果需要的是已经缓存的 ,就不需要再新建,直接从缓存中获取
/**
* 享元工厂,一般是单例
*/
public class AuthFlyweightFactory {
/**
* 享元key格式化样式
*/
private static final String FLYWEIGHT_KEY_PATTERN = "%s_%s";
private Map<String, AuthFlyweight> fsMap = new HashMap<>();
private static AuthFlyweightFactory instance = new AuthFlyweightFactory();
private AuthFlyweightFactory() {
}
public static AuthFlyweightFactory getInstance() {
return instance;
}
public AuthFlyweight getFlyweight(String securityEntity, String permit) {
String key = String.format(FLYWEIGHT_KEY_PATTERN, securityEntity, permit);
AuthFlyweight authFlyweight = fsMap.get(key);
if (null == authFlyweight) {
authFlyweight = new SecurityFlyweight(securityEntity, permit);
fsMap.put(key, authFlyweight);
}
return authFlyweight;
}
public void showAuthFlyweight() {
for (AuthFlyweight value : fsMap.values()) {
System.out.println(value);
}
}
}
- 为了示例,我们还需要新建一个权限管理的类,用来给某个人分配权限,这个类也可以设计成单例的,在这个类中,我们模拟数据库给莫个人缓存了权限的列表
/**
* 权限管理类,主要管理人员对应的权限
*/
public class SecurityManager {
private static SecurityManager instance = new SecurityManager();
private SecurityManager() {
}
public static SecurityManager getInstance() {
return instance;
}
private static Map<String, Collection<AuthFlyweight>> userSecurityMap = new HashMap<>();
/**
* 验证是否有权限
*
* @param user :
* @param securityEntity :
* @param permit :
* @return :
*/
public boolean hasPermit(String user, String securityEntity, String permit) {
Collection<AuthFlyweight> authFlyweights = userSecurityMap.get(user);
if (null == authFlyweights || authFlyweights.size() == 0) {
return false;
}
for (AuthFlyweight flyweight : authFlyweights) {
if (flyweight.match(securityEntity, permit)) {
return true;
}
}
return false;
}
/**
* 增加权限
*
* @param user :
* @param securityEntity :
* @param permit :
*/
public void addAuth(String user, String securityEntity, String permit) {
// 查看是否已经有权限
boolean hasPermit = hasPermit(user, securityEntity, permit);
if (hasPermit) {
return;
}
Collection<AuthFlyweight> authFlyweights = userSecurityMap.get(user);
if (null == authFlyweights) {
authFlyweights = new ArrayList<>();
}
// 从享元工厂中获取对象,进行缓存
AuthFlyweight authFlyweight = AuthFlyweightFactory.getInstance().getFlyweight(securityEntity, permit);
authFlyweights.add(authFlyweight);
userSecurityMap.put(user, authFlyweights);
}
/**
* 删除权限
*
* @param user :
* @param securityEntity :
* @param permit :
*/
public void deleteAuth(String user, String securityEntity, String permit) {
// 查看是否已经有权限
boolean hasPermit = hasPermit(user, securityEntity, permit);
if (!hasPermit) {
return;
}
Collection<AuthFlyweight> authFlyweights = userSecurityMap.get(user);
if (null == authFlyweights || authFlyweights.size() == 0) {
return;
}
authFlyweights.removeIf(authFlyweight -> authFlyweight.match(securityEntity, permit));
}
}
- 编写一个测试类,在这个测试类中,我们先预制一些权限,然后新增和删除操作,最后打印出享元工厂中缓存的对象
public class Client {
@Test
public void testAuthFlyweight() {
//预制一些权限数据
SecurityManager.getInstance().addAuth("张三", "文件列表", "查看");
SecurityManager.getInstance().addAuth("李四", "文件列表", "查看");
System.out.print("预制的【张三】对【文件列表】的【查看】权限: ");
System.out.println(SecurityManager.getInstance().hasPermit("张三", "文件列表", "查看"));
System.out.println("----------------------------------------------------------------------------");
System.out.print("预制的【李四】对【文件列表】的【查看】权限: ");
System.out.println(SecurityManager.getInstance().hasPermit("李四", "文件列表", "查看"));
System.out.println("----------------------------------------------------------------------------");
System.out.print("预制的【张三】对【文件列表】的【修改】权限: ");
System.out.println(SecurityManager.getInstance().hasPermit("张三", "文件列表", "修改"));
System.out.println("----------------------------------------------------------------------------");
// 增加【文件列表】的【修改】权限
SecurityManager.getInstance().addAuth("张三", "文件列表", "修改");
System.out.print("增加的【张三】对【文件列表】的【修改】权限: ");
System.out.println(SecurityManager.getInstance().hasPermit("张三", "文件列表", "修改"));
System.out.println("----------------------------------------------------------------------------");
SecurityManager.getInstance().addAuth("李四", "文件列表", "修改");
System.out.print("增加的【李四】对【文件列表】的【修改】权限: ");
System.out.println(SecurityManager.getInstance().hasPermit("李四", "文件列表", "修改"));
System.out.println("----------------------------------------------------------------------------");
// 删除【张三】对【文件列表】的【修改】权限
SecurityManager.getInstance().deleteAuth("张三", "文件列表", "修改");
System.out.print("增加的【张三】对【文件列表】的【修改】权限: ");
System.out.println(SecurityManager.getInstance().hasPermit("张三", "文件列表", "修改"));
System.out.println("----------------------------------------------------------------------------");
System.out.println("最后的共享的享元对象: ");
AuthFlyweightFactory.getInstance().showAuthFlyweight();
}
}
- 执行结果如下:
预制的【张三】对【文件列表】的【查看】权限: true
----------------------------------------------------------------------------
预制的【李四】对【文件列表】的【查看】权限: true
----------------------------------------------------------------------------
预制的【张三】对【文件列表】的【修改】权限: false
----------------------------------------------------------------------------
增加的【张三】对【文件列表】的【修改】权限: true
----------------------------------------------------------------------------
增加的【李四】对【文件列表】的【修改】权限: true
----------------------------------------------------------------------------
增加的【张三】对【文件列表】的【修改】权限: false
----------------------------------------------------------------------------
最后的共享的享元对象:
SecurityFlyweight{securityEntity='文件列表', permit='修改'}
SecurityFlyweight{securityEntity='文件列表', permit='查看'}
从结果我们可以看出,虽然我们对张三和李四都新增过对文件列表的修改权限,但是享元工厂打印出来的只有两个享元实体对象,因为这些对象是公用的,在后面的逻辑中,无论新增了多少人,只要安全实体和权限没有变化,系统的存储就不会出现巨大的新增。
- 示例类图如下
后记
个人总结,欢迎转载、评论、批评指正