设计模式--享元模式 (Flyweight)

  享元模式的宗旨:通过共享来为大量的细粒度对象提供有效支持。
  避免大量拥有相同类型的小类(细粒度,小类中的不可变部份)的开销(如耗费内存),使所有客户代码共享一个实例(元类)。

  怎么使用?(以人力资源系统中的公司员工为例):
  一般来说,在需要对某个员工进行访问的时候,我们都会new一个员工类,如果两个客户代码同时访问同一个员工,则会产生两个相同(或部份相同)的同一员工实例,这样,对于一个员工来说,内存中就可能有该员工的多个实例存在,如果一个企业有10000个员工,那内存中实际的员工实例可能就不止10000个。在这种情况下,我们就可以用享元模式来解决这个问题。
(假设我们系统中原有的类如下图OldPerson所示)

public class OldPerson {
	private String id;
	private String name;
	private double basicSalary;
	private double variableSalary;
 
	/* 省略setter和getter */
 
	public OldPerson(String id, String name) {
		this.id = id;
		this.name = name;
	}
}

我们需要分两步来完成这个模式:

1、提取类中的不可变部份
仔细思考上面的OldPerson类,我们发现,在任何一种情况下,员工的id号和name都是不变的,不管是计算基础薪资的客户代码,还是计算绩效薪资的客户代码,id和name都具有不变性,所以我们可以将这两个属性列为员工类的不可变部份,利用这两个不变属性,创建一个接口NewPerson。

1
2
3
4
5
public interface NewPerson {
	public String getId();
	public String getName();
	public String toString();
}

2、创建享元工厂
在这一步,我们实例化享元并组织共享享元的客户代码,而且我们必须保证各个客户代码都使用享元工厂创建享元实例,而不能自己创建。下图是我们创建的享元工厂。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class PersonFactory {
	private static Map<string , NewPerson> persons = new HashMap();
	static {
		persons.put("001", new NewPersonImpl("001", "Tom"));
		persons.put("002", new NewPersonImpl("002", "Jerry"));
	}
 
	public static NewPerson getPerson(String id) {
		return persons.get(id);
	}
 
	private static class NewPersonImpl implements NewPerson {
		private String id;
		private String name;
 
		public NewPersonImpl(String id, String name) {
			this.id = id;
			this.name = name;
		}
 
		public String getId() {return id;}
		public String getName() {return name;}
		public String toString() {
			return this.id + " ===> " + this.name;
		}
	}
}
</string>

在PersonFactory中,我们创建了它的一个私有内部类NewPersonImpl,并实现了NewPerson,这样即可禁止客户代码自己实例化NewPersonImple,这也是为什么我们需要创建NewPerson接口的原因了。

客户代码调用都需要使用PersonFactory.getPerson来获得某员工的唯一实例。比如:

1
2
3
4
5
6
public class TestNewPerson {
	public static void main(String[] args) {
		System.out.println(PersonFactory.getPerson("001"));
		System.out.println(PersonFactory.getPerson("002"));
	}
}

需要注意的是,享元对象中的属性必须是不可变的。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注