一、概念
1、定义:减少创建对象的数量,减少内存占用,从而改善应用所需的对象结构的方式(即改善性能)。运用了共享技术有效地支持大量细粒度的对象。(复用对象的思想)
2、类型:结构型,英文名FlyWeight
(最轻量级)。
3、适用场景
- 常常应用于系统底层的开发,以便解决系统性能问题。系统中对象较多时,会导致内存溢出,可以把共同的部分抽象出来。
- 如果系统中有大量的相似对象,需要使用缓冲池的场景。例如一个系统中有大量的细粒度的对象,并且这些对象中有大部分可以外部化。(享元模式的外部化和内部化)。
- 在有足够多的享元对象可供共享时,才值得我们使用这个模式,享元模式就是复用对象的思想。如果这个对象的复用度很低,那没必要使用这个模式。
4、优缺点
- 优点
- 减少对象创建,降低了堆内存中对象的数量,提高系统性能。
- 减少内存之外的其他资源的占用(减少new关键字生成实例的次数,减少了程序运行时间,提高执行速度)。
- 缺点
- 关注内部/外部状态,关注线程安全问题。为了使用享元对象,大部分都是
HashMap
,是线程不安全的,如果为了线程安全去使用HashTable
的话会得不偿失,里面有同步锁。 - 使系统、程序的逻辑复杂化了,使用享元对象提高了系统的复杂度,要分离出外部状态和内部状态,而且外部状态是不应该随着内部状态的变化而变化的,否则系统就混乱了。
- 关注内部/外部状态,关注线程安全问题。为了使用享元对象,大部分都是
5、扩展
内部状态
:可以共享的状态,在享元对象的内部,不会随着外部环境的改变而改变。外部状态
:不可以共享的状态,在享元对象的外部,随着外部环境的改变而改变。
二、Coding
业务场景:网站要求各个部门做年底总结报告,如果这些报告都生成过了就没必要再new一个。
//Employee接口,所有经理都要作报告
public interface Employee {
//做报告
void report();
}
//经理类
public class Manager implements Employee {
private String department; //管理者所在部门,对于manager实体来说是外部状态,依赖于外部传入的参数
private String reportContent; //作报告的内容
//new管理者需要知道在哪个部门,而报告内容在外部设置就行
public Manager(String department) {
this.department = department;
}
public void setReportContent(String reportContent) {
this.reportContent = reportContent;
}
@Override
public void report() {
System.out.println(reportContent);
}
}
//工厂类
public class EmployeeFactory {
//容器,map中key为部门,value为对应的员工
private static final Map<String, Employee> EMPLOYEE_MAP = new HashMap<>();
//从EMPLOYEE_MAP容器中获取对应的Manager,入参为department
public static Employee getManager(String department) {
Manager manager = (Manager) EMPLOYEE_MAP.get(department);
//如果没有,就直接new一个,首先系统要创建经理,然后再创建报告,都创建好后放进对象池中,下次如果再让他作报告,直接从对象池中拿
if (manager == null) {
manager = new Manager(department);
System.out.println("创建部门经理:"+department);
String reportContent = department + "部门汇报:此次报告的主要内容是...";
manager.setReportContent(reportContent);
System.out.println(" 创建报告" + reportContent);
EMPLOYEE_MAP.put(department, manager);
}
return manager;
}
}
//测试类
public class Test {
private static final String departments[] = {"RD", "QA", "PM", "BD"};
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
//在数组中随机取
String department = departments[(int) (Math.random() * departments.length)];
Manager manager = (Manager) EmployeeFactory.getManager(department);
manager.report();
}
}
}
//测试结果,带空格的都是首次创建
创建部门经理:QA
创建报告QA部门汇报:此次报告的主要内容是...
QA部门汇报:此次报告的主要内容是...
QA部门汇报:此次报告的主要内容是...
QA部门汇报:此次报告的主要内容是...
创建部门经理:RD
创建报告RD部门汇报:此次报告的主要内容是...
RD部门汇报:此次报告的主要内容是...
创建部门经理:PM
创建报告PM部门汇报:此次报告的主要内容是...
PM部门汇报:此次报告的主要内容是...
QA部门汇报:此次报告的主要内容是...
PM部门汇报:此次报告的主要内容是...
RD部门汇报:此次报告的主要内容是...
PM部门汇报:此次报告的主要内容是...
QA部门汇报:此次报告的主要内容是...
EmployeeFactory
和Employee
是组合关系,EmployeeFactory
的getManager
返回值是Employee
,然后它来创建对应的Manager
实例,Manager
对应的实现是Employee
。
关于内部状态与外部状态
public class Manager implements Employee {
//管理者所在部门,对于manager实体来说是外部状态,依赖于外部传入的参数
private String department;
//如果我们像下面这样写就是内部状态,manager的title是不变的,它不随外部状态(比如department)的变化而变化
private String title = "部门经理";
}
假设要用程序画圆形,如果这个半径是固定好的,那就是内部状态,如果半径需要在应用层传入的话,就是外部状态。
三、源码
Ingeger
类的valueOf()
方法,其中IntegerCatch
的范围为-128~127。
public static Integer valueOf(int i) {
//i的范围在-128~127,这个范围的数java会进行缓存,下一次使用,就会直接从缓存中取出来而不是new Integer对象
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
//当这个数字没有进入if里边就要new Integer对象
return new Integer(i);
}
//测试代码
public class Test {
public static void main(String[] args) {
Integer a = Integer.valueOf(100);
Integer b = 100;
System.out.println("a == b:" + (a == b)); //true
Integer c = Integer.valueOf(1000);
Integer d = 1000;
System.out.println("c == d:" + (c == d)); //false
}
}
a
和b
是相等的,且是同一个对象,因为这个100刚好在缓存的范围,c
和d
虽然数字一样,但不是同一个对象,因为d
是创建出来的新的Integer对象。同理Long
类型也是有LongCatch
。