设计模式——享元模式

1 场景问题

1.1 加入权限控制

考虑这样一个问题,给系统加入权限控制,这基本上是所有的应用系统都有的功能了。

对于应用系统而言,一般先要登录系统,才可以使用系统的功能,登录过后,

用户的每次操作都需要经过权限系统的控制,确保该用户有操作该功能的权限,

同时还要控制该用户对数据的访问权限、修改权限等等。总之一句话,一个安全的系统,

需要对用户的每一次操作都要做权限检测,包括功能和数据,以确保只有获得相应授权的人,

才能执行相应的功能,操作相应的数据。

举个例子来说吧:普通人员都有能查看到本部门人员列表的权限,但是在人员列表中每个人员的薪资数据,

普通人员是不可以看到的;而部门经理在查看本部门人员列表的时候,就可以看到每个人员相应的薪资数据。

现在就要来实现为系统加入权限控制的功能,该怎么实现呢?

1.2 不使用模式的解决方案

1.看看现在都已经有什么了

系统的授权工作已经完成,授权数据记录在数据库里面,具体的数据结构就不去展开了,反正里面记录了人员对安全实体所拥有的权限。假如现在系统中已有如下的授权数据:

张三  对  人员列表   拥有    查看的权限
李四  对  人员列表   拥有    查看的权限
李四  对  薪资数据   拥有    查看的权限
李四  对  薪资数据   拥有    修改的权限

2.思路选择

由于操作人员进行授权操作过后,各人员被授予的权限是记录在数据库中的,刚开始有开发人员提出,

每次用户操作系统的时候,都直接到数据库里面去动态查询,以判断该人员是否拥有相应的权限,

但很快就被否决掉了,试想一下,用户操作那么频繁,每次都到数据库里面动态查询,

这会严重加剧数据库服务器的负担,使系统变慢。

为了加快系统运行的速度,开发小组决定采用一定的缓存,当每个人员登录的时候,

就把该人员能操作的权限获取到,存储在内存中,这样每次操作的时候,

就直接在内存里面进行权限的校验,速度会大大加快,这是典型的以空间换时间的做法。

3.实现示例

(1)首先定义描述授权数据的数据对象,示例代码如下:

/**
 * 描述授权数据的数据model
 */
public class AuthorizationModel {
    /**
     * 人员
     */
    private String user;
    /**
     * 安全实体
     */
    private String securityEntity;
    /**
     * 权限
     */
    private String permit;
    public String getUser() {
       return user;
    }
    public void setUser(String user) {
       this.user = user;
    }
    public String getSecurityEntity() {
       return securityEntity;
    }
    public void setSecurityEntity(String securityEntity) {
       this.securityEntity = securityEntity;
    }
    public String getPermit() {
       return permit;
    }
    public void setPermit(String permit) {
       this.permit = permit;
    }
}

(2)为了测试方便,做一个模拟的内存数据库,把授权数据存储在里面,用最简单的字符串存储的方式。示例代码如下:

/**
 * 供测试用,在内存中模拟数据库中的值
 */
public class TestDB {
    /**
     * 用来存放授权数据的值
     */
    public static Collection<String> colDB = new ArrayList<String>();
    static{
       //通过静态块来填充模拟的数据    
       colDB.add("张三,人员列表,查看");
       colDB.add("李四,人员列表,查看");
       colDB.add("李四,薪资数据,查看");
       colDB.add("李四,薪资数据,修改");
       //增加更多的授权数据
       for(int i=0;i<3;i++){
           colDB.add("张三"+i+",人员列表,查看");
       }
    }  
}

(3)接下来实现登录和权限控制的业务,示例代码如下:

/**
 * 安全管理,实现成单例
 */
public class SecurityMgr {
    private static SecurityMgr securityMgr = new SecurityMgr();
    private SecurityMgr(){     
    }
    public static SecurityMgr getInstance(){
       return securityMgr;
    }
    /**
     * 在运行期间,用来存放登录人员对应的权限,
     * 在Web应用中,这些数据通常会存放到session中
     */
    private Map<String,Collection<AuthorizationModel>> map = new HashMap<String,Collection<AuthorizationModel>>();

    /**
     * 模拟登录的功能
     * @param user 登录的用户
     */
    public void login(String user){
       //登录时就需要把该用户所拥有的权限,从数据库中取出来,放到缓存中去
       Collection<AuthorizationModel> col = queryByUser(user);
       map.put(user, col);
    }
    /**
     * 判断某用户对某个安全实体是否拥有某权限
     * @param user 被检测权限的用户
     * @param securityEntity 安全实体
     * @param permit 权限
     * @return true表示拥有相应权限,false表示没有相应权限
     */
    public boolean hasPermit(String user,String securityEntity,String permit){
       Collection<AuthorizationModel> col = map.get(user);
       if(col==null || col.size()==0){
           System.out.println(user+"没有登录或是没有被分配任何权限");
           return false;
       }
       for(AuthorizationModel am : col){
           //输出当前实例,看看是否同一个实例对象
           System.out.println("am=="+am);
           if(am.getSecurityEntity().equals(securityEntity) && am.getPermit().equals(permit)){
              return true;
           }
       }
       return false;
    }
    /**
     * 从数据库中获取某人所拥有的权限
     * @param user 需要获取所拥有的权限的人员
     * @return 某人所拥有的权限
     */
    private Collection<AuthorizationModel> queryByUser(String user){
       Collection<AuthorizationModel> col = new ArrayList<AuthorizationModel>();
       for(String s : TestDB.colDB){
           String ss[] = s.split(",");
           if(ss[0].equals(user)){
              AuthorizationModel am = new AuthorizationModel();
              am.setUser(ss[0]);
              am.setSecurityEntity(ss[1]);
              am.setPermit(ss[2]);

              col.add(am);
           }
       }
       return col;
    }
}

(4)好不好用呢,写个客户端来测试一下,示例代码如下:

public class Client {
    public static void main(String[] args) {
       //需要先登录,然后再判断是否有权限
       SecurityMgr mgr = SecurityMgr.getInstance();
       mgr.login("张三");
       mgr.login("李四");   
       boolean f1 = mgr.hasPermit("张三","薪资数据","查看");
       boolean f2 = mgr.hasPermit("李四","薪资数据","查看");     

       System.out.println("f1=="+f1);
       System.out.println("f2=="+f2);
       for(int i=0;i<3;i++){
           mgr.login("张三"+i);
           mgr.hasPermit("张三"+i,"薪资数据","查看");
       }
    }
}

1.3 有何问题

仔细看看上面输出结果,框住部分的值是不同的,表明这些对象实例肯定不是同一个对象实例,

而是多个对象实例。这就引出一个问题了,就是对象实例数目太多,为什么这么说呢?

看看就描述这么几条数据,数数看有多少个对象实例呢?目前是一条数据就有一个对象实例,

这很恐怖,数据库的数据量是很大的,如果有几万条,几十万条,岂不是需要几万个,甚至几十万个对象实例,这会耗费掉大量的内存。

把上面的问题描述出来就是:在系统当中,存在大量的细粒度对象,而且存在大量的重复数据,严重耗费内存,如何解决?

2 解决方案

2.1 享元模式来解决

应用享元模式来解决的思路

仔细观察和分析上面的授权信息,会发现有一些数据是重复出现的,

比如:人员列表、薪资数据、查看、修改等等。至于人员相关的数据,考虑到每个描述授权的对象都是和某个人员相关的,

所以存放的时候,会把相同人员的授权信息组织在一起,就不去考虑人员数据的重复性了。

把内部状态分离出来共享,称之为享元,通过共享享元对象来减少对内存的占用。

把外部状态分离出来,放到外部,让应用在使用的时候进行维护,并在需要的时候传递给享元对象使用。

为了控制对内部状态的共享,并且让外部能简单的使用共享数据,提供一个工厂来管理享元,把它称为享元工厂。

2.2 享元模式示例代码

1.先看享元的接口定义,通过这个接口flyweight可以接受并作用于外部状态,示例代码如下:

/**
 * 享元接口,通过这个接口享元可以接受并作用于外部状态
 */
public interface Flyweight {
    /**
     * 示例操作,传入外部状态
     * @param extrinsicState 示例参数,外部状态
     */
    public void operation(String extrinsicState);
}

2.接下来看看具体的享元接口的实现,先看共享享元的实现,封装flyweight的内部状态,当然也可以提供功能方法,示例代码如下:

/**
 * 享元对象
 */
public class ConcreteFlyweight implements Flyweight{
    /**
     * 示例,描述内部状态
     */
    private String intrinsicState;
    /**
     * 构造方法,传入享元对象的内部状态的数据
     * @param state 享元对象的内部状态的数据
     */
    public ConcreteFlyweight(String state){
       this.intrinsicState = state;
    }

    public void operation(String extrinsicState) {
       //具体的功能处理,可能会用到享元内部、外部的状态
    }  
}

/**
 * 不需要共享的flyweight对象,
 * 通常是将被共享的享元对象作为子节点,组合出来的对象
 */
public class UnsharedConcreteFlyweight implements Flyweight{
    /**
     * 示例,描述对象的状态
     */
    private String allState;

    public void operation(String extrinsicState) {
       // 具体的功能处理
    }
}

3.在享元模式中,客户端不能直接创建共享的享元对象实例,必须通过享元工厂来创建。接下来看看享元工厂的实现,示例代码如下:

/**
 * 享元工厂
 */
public class FlyweightFactory {
    /**
     * 缓存多个flyweight对象,这里只是示意一下
     */
    private Map<String,Flyweight> fsMap = new HashMap<String,Flyweight>();
    /**
     * 获取key对应的享元对象
     * @param key 获取享元对象的key,只是示意
     * @return key 对应的享元对象
     */
    public Flyweight getFlyweight(String key) {
       //这个方法里面基本的实现步骤如下:      
       //1:先从缓存里面查找,是否存在key对应的Flyweight对象
       Flyweight f = fsMap.get(key);

       //2:如果存在,就返回相对应的Flyweight对象
       if(f==null){
           //3:如果不存在
           //3.1:创建一个新的Flyweight对象
           f = new ConcreteFlyweight(key);
           //3.2:把这个新的Flyweight对象添加到缓存里面
           fsMap.put(key,f);
           //3.3:然后返回这个新的Flyweight对象
       }

       return f;
    }
}

4.最后来看看客户端的实现,客户端通常会维持一个对flyweight的引用,计算或存储一个或多个flyweight的外部状态。示例代码如下:

/**
 * Client对象,通常会维持一个对flyweight的引用,
 * 计算或存储一个或多个flyweight的外部状态
 */
public class Client {
    //具体的功能处理
}

2.3 使用享元模式重写示例

1.按照享元模式,也为了系统的扩展性和灵活性,给享元定义一个接口,外部使用享元还是面向接口来编程,示例代码如下:

/**
 * 描述授权数据的享元接口
 */
public interface Flyweight {
    /**
     * 判断传入的安全实体和权限,是否和享元对象内部状态匹配
     * @param securityEntity 安全实体
     * @param permit 权限
     * @return true表示匹配,false表示不匹配
     */
    public boolean match(String securityEntity,String permit);
}

2.定义了享元接口,该来实现享元对象了,这个对象需要封装授权数据中重复出现部分的数据,示例代码如下:

/**
 * 封装授权数据中重复出现部分的享元对象
 */
public class AuthorizationFlyweight implements Flyweight{
    /**
     * 内部状态,安全实体
     */
    private String securityEntity;
    /**
     * 内部状态,权限
     */
    private String permit;
    /**
     * 构造方法,传入状态数据
     * @param state 状态数据,包含安全实体和权限的数据,用","分隔
     */
    public AuthorizationFlyweight(String state){
       String ss[] = state.split(",");
       securityEntity = ss[0];
       permit = ss[1];
    }

    public String getSecurityEntity() {
       return securityEntity;
    }
    public String getPermit() {
       return permit;
    }

    public boolean match(String securityEntity, String permit) {
       if(this.securityEntity.equals(securityEntity) && this.permit.equals(permit)){
           return true;
       }
       return false;
    }  
}

3.,来看看如何管理这些享元,提供享元工厂来负责享元对象的共享管理和对外提供访问享元的接口。

/**
 * 享元工厂,通常实现成为单例
 */
public class FlyweightFactory {
    private static FlyweightFactory factory = new FlyweightFactory();
    private FlyweightFactory(){    
    }
    public static FlyweightFactory getInstance(){
       return factory;
    }
    /**
     * 缓存多个flyweight对象
     */
    private Map<String,Flyweight> fsMap = new HashMap<String,Flyweight>();
    /**
     * 获取key对应的享元对象
     * @param key 获取享元对象的key
     * @return key对应的享元对象
     */
    public Flyweight getFlyweight(String key) {
       Flyweight f = fsMap.get(key);
       if(f==null){
           f = new AuthorizationFlyweight(key);
           fsMap.put(key,f);
       }
       return f;
    }
}

4.使用享元对象

/**
 * 安全管理,实现成单例
 */
public class SecurityMgr {
    private static SecurityMgr securityMgr = new SecurityMgr();
    private SecurityMgr(){     
    }
    public static SecurityMgr getInstance(){
       return securityMgr;
    }  
    /**
     * 在运行期间,用来存放登录人员对应的权限,
     * 在Web应用中,这些数据通常会存放到session中
     */
    private Map<String,Collection<Flyweight>> map = new HashMap<String,Collection<Flyweight>>();
    /**
     * 模拟登录的功能
     * @param user 登录的用户
     */
    public void login(String user){
       //登录时就需要把该用户所拥有的权限,从数据库中取出来,放到缓存中去
       Collection<Flyweight> col = queryByUser(user);
       map.put(user, col);
    }
    /**
     * 判断某用户对某个安全实体是否拥有某权限
     * @param user 被检测权限的用户
     * @param securityEntity 安全实体
     * @param permit 权限
     * @return true表示拥有相应权限,false表示没有相应权限
     */
    public boolean hasPermit(String user,String securityEntity,String permit){
       Collection<Flyweight> col = map.get(user);
       if(col==null || col.size()==0){
           System.out.println(user+"没有登录或是没有被分配任何权限");
           return false;
       }
       for(Flyweight fm : col){
           //输出当前实例,看看是否同一个实例对象
           System.out.println("fm=="+fm);
           if(fm.match(securityEntity, permit)){
              return true;
           }
       }
       return false;
    }
    /**
     * 从数据库中获取某人所拥有的权限
     * @param user 需要获取所拥有的权限的人员
     * @return 某人所拥有的权限
     */
    private Collection<Flyweight> queryByUser(String user){
       Collection<Flyweight> col = new ArrayList<Flyweight>();
       for(String s : TestDB.colDB){
           String ss[] = s.split(",");
           if(ss[0].equals(user)){
              Flyweight fm = FlyweightFactory.getInstance().getFlyweight(ss[1]+","+ss[2]);
              col.add(fm);
           }
       }
       return col;
    }
}

参考文档:http://www.jianshu.com/p/3a9a6f15391b

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值