享元模式

1.享元模式是什么

1.百度百科

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

2.维基百科

In computer programming, flyweight is a software design pattern. A flyweight is an object that minimizes memory usage by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory. Often some parts of the object state can be shared, and it is common practice to hold them in external data structures and pass them to the objects temporarily when they are used.

A classic example usage of the flyweight pattern is the data structures for graphical representation of characters in a word processor. It might be desirable to have, for each character in a document, a glyph object containing its font outline, font metrics, and other formatting data, but this would amount to hundreds or thousands of bytes for each character. Instead, for every character there might be a reference to a flyweight glyph object shared by every instance of the same character in the document; only the position of each character (in the document and/or the page) would need to be stored internally.

Another example is string interning.

In other contexts the idea of sharing identical data structures is called hash consing.

3.lz理解

一个对象如果被大量创建则十分消耗内存。那么我们可以吧其中通用的部分存储到一个一个通用的对象中。不通用的属性由各个对象自己维护。

这里解释下所谓的通用部分和不通用部分

1.通用部分(内部状态、内蕴状态)

在享元对象内部不随外界环境改变而改变的共享部分。
内部属性通常来说应该具备一下几条特性。

1.内部状态存储于对象内部。

2.内部状态可以被一些对象共享。

3.内部状态独立于具体的场景,通常不会改变。

2.不通用部分(外部状态、外蕴状态)

外部状态取决于具体的业务场景,并根据场景而变化,不可以共享的状态。一个外部状态与另一个外部状态之间是相互独立的。

由于享元模式区分了内部状态和外部状态,所以我们可以通过设置不同的外部状态使得相同的对象可以具备一些不同的特性,而内部状态设置为相同部分。在我们的程序设计过程中,我们可能会需要大量的细粒度对象来表示对象,如果这些对象除了几个参数不同外其他部分都相同,这个时候我们就可以利用享元模式来大大减少应用程序当中的对象。如何利用享元模式呢?这里我们只需要将他们少部分的不同的部分当做参数移动到类实例的外部去,然后再方法调用的时候将他们传递过来就可以了。

4.核心角色

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

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

享元工厂角色(FlyweightFactory):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计。

客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。

当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),类似JDK中的常量池,返回新创建的实例并将其存储在享元池中。

2.享元模式解决了什么问题

可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。

3.享元模式用法

一个山寨版的String常量池
抽象享元角色,里面有一个可用于注入外部属性的方法。

/**
 * 抽象享元角色
 */
public interface Flyweight {
	// 一个示意性方法,参数state是外蕴状态
	public void operation(String state);
}

具体享元角色类



public class ConcreteFlyweight implements Flyweight {
  //内部字符
	private Character intrinsicState = null;

	/**
	 * 构造函数,内蕴状态作为参数传入
	 *
	 * @param state
	 */
	public ConcreteFlyweight(Character state) {
		this.intrinsicState = state;
	}

	/**
	 * 外蕴状态作为参数传入方法中,改变方法的行为, 但是并不改变对象的内蕴状态。
	 */
	@Override
	public void operation(String state) {
		Object obj = new Object();
		// TODO Auto-generated method stub
		System.out.println("内部状态:内容" + this.intrinsicState+" 引用地址:"+Integer.toHexString(hashCode()));
		System.out.println("外部状态:内容" + state);
	}
}

享元工厂实例

public class FlyweightFactory {

	private Map<Character, Flyweight> files = new HashMap<Character, Flyweight>();

	public Flyweight factory(Character state) {
		// 先从缓存中查找对象
		Flyweight fly = files.get(state);
		if (fly == null) {
			// 如果对象不存在则创建一个新的Flyweight对象
			fly = new ConcreteFlyweight(state);
			// 把这个新的Flyweight对象添加到缓存中
			files.put(state, fly);
		}
		return fly;
	}
}

客户端调用

public class Client {

	public static void main(String[] args) {

		FlyweightFactory factory = new FlyweightFactory();
		Flyweight fly1 = factory.factory(new Character('a'));
		fly1.operation("First Call");

		Flyweight fly2 = factory.factory(new Character('b'));
		fly2.operation("Second Call");

		Flyweight fly3 = factory.factory(new Character('a'));
		fly3.operation("Third Call");

		System.out.println(fly1 == fly3);

		System.out.println(fly1 == fly2);
	}

}

结果:

内部状态:内容a 引用地址:33909752
外部状态:内容First Call

内部状态:内容b 引用地址:55f96302
外部状态:内容Second Call

内部状态:内容a 引用地址:33909752
外部状态:内容Third Call

true

false

第一个和第三个使用的对象一样,所以我们是达到目标了。

4.享元模式的问题

1、由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。

2、为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

5.享元模式总结

使用场景:
1.对象的属性可以拆分
2.对象的变量是一部分可以变动另外

碎碎念:
享元模式其实使用的非常广泛。只要对象处于反复创建的环境中,并且每个对象有部分属性是共通的那么我们就能使用享元模式。符合这种场景的比如string的常量池,就是享元模式。还有jDK中的类文件放在方法区也是享元模式的一种。在互联网应用中通常我们描述的叫XX池的东西都是使用的享元模式。

引用

https://segmentfault.com/a/1190000006128940

posted @ 2018-02-12 10:17  枫飘雪落  阅读(299)  评论(0编辑  收藏  举报