享元模式(FlyWeight)

178 阅读5分钟

1. 模式概念

所谓享元模式就是以共享的方式支持大量细粒度对象的复用。

在了解享元模式之前我们先要了解两个概念:内部状态(Internal State)、外部状态(External State)。

  • 内部状态:存储在享元对象内部,不随外界环境改变而改变的共享部分。
  • 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态,享元对象的外部状态由客户端保存,在需要用到的时候再传入享元对象的内部。

内部状态存储于享元对象内部,而外部状态则应该由客户端来考虑。

2. 单纯享元模式:

这里写图片描述 角色如下:

  • 抽象享元角色(Flyweight):规定所有具体享元角色需要实现的方法
  • 具体享元角色(ConcreteFlyweight): 实现抽象享元中的方法,为内部状态提供存储空间
  • 享元工厂角色(FlyweightFactory): 负责创建和管理享元对象,保证享元对象被系统共享。

代码实现:


public interface Flyweight {
    //name 表示外部状态
    public void operation(String name);
}

public class ConcreteFlyweight implements Flyweight {

	//内部状态
    private String intrinsicState;

    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }

    @Override
    public void operation(String name) {
        System.out.println("内部状态:" + intrinsicState);
        System.out.println("外部状态:" + name);
    }
}

/**
通过工厂来生成实例,传入内部状态生成不同的实例
缓存的对象:key-内部状态, value-具体享元对象
*/
public class FlyweightFactory {

    private static HashMap<String, Flyweight> files = new HashMap<>();

    public static Flyweight getInstance(String intrinsicState) {
        Flyweight value = files.get(intrinsicState);
        if(value == null) {
            value = new ConcreteFlyweight(intrinsicState);
            files.put(intrinsicState, value);
        }
        return value;
    }

    public static int getSize() {
        return files.size();
    }
}

//客户端程序
public class Client {

    public static void main(String[] args) {

        Flyweight fly1 = FlyweightFactory.getInstance("土豆");
        fly1.operation("我");
        Flyweight fly2 = FlyweightFactory.getInstance("辣椒");
        fly2.operation("他");
        Flyweight fly3 = FlyweightFactory.getInstance("土豆");
        fly3.operation("你");

        System.out.println(fly1 == fly3);
        System.out.println(FlyweightFactory.getSize());
    }
}

3. 复合享元

在复合享元模式中,将一些单纯享元对象复合,形成复合享元对象,这些复合享元对象是不可以共享的,但是它们可以分解成单纯享元对象,这些单纯享元对象可以共享。 这里写图片描述

源代码:

public interface Flyweight {

    /**
     * @param name  表示外部状态
     */
    public void operation(String name);
}

//共享
public class ConcreteFlyweight implements Flyweight {

	//内部状态
    private String intrinsicState;

    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }

    @Override
    public void operation(String name) {
        System.out.println("点菜者:" + name);
        System.out.println("菜名:" + intrinsicState);
    }
}

//不共享
public class ConcreteCompositeFlyweight implements Flyweight {

    private HashMap<String, Flyweight> map = new HashMap<>();

	//添加单纯享元对象
    public void add(String state, Flyweight fly) {
        map.put(state, fly);
    }
    @Override
    public void operation(String name) {
        Flyweight temp = null;
        for(String key : map.keySet()) {
            temp = map.get(key);
            temp.operation(name);
        }
    }
}

//生产实例
public class FlyweightFactory {

    private static HashMap<String, Flyweight> files = new HashMap<>();

    public static Flyweight getInstance(String state) {
        Flyweight fly = files.get(state);
        if(fly == null) {
            fly = new ConcreteFlyweight(state);
            files.put(state, fly);
        }
        return fly;
    }

    public static Flyweight getInstance(List<String> states) {
        ConcreteCompositeFlyweight compositeFlyweight = new ConcreteCompositeFlyweight();
        for(String state : states) {
            compositeFlyweight.add(state, getInstance(state));
        }

        return compositeFlyweight;
    }
}


public class Client {

    public static void main(String[] args) {
        List<String> compositeState = new ArrayList<String>();
        compositeState.add("辣椒炒肉");
        compositeState.add("牛肉");
        compositeState.add("鸡肉");
        compositeState.add("辣椒炒肉");
        compositeState.add("牛肉");

        Flyweight compositeFly1 = FlyweightFactory.getInstance(compositeState);
        Flyweight compositeFly2 = FlyweightFactory.getInstance(compositeState);
        compositeFly1.operation("我");//外蕴状态是同一个
        System.out.println();
        compositeFly2.operation("你");

        System.out.println("---------------------------------");
        System.out.println("复合享元模式是否可以共享对象:" + (compositeFly1 == compositeFly2));

        String state = "牛肉";
        Flyweight fly1 = FlyweightFactory.getInstance(state);
        Flyweight fly2 = FlyweightFactory.getInstance(state);
        System.out.println("单纯享元模式是否可以共享对象:" + (fly1 == fly2));
    }
}

4. 研磨设计模式

问题描述:在系统当中,存在大量的细粒度对象,而且存在大量的重复数据,严重耗费内存,如何解决? 问题解答:享元模式

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



/**
 * 封装授权数据中重复出现部分的享元对象
 */
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;
	}

	public void add(Flyweight f) {
		throw new UnsupportedOperationException("对象不支持这个功能");
	}
}

/**
 * 不需要共享的享元对象的实现,也是组合模式中的组合对象
 */
public class UnsharedConcreteFlyweight implements Flyweight{
	/**
	 * 记录每个组合对象所包含的子组件
	 */
	private List<Flyweight> list = new ArrayList<Flyweight>();
	
	public void add(Flyweight f) {
		list.add(f);
	}
	
	public boolean match(String securityEntity, String permit) {
		for(Flyweight f : list){
			//递归调用
			if(f.match(securityEntity, permit)){
				return true;
			}
		}
		return false;
	}
}

public class TestDB {
	/**
	 * 用来存放单独授权数据的值
	 */
	public static Collection<String> colDB = new ArrayList<String>();
	/**
	 * 用来存放组合授权数据的值,key为组合数据的id,value为该组合包含的多条授权数据的值
	 */
	public static Map<String,String[]> mapDB = new HashMap<String,String[]>();
	
	static{
		//通过静态块来填充模拟的数据,增加一个标识来表明是否组合授权数据
		colDB.add("张三,人员列表,查看,1");
		colDB.add("李四,人员列表,查看,1");
		colDB.add("李四,操作薪资数据,,2");
		
		mapDB.put("操作薪资数据",new String[]{"薪资数据,查看","薪资数据,修改"});
		
		//增加更多的授权数据
		for(int i=0;i<3;i++){
			colDB.add("张三"+i+",人员列表,查看,1");
		}
	}	
}


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;
	}
}


/**
 * 安全管理,实现成单例
 */
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);
		System.out.println("现在测试"+securityEntity+"的"+permit+"权限,map.size="+map.size());
		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 = null;
				if(ss[3].equals("2")){
					//表示是组合
					fm = new UnsharedConcreteFlyweight();
					//获取需要组合的数据
					String tempSs[] = TestDB.mapDB.get(ss[1]);
					for(String tempS : tempSs){
						Flyweight tempFm = FlyweightFactory.getInstance().getFlyweight(tempS);
						//把这个对象加入到组合对象中
						fm.add(tempFm);
					}
				}else{
					fm = FlyweightFactory.getInstance().getFlyweight(ss[1]+","+ss[2]);
				}
				
				col.add(fm);
			}
		}
		return col;
	}	
}

public class Client {
	public static void main(String[] args) throws Exception{
		//需要先登录,然后再判断是否有权限
		SecurityMgr mgr = SecurityMgr.getInstance();
		mgr.login("张三");
		mgr.login("李四");		
		boolean f1 = mgr.hasPermit("张三","薪资数据","查看");
		boolean f2 = mgr.hasPermit("李四","薪资数据","查看");
		boolean f3 = mgr.hasPermit("李四","薪资数据","修改");
		
		System.out.println("f1=="+f1);
		System.out.println("f2=="+f2);
		System.out.println("f3=="+f3);
		
		for(int i=0;i<3;i++){
			mgr.login("张三"+i);
			mgr.hasPermit("张三"+i,"薪资数据","查看");
		}
	}
}


5. 总结

1、享元模式可以极大地减少系统中对象的数量。

2、享元模式的核心在于享元工厂,它主要用来确保合理地共享享元对象。

3、内部状态为不变部分,存储于享元对象内部,而外部状态是可变部分,它应当由客户端来负责。