享元模式

享元模式

一、定义

摘自百度百科: 它使用共享物件,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于只是因重复而导致使用无法令人接受的大量内存的大量物件。通常物件中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。

点击查看源码


二、角色分类

抽象享元类(Flyweight)

通常是一个接口或抽象类,其声明了具体享元类的公共方法,这些方法可以向外界提供享元对象的内部数据或内部状态,同时也可以通过这些方法来设置外部数据或外部状态

具体享元类(Concrete Flyweight)

它实现了抽象享元类,它的实例被称为享元对象;在具体享元类内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。

非共享具体享元类(Unshared Concrete Flyweight)

不能被共享的子类可被设计为非共享具体享元类,当需要一个非共享具体享元类的对象时可以直接通过实例化创建

享元工厂类(Flyweight Factory)

该类主要用于创建和管理享元对象,它针对抽象享元类变成,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储键值对的集合(也可以是其他类型的集合),可以结合工厂模式进行设计

客户角色(Client)

具体调用方法的角色


三、实现方式

UML图

未命名文件 (1)

单纯享元模式

抽象享元类(Flyweight)

1
2
3
public interface Flyweight {
public void operate(String type);
}

具体享元类(Concrete Flyweight)和非共享具体享元类(Unshared Concrete Flyweight)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CharacterFlyWeight implements Flyweight {
// 内部状态是不变的,外部状态是变化的
// 然后通过共享不变的部分,达到减少对象数量并节约内存的目的
private String name;

/**
* 外部状态(非享元)
* @param name
*/
public CharacterFlyWeight(String name) {
this.name = name;
}

/**
* 具体享元+非享元结合
* @param type
*/
@Override
public void operate(String type) {
System.out.println("姓名 = " + name);
System.out.println("属性 = " + type);
}
}

享元工厂(Flyweight Factory)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FlyweightFactory {
// 由工厂方法产生所需要的享元对象
private Map<String, Flyweight> characterPool = new HashMap<>();

public Flyweight factory(String user) {
// 先从缓存中查找对象
Flyweight flyweight = characterPool.get(user);
if (null == flyweight) {
// 如果对象不存在则创建一个新对象
flyweight = new CharacterFlyWeight(user);
// 将新对象添加到缓存中
characterPool.put(user, flyweight);
}
return flyweight;
}
}

客户角色(Client)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Client {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweightA = factory.factory("小A");
flyweightA.operate("文静");

Flyweight flyweightB = factory.factory("小B");
flyweightB.operate("腼腆");

Flyweight flyweightC = factory.factory("小A");
flyweightC.operate("活泼");

System.out.println(flyweightA == flyweightC); //true

}
}

运行结果

1
2
3
4
5
6
7
姓名 = 小A
属性 = 文静
姓名 = 小B
属性 = 腼腆
姓名 = 小A
属性 = 活泼
true

复合享元模式

抽象享元角色(Flyweight)

1
2
3
public interface Flyweight {
public void operate(String type);
}

具体享元角色(Concrete Flyweight)和非共享具体享元角色(Unshared Concrete Flyweight)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CharacterFlyweight implements Flyweight {
// 内部状态是不变的,外部状态变化
// 通过共享不变的部分,达到减少对象数量并节约内存的目的
private String name;

/**
* 外部状态(非享元)
* @param name
*/
public CharacterFlyweight(String name) {
this.name = name;
}

/**
* 具体享元和非享元结合
* @param type
*/
@Override
public void operate(String type) {
System.out.println("姓名 = " + name);
System.out.println("属性 = " + type);
}
}

复合享元角色(Composite Concrete Flyweight)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CharacterCompositeFlyweight implements Flyweight {
private Map<String, Flyweight> files = new HashMap<>();

/**
* 增加一个新的单纯享元对象到集合里
*/
public void add(String key, Flyweight flyweight) {
files.put(key, flyweight);
}

/**
* 外部状态作为参数传入到方法中
*/
@Override
public void operate(String type) {
Flyweight flyweight;
for (String key : files.keySet()) {
flyweight = files.get(key);
flyweight.operate(type);
}
}
}

享元工厂角色(Flyweight Factory)

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
29
30
public class FlyweightFactory {
private Map<String, Flyweight> characterPool = new HashMap<>();

/**
* 复合工厂方法
* 一种用于提供单纯享元对象,另一种提供复合享元对象
*/
public Flyweight factory(List<String> compositeState) {
CharacterCompositeFlyweight compositeFlyweight = new CharacterCompositeFlyweight();
compositeState.forEach(any -> compositeFlyweight.add(any, this.factory(any)));
return compositeFlyweight;
}

/**
* 单纯工厂方法
* 提供所需要的享元对象
*/
public Flyweight factory(String user) {
// 先从缓存中查找对象
Flyweight flyweight = characterPool.get(user);
if (null == flyweight) {
// 如果对象不存在则创建一个新对象
flyweight = new CharacterFlyweight(user);
// 将新对象添加到缓存中
characterPool.put(user, flyweight);
}
return flyweight;
}

}

客户角色(Client)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Client {
public static void main(String[] args) {
List<String> compositeState = new ArrayList<String>();
compositeState.add("小A");
compositeState.add("小B");
compositeState.add("小C");
compositeState.add("小B");
compositeState.add("小A");

FlyweightFactory flyFactory = new FlyweightFactory();
Flyweight compositeFly1 = flyFactory.factory(compositeState);
Flyweight compositeFly2 = flyFactory.factory(compositeState);
compositeFly1.operate("梦游中...");

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

String user = "小A";
Flyweight fly1 = flyFactory.factory(user);
Flyweight fly2 = flyFactory.factory(user);
System.out.println("单纯享元模式是否可以共享对象:" + (fly1 == fly2)); //true
}
}

运行结果

1
2
3
4
5
6
7
8
9
姓名 = 小B
属性 = 梦游中...
姓名 = 小A
属性 = 梦游中...
姓名 = 小C
属性 = 梦游中...
---------------------------------
复合享元模式是否可以共享对象:false
单纯享元模式是否可以共享对象:true

四、应用场景

以下部分内容摘自菜鸟教程

意图: 运用共享技术有效地支持大量细粒度的对象。

主要解决: 在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

何时使用:

  1. 系统中有大量对象。
  2. 这些对象消耗大量内存。
  3. 这些对象的状态大部分可以外部化。
  4. 这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。
  5. 系统不依赖于这些对象身份,这些对象是不可分辨的。

如何解决: 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。

关键代码: 用 HashMap 存储这些对象。

应用实例:

  1. JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。
  2. 数据库的连接池。

使用场景:

  1. 系统有大量相似对象。
  2. 需要缓冲池的场景。

注意事项:

  1. 注意划分外部状态和内部状态,否则可能会引起线程安全问题。
  2. 这些类必须有一个工厂对象加以控制。

五、优缺点

优点

大大减少对象的创建,降低系统的内存,使效率提高。

缺点

提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。