Skip to content

享元模式

ZHI-XINHUA edited this page Jan 11, 2019 · 1 revision

享元模式(Flyweight)

享元模式是对象的结构模式,以共享的方式高效地支持大量的细粒度对象。

享元对象分内蕴状态和外蕴状态,外蕴状态不可以影响享元对象的内蕴状态,它们是相互独立的:

  • 内蕴状态:一个内蕴状态是存储在享元对象内部的,并且是不会随环境改变而有所不同的。因此,一个享元可以具有内蕴状态并可以共享。
  • 外蕴状态:一个外蕴状态是随着环境改变而 改变、不可以共享的状态。享元对象的外蕴状态必须是由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。

1、角色

  • 抽象享元角色(Flyweight):是多有具体享元类的超类,为这些类规定需要实现的公共接口。那些需要外蕴状态的操作可以通过调用方法以参数形式传入。
  • 享元工厂角色(FlyweightFactory):负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用 一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个复合要求的享元对象。如果已有,享元工厂角色就应当提供这个已有 的享元对象;如果没有,就创建一个合适的享元对象。
  • 具体享元角色(ConcreteFlyweight):实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与 对象所处的环境无关,从而使得享元对象可以在系统内共享的。
  • 客户端角色(Client):本角色需要维护一个对所有享元对象的引用。本角色需要自行存储所有共享对象的外蕴状态。

2、demo

uml:

(1)Flyweight 抽象享元角色

/**
 * 抽象享元角色(Flyweight):是多有具体享元类的超类,为这些类规定需要实现的公共接口。那些需要外蕴状态的操作可以通过调用方法以参数形式传入
 */
public abstract  class Flyweight {

    /**
     * 一个示意的方法,参数outState是外蕴状态
     * @param outState
     */
    abstract public void operation(String outState);
}

(2)ConcreteFlyweight具体享元角色

/**
 * 具体享元角色(ConcreteFlyweight):实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与
 * 对象所处的环境无关,从而使得享元对象可以在系统内共享的。
 */
public class ConcreteFlyweight extends Flyweight {
    //为内蕴状态提供存储空间
    private String innerState;

    /**
     * 构造器,内蕴状态作为参数传入
     * @param innerState
     */
    public ConcreteFlyweight(String innerState){
        this.innerState = innerState;
    }

    /**
     * 外蕴状态作为参数传入方法中,改变方法的行为,但是不改变对象的内蕴状态
     * @param outState
     */
    @Override
    public void operation(String outState) {
        System.out.println("对象:"+this+" 内蕴状态innerState:"+innerState+" 外蕴状态outState:"+outState);
    }
}

(3)FlyweightFactory 享元工厂角色:负责创建和管理享元角色

/**
 *  享元工厂角色(FlyweightFactory):负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用
 *  一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个复合要求的享元对象。如果已有,享元工厂角色就应当提供这个已有
 *  的享元对象;如果没有,就创建一个合适的享元对象
 */
public class FlyweightFactory {
    private Map<String,Flyweight> map = new HashMap<String,Flyweight>();

    public FlyweightFactory(){

    }

    /**
     *  必须指出的是,客户端不可以直接将具体享元类实例化,而必须通过一个工厂对象利用一个
     *  factory()方法得到享元对象。一般而言,享元工厂对象在整个系统中只有一个,隐藏可以是单例模式。
     * @param innerState
     * @return
     */
    public Flyweight factory(String innerState){
        if(map.containsKey(innerState)){
            return map.get(innerState);
        }
        Flyweight flyweight = new ConcreteFlyweight(innerState);
        map.put(innerState,flyweight);
        return flyweight;

    }
    
}

(4)客户端角色(Client):本角色需要维护一个对所有享元对象的引用

/**
 * Created by xh.zhi on 2018-11-6.
 * 客户端角色(Client):本角色需要维护一个对所有享元对象的引用。本角色需要自行存储所有共享对象的外蕴状态。
 */
public class Client {
    public static void main(String[] args) {
        //创建一个享元工厂对象
        FlyweightFactory factory = new FlyweightFactory();
        //向享元工厂对象请求一个内蕴状态为a的享元对象
        Flyweight fly = factory.factory("a");
        //已参数方式传入一个外蕴状态
        fly.operation("hello");


        Flyweight fly1 = factory.factory("a");
        //已参数方式传入一个外蕴状态
        fly1.operation("world");

    }
}

3、享元模式开发中应用的场景:

  • 享元模式由于其共享的特性,可以在任何“池”中操作,比如:线程池、数据库连接池。
  • String类 ,String对象是不可变对象,一旦创建出来就不能改变,如果需要改变一个字符串的值,就只好创建一个新的String对象。在JVM内部,String 对象是共享的。如果系统中有两个String对象所包含的字符串相同的话,JVM实际上只创建一个String对象提供给两个引用,从而实现String对象的共享。String的intern()方法给出这个字符串再共享池中的唯一实例。

4、享元模式的优缺点

时间换空间。

优点:

  • 极大减少内存中对象的数量
  • 相同或相似对象内存中只存一份,极大的节约资源,提高系统性能
  • 外部状态相对独立,不影响内部状态

缺点:

  • 模式较复杂,使程序逻辑复杂化
  • 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态使运行时间变长。用时间换取了空间。