设计模式-享元模式

阅读更多

一、介绍

       享元模式,英文(Flyweight),这个翻译还是比较OK的。网上解释比较多,也比较抽象,用我的话来说这个模式就是一个公共,共享的区域,里面放了一些大家可以共用的对象。因为我们知道,创建对象是需要花费时间,占用内存的,但是有些对象常常不需要那么多,仅仅需要一个,或者多个就足够了,也就是不需要到哪儿使用就开始创建。上面解释有点像单例模式,其实单例也是享元 特殊的一种,都是为了复用对象,减少开销儿存在,至于区别,后面说。

 

 

二、实例分析:

       2.1  这里用设计模式里面 字母的例子:我们知道英文里面有26的字母,而我们每一个字母都假设是一个对象,那么我们需要写一篇文章,肯定会使用很多字母,不可能new 很多对象吧,内存消耗不起。这时你会怎么做呢?

      

      2.2 再来一个案例:我们现在到餐馆吃饭,,会给你一个菜单,然后通过一个 点菜器,要吃什么菜,选择了之后就传输到后台了,过一会到时候菜就送上来了。当我们后台看到A,B,C三个人,假设都点了鱼香肉丝这个菜的时候,那么我们就可以一次抄起来,3个人都用的一个对象(实际情况有点区别),这里怎么实现的呢?

 

     先简单看看代码设计吧:

     a. 首先我们要定义一个行为,点菜的行为,我称为享元行为,也就是说大家都要用的一种行为。

     

/**
 * 
 * 设计一个抽象类,规定我们的行为
 *
 */
public abstract class Flyweight {
	public abstract void dianCai();
}

    

 

   b. 然后对行为具体化,也就是后台如何炒菜。我们要通过菜名才产生对象,因此要一个属性name,并强制

       要求点菜,必须传入name.(不然谁知道你点的什么菜!凭啥产生对象)

     

/**
 * 
 * 实现主要的行为,这里我们会得到具体的菜
 * 
 */
public class FlyweightImpl extends Flyweight{
        // 通过菜名,获取不同的菜(对象)
	private String name;
	public FlyweightImpl(String name){
		this.name = name;
	}
	@Override
	public void dianCai() {
		// 这里的行为,假设仅仅是打印
		System.out.println("点的菜名是:"+name);
	}
}

 

 

    c.上面可以根据name产生对象了,但是我们需要知道A B C 三个人,到底点的什么菜呢,是否3个人都点了一样的菜呢?因此我们需要一个集合对象,来保存顾客点的什么菜,从而觉得 是抄一份呢,还是抄多份,相当于判断 都点的不同的菜,产生不同的对象,还是点的相同的菜,只需要产生一个对象。这里肯定根据菜名菜判断啦。

      

import java.util.HashMap;
import java.util.Map;
/**
 * 
 * 炒菜工厂,相当于厨房
 *
 */
public class FlyweightFactory {
        // 一个简单的单例
        private FlyweightFactory(){}
	public static FlyweightFactory  factory = new FlyweightFactory();         
	// 这个用来统一管理客户点的菜,可以从集合中判断哪些菜,被点过了
	private Map map = new HashMap();
	
	// 获得菜的对象,传入用户名字
	public FlyweightImpl getCai(String name){
		FlyweightImpl cai = null;
		// 判断是否有人点过了
		if(map.containsKey(name)){
			// 如果已经有人点了,那么只要炒成一份就行了
			cai = map.get(name);
		}else{
			// 如果没有,就再抄一份,并记录下来
			map.put(name, cai);
		}
		return cai;
	}
	// 计算一共抄了好多份菜,产生了好多个对象
	public int getSize(){
		return map.size();
	}
}

   

 

    d.下面进行测试

       

/**
 * 
 * 客人点菜
 *
 */
public class Client {
	public static void main(String[] args) {
		List list = new ArrayList();
		// 这假设是用户 a b c d e f在 某个是时间点 ,点的菜
		FlyweightFactory chufang = FlyweightFactory.factory;
		Flyweight a = chufang.getCai("鱼香肉丝");
		Flyweight b = chufang.getCai("鱼香肉丝");
		Flyweight c = chufang.getCai("鱼香肉丝");
		Flyweight d = chufang.getCai("小白菜");
		Flyweight e = chufang.getCai("小白菜");
		Flyweight f = chufang.getCai("红烧肉");
		list.add(a);list.add(b);list.add(c);
		list.add(d);list.add(e);list.add(f);
		
		
		// 比较对象
		System.out.println(a.equals(b));
		System.out.println(a.equals(c));
		System.out.println(a.equals(e));
		System.out.println(d.equals(e));
		System.out.println(d.equals(f));
		
		System.out.println("一共产生的对象:"+chufang.getSize());
		
		// 
		for(Flyweight fly : list){
			fly.dianCai();
		}
	}
}

    输出信息:

 

    

true
true
false
true
false
一共产生的对象:3
上的菜是:鱼香肉丝
上的菜是:鱼香肉丝
上的菜是:鱼香肉丝
上的菜是:小白菜
上的菜是:小白菜
上的菜是:红烧肉

 
   从上面看到,虽然点了 7到菜,但是除开一样的,其实只产生了3个对象。

 

   像字母那个实例,也许有的人会说,可以建立26个 对象,都用单例,那么也是可行的。但是如果像下面这个点菜的模式,不同的菜品,不可能创建不同的对象吧,那就太多了。

 

 三、代码分析

        享元模式,主要是为了产生对象,并管理对象,让重复对象提升利用率,减少类存开销。这里有一个很好的例子:“活字印刷术”,相信这个大家想象一下就明白。

        享元模式的使用场景,相信也有一定了解了,从代码上来讲:

        1、享元模式 要分为内部外部状态。

              内部状态:也就是对象是根据 内部的参数而创建或者区分。比如几个引用的对象是否是同一个呢?我们可以通过对象的主键ID 进行比较,也可以通过里面集合属性(name+id) 进行比较,判断 这个对象是否一样,参考对象equals()方法。比如上面例子就是通过菜名字(name) 进行区分,创建对象的时候也是通过name 进行创建。这样的对象创建出来,name 就无法更改了,无法更改的东西,我们就是内部状态,判断该对象是否共享(已经存在),都要经过这个内部状态来判断。

 

               外部状态:就是外部可以改变的,简单来说,假设对象已经创建了,A B C 都拿到这个对象,那么就可以对立面的属性就行改变,因为这个对象是共享的,因此一般都是发生在客户端,拿到对象之后。

                上面例子,我们虽然点 鱼香肉丝的 有3个人,但是我想知道是哪些人点了,怎么办呢?我们尝试加一个可以改变的外部属性。

                

/**
 * 
 * 设计一个抽象类,规定我们的行为
 *
 */
public abstract class Flyweight {
	public abstract void dianCai();
	// 添加一个 添加名字的方法
	public abstract void addPerson(String name);
        // 获得外部属性的方法
	public abstract List getPersons();
}
 

 

 

/**
 * 
 * 实现主要的行为,这里我们会得到具体的菜
 *
 */
public class FlyweightImpl extends Flyweight{
    // 通过菜名,获取不同的菜(对象)
	private String name;

	public FlyweightImpl(String name){
		this.name = name;
	}
	@Override
	public void dianCai() {
		// 这里的行为,假设仅仅是打印
		System.out.println("上的菜是:"+name);
	}
	
	// 外部属性,存放那些人点了这些菜
	private List persons = new ArrayList();

	@Override
	public void addPerson(String name) {
		persons.add(name);
	}

}
 

 

  

Test

FlyweightFactory chufang = FlyweightFactory.factory;
		Flyweight a = chufang.getCai("鱼香肉丝");
		a.addPerson("A");
		Flyweight b = chufang.getCai("鱼香肉丝");
		b.addPerson("B");
		Flyweight c = chufang.getCai("鱼香肉丝");
		c.addPerson("C");

System.out.println(Arrays.toString(a.getPersons().toArray()));
 

 

从上面可以看出,内部状态负责创建对象,如果需要其他的外部状态,是可以从新定义的。而我们熟悉的连接池 就是使用了享元模式。它根据url,driver,username,password内部属性, 创建一个可以共享的对象,然后创建的对象本身可以 断开连接等其他额外的操作,这些都是获得对象之后,在外部操作的。后面我们可以写一个简单的数据库连接池。

 

小结:

        享元模式 为了减少重复对象,提高对象利用率,减少内存开销存在。

        1.首先用抽象类或者接口  指定产生这个对象的行为。如:dianCai();

        2.写一个具体的实现类,实现上面的行为,当让也可以存放一些外部状态属性

        3.需要一个工厂,用来获得对象,并对产生的对象进行管理

        4.客户端利用工厂,和传入的参数,而获得对象,如果有外部状态,并可以改变它。

 

其他:这个模式一般和 单例 工厂模式一起用。这里没加线程控制,仅仅写了享元模式的过程。单例 模式和享元模式的区别在于,单例仅仅是产生不重复的对象,而享元模式会根据内部状态来产生不重复的对象,更加灵活。

你可能感兴趣的:(享元模式,设计模式,享元模式详解)