定义:
享元模式(Flyweight Pattern)指的是运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
Flyweight指的是特轻量级拳击手,最轻量级摔跤手的意思,所以享元模式也叫轻量级模式。为什么享元模式叫做Flyweight呢?Flyweight翻译过来就是让让体重飞了,不就是变轻了的意思吗?而享元模式的核心就是通过共享实例减少代码量和对象的数量,不就类似于这个意思吗?
从中文的意思来理解,享元中的享就是共享的意思,而元可以理解为数据、实例,所以享元模式的意思就是共享实例,共享实例这样就能够减少对象实例的频繁的创建了。
这样是不是就很好理解了呢?
通过享元模式的定义我们可以发现有两个重要的定义一个是细粒度对象,另一个是共享对象。细粒度对象就是指性质相近的对象,什么是性质相近呢?可以将对象的属性分为内部状态和外部状态。内部状态往往就是可以共享的相同的内容,而外部状态就是不能共享的,需要通过外部环境来设置的。
享元模式可以分成单纯享元模式和复合享元模式两种形式。
单纯享元模式指的是在单纯的享元模式中,所有的享元对象都是可以共享的。
复合享元模式指的是将一些单纯享元使用合成模式加以复合,形成复合享元对象。这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
在java中的String类型就是使用了享元模式。String对象是final类型,对象一旦创建就不可改变。在java中字符串常量都是存在常量池中的,java会确保一个字符串常量在常量池中只有一个拷贝。String a="abc",其中"abc"就是一个字符串常量。比如最常见的一个面试题:
String a = "abc"; String b = "abc"; 复制代码
a和b两个引用都指向了常量池中的同一个字符串常量"abc"。常量池的设计就避免了创建更多相同对象时所产生的不必要的大量的资源消耗。
组成部分:
通过上面的描述,可以总结出享元模式的主要组成部分有以下几点:
1、抽象享元(Flyweight):指的是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
2、具体享元(Concrete Flyweight):指的是实现抽象享元角色中所规定的接口。
3、非享元(Unsharable Flyweight):指的是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。这点是单纯享元所没有的,因为单纯享元中的对象都是可共享对象。
4、享元工厂(Flyweight Factory):在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)用于存储具有相同内部状态的享元对象。负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
栗子:
首先定义一个抽象的享元接口:
public interface Flyweight { public void operation(String state); } 复制代码
接着定义一个具体的享元角色ConcreteFlyweight并且实现Flyweight接口:
public class ConcreteFlyweight implements Flyweight{ private String intrinsicState = null; public ConcreteFlyweight(String state){ this.intrinsicState = state; } @Override public void operation(String state) { System.out.println("内部状态:" + this.intrinsicState); System.out.println("外部状态:" + state); } } 复制代码
对于复合享元还有个非享元角色:
public class UnsharedConcreteFlyweight { private String state; UnsharedConcreteFlyweight(String state) { this.state= stateinfo; } public String getState() { return state; } public void Setate(String stateinfo) { this.state= stateinfo; } } 复制代码
定义一个享元工厂角色类,一般来说享元工厂对象在整个系统中只有一个,因此也可以使用单例模式。
享元工厂可以理解为生产享元的地方,当需要享元对象实例时,就调用工厂里的方法并传入内部状态,生产享元对象。
public class FlyweightFactory { private Map<String,Flyweight> files = new HashMap<String,Flyweight>(); public Flyweight factory(String state){ Flyweight flyObject = files.get(state); if(flyObject == null){ flyObject = new ConcreteFlyweight(state); files.put(state, flyObject); } return flyObject; } } 复制代码
测试方法:
public class FlyWeigthTest { public static void main(String[] args) { FlyweightFactory factory = new FlyweightFactory(); Flyweight fly = factory.factory(new String("状态A")); fly.operation("状态A"); fly = factory.factory(new String("状态B")); fly.operation("状态B"); fly = factory.factory(new String("状态A")); fly.operation("状态C"); } } 复制代码
运行结果如下:
可以看到虽然new了三个享元对象,但是实际创建的享元对象只有两个,这就是共享的含义。
享元模式的优点
1、享元模式最大的优点在于它大幅度地降低内存中对象的数量,减少代码量。
2、享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
享元模式的缺点
1、享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
2、享元模式将享元对象的状态外部化,而读取外部状态使得运行时间变长。
应用场景:
关于享元模式的应用场景其实在它的定义已经说明的很清楚了:
1、一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费;
2、对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
3、使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此,应当在多次重复使用享元对象时才值得使用享元模式。
总结:
对于享元模式的总结就一句话,尽量通过共享实例来避免new出更多地实例,当需要某个实例时,并不总是通过new关键字来生成实例,而是尽量共用已经存在的实例。这就是享元模式的究极要义。
至此设计模式中的结构型设计模式已经全部介绍完了。