侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 292 篇文章
  • 累计创建 132 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

JAVA设计模式之享元模式

孔子说JAVA
2019-11-20 / 0 评论 / 0 点赞 / 82 阅读 / 6,354 字 / 正在检测是否收录...

1、享元模式(Flyweight)的定义

如果在一个系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必为每一次使用都创建新的对象。目的是提高系统性能。采用一个共享来避免大量拥有相同内容对象的开销。这种开销中最常见、直观的就是内存的损耗。享元模式运用共享技术(共享池)有效地支持大量细粒度对象的复用(减少对象数量),通常与工厂模式一起使用。

  • 为了减少不必要内存消耗,将多个对同一对象的访问集中起来,不必为每个访问者创建一个单独的对象,以此来降低内存的消耗。在项目中出现很多相同或类似的对象,享元模式会创建一个享元池将这些公共的实例保存在享元池中。可以针对的创建不同的对象,然后通过复用的方式进行分配。需要的时候就将对应的对象取出,不需要则放回。
  • 享元模式使用了单例模式。

  • 享元模式与单例模式的区别 : 享元设计模式是一个类有很多对象,而单例是一个类仅一个对象。享元模式是为了节约内存空间,提升程序性能,而单例模式则主要是出于共享状态的目的。

2、模式(Flyweight)优缺点

享元模式是一种结构型设计模式。其主要优缺点如下:

优点:

  1. 减少对象的创建,降低内存中对象的数量,降低系统的内存,提高效率
  2. 减少内存之外的其他资源占用
  3. 外部状态不会影响内部状态,可以在不同环境下进行共享。

缺点:

  1. 关注内/外状态,关注线程安全问题
  2. 为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
  3. 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

3、模式(Flyweight)适用环境

  1. 常常应用于系统底层的开发,以便解决系统的性能问题
  2. 系统有大量相似对象,需要缓冲池的场景
  3. 一个系统有大量细粒度化的对象,占据大量的内存。
  4. 对象大部分属性可以外部化,并且能将外部的属性放入内部属性中来。
  5. 使用享元模式需要维护享元池,所以要用那种常用的经常调用的对象可以使用享元模式。

4、模式(Flyweight)的结构

单纯享元模式

image-1649383092876

复合享元模式

image-1649383098217

享元模式包含如下角色:

将享元模式分为:单纯享元模式和复合享元模式

  • 单纯享元模式包括抽象享元、具体享元、享元工厂、客户端角色;
  • 复合享元模式包括抽象享元、具体享元、复合享元、享元工厂、客户端角色。
  1. Flyweight (抽象享元) :一般是接口或者抽象类,定义了享元类的公共方法。这些方法可以分享内部状态的数据,也可以调用这些方法修改外部状态。
  2. ConcreteFlyweight(具体享元类) :具体享元类实现了抽象享元类的方法,为享元对象开辟了内存空间来保存享元对象的内部数据,同时可以通过和单例模式结合只创建一个享元对象。
  3. ConcreteCompositeFlyweight(复合享元角色/非共具体享元类) :并不是所有的享元类都需要被共享的,有的享元类就不要被共享,可以通过享元类来实例一个非共享享元对象。
  4. Flyweight(享元工厂类) :享元工厂类创建并且管理享元类,享元工厂类针对享元类来进行编程,通过提供一个享元池来进行享元对象的管理。一般享元池设计成键值对,或者其他的存储结构来存储。当客户端进行享元对象的请求时,如果享元池中有对应的享元对象则直接返回对应的对象,否则工厂类创建对应的享元对象并保存到享元池。
  5. 客户端角色 :维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。

5、模式(Flyweight)的应用实例

5.1 单纯享元模式实例

抽象享元角色类有一个外蕴状态

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

具体享元角色类ConcreteFlyweight有一个内蕴状态 ,它的值应当在享元对象被创建时赋予。

  • 所有的内蕴状态在对象创建之后,就不会再改变了。
  • 如果一个享元对象有外蕴状态的话,所有的外部状态都必须存储在客户端,在使用享元对象时,再由客户端传入享元对象。

这里只有一个外蕴状态,operation()方法的参数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) {
        // TODO Auto-generated method stub
        System.out.println("Intrinsic State = " + this.intrinsicState);
        System.out.println("Extrinsic State = " + state);
    }
}

享元工厂角色类 ,必须指出的是,客户端不可以直接将具体享元类实例化,而必须通过一个工厂对象,利用一个factory()方法得到享元对象。

  • 一般而言,享元工厂对象在整个系统中只有一个,因此也可以使用单例模式。

当客户端需要单纯享元对象的时候,需要调用享元工厂的factory()方法,并传入所需的单纯享元对象的内蕴状态,由工厂方法产生所需要的享元对象。

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);  // state内蕴状态
            //把这个新的Flyweight对象添加到缓存中
            files.put(state, fly);
        }
        return fly;
    }
}

public class Client {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        FlyweightFactory factory = new FlyweightFactory();
        Flyweight fly = factory.factory(new Character('a'));
        fly.operation("First Call");
        
        fly = factory.factory(new Character('b'));
        fly.operation("Second Call");
        
        fly = factory.factory(new Character('a'));
        fly.operation("Third Call");
    }

}

/**
虽然客户端申请了三个享元对象,但是实际创建的享元对象只有两个,这就是共享的含义。运行结果如下:
Intrinsic State = a
Extrinsic State = First Call
Intrinsic State = b
Extrinsic State = Second Call
Intrinsic State = a
Extrinsic State = Third Call
*/

5.2复合享元模式实例

在单纯享元模式中,所有的享元对象都是单纯享元对象,也就是说都是可以直接共享的。还有一种较为复杂的情况,将一些单纯享元使用合成模式加以复合,形成复合享元对象。这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。

抽象享元角色类

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) {
        // TODO Auto-generated method stub
        System.out.println("Intrinsic State = " + this.intrinsicState);
        System.out.println("Extrinsic State = " + state);
    }
 
}

复合享元对象是由单纯享元对象通过复合而成的,因此它提供了add()这样的聚集管理方法。由于一个复合享元对象具有不同的聚集元素,这些聚集元素在复合享元对象被创建之后加入,这本身就意味着复合享元对象的状态是会改变的,因此复合享元对象是不能共享的。

复合享元角色实现了抽象享元角色所规定的接口,也就是operation()方法,这个方法有一个参数,代表复合享元对象的外蕴状态。一个复合享元对象的所有单纯享元对象元素的外蕴状态都是与复合享元对象的外蕴状态相等的;而一个复合享元对象所含有的单纯享元对象的内蕴状态一般是不相等的,不然就没有使用价值了。

public class ConcreteCompositeFlyweight implements Flyweight {
    
    private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
    /**
     * 增加一个新的单纯享元对象到聚集中
     */
    public void add(Character key , Flyweight fly){
        files.put(key,fly);
    }
    /**
     * 外蕴状态作为参数传入到方法中
     */
    @Override
    public void operation(String state) {
        Flyweight fly = null;
        for(Object o : files.keySet()){
            fly = files.get(o);
            fly.operation(state);
        }
    }
}

享元工厂角色提供两种不同的方法 ,一种用于提供单纯享元对象,另一种用于提供复合享元对象。

public class FlyweightFactory {
    private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
    /**
     * 复合享元工厂方法
     */
    public Flyweight factory(List<Character> compositeState){
        ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight();
        
        for(Character state : compositeState){
            compositeFly.add(state,this.factory(state));
        }
        
        return compositeFly;
    }
    /**
     * 单纯享元工厂方法
     */
    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) {
        List<Character> compositeState = new ArrayList<Character>();
        compositeState.add('a');
        compositeState.add('b');
        compositeState.add('c');
        compositeState.add('a');
        compositeState.add('b');
        
        FlyweightFactory flyFactory = new FlyweightFactory();
        Flyweight compositeFly1 = flyFactory.factory(compositeState);
        Flyweight compositeFly2 = flyFactory.factory(compositeState);
        compositeFly1.operation("Composite Call");
        
        System.out.println("---------------------------------");        
        System.out.println("复合享元模式是否可以共享对象:" + (compositeFly1 == compositeFly2));
        
        Character state = 'a';
        Flyweight fly1 = flyFactory.factory(state);
        Flyweight fly2 = flyFactory.factory(state);
        System.out.println("单纯享元模式是否可以共享对象:" + (fly1 == fly2));
    }
}

/**
运行结果如下:
Intrinsic State = b
Extrinsic State = Composite Call
Intrinsic State = c
Extrinsic State = Composite Call
Intrinsic State = a
Extrinsic State = Composite Call
---------------------------------
复合享元模式是否可以共享对象:false
单纯享元模式是否可以共享对象:true
*/

从运行结果可以看出:

  1. 一个复合享元对象的所有单纯享元对象元素的外蕴状态都是与复合享元对象的外蕴状态相等的。即外运状态都等于Composite Call。
  2. 一个复合享元对象所含有的单纯享元对象的内蕴状态一般是不相等的。即内蕴状态分别为b、c、a。
  3. 复合享元对象是不能共享的。即使用相同的对象compositeState通过工厂分别两次创建出的对象不是同一个对象。
  4. 单纯享元对象是可以共享的。即使用相同的对象state通过工厂分别两次创建出的对象是同一个对象。
0

评论区