java设计模式——享元模式(Flyweight Pattern)

概述:
       面向对象技术可以很好地解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致运行代价过高,带来性能下降等问题。 享元模式正是为解决这一类问题而诞生的。享元模式通过共享技术实现相同或相似对象的重用。
  • 在享元模式中可以共享的相同内容称为内部状态(IntrinsicState),而那些需要外部环境来设置的不能共享的内容称为外部状态(Extrinsic State),由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。
  • 在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)用于存储具有相同内部状态的享元对象。
  • 在享元模式中共享的是享元对象的内部状态,外部状态需要通过环境来设置。在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为细粒度对象。享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。
定义:
       享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

结构:
  • Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
  • FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合), 可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。

UML图:


场景:比如斯诺克台球,每个台球的形状大小都是一样的,只是有的颜色不一样,如果每个球都是一个独立对象,则肯定会消耗太多内存影响系统性能,为了解决这个问题,可以使用享元模式。

代码分析:
/**
 * Created by **
 *  台球类:抽象享元类
  */
public abstract class Billiards {
    private final String  TAG "test";
    private String  color;
    public Billiards(String color){
        this. color = color;
    }
    public void display(){
        Log. d( TAG, " 台球颜色: "+ this. color);
    }

}

/**
 * Created by **
 *  红色台球:具体的享元类
  */
public class redBilliards  extends Billiards {
    public redBilliards(String color) {
        super(color);
    }
}

/**
 * Created by **
 *  黑色台球:具体享元类
  */
public class blackBilliard  extends Billiards {

    public blackBilliard(String color) {
        super(color);
    }
}

/**
 * Created by **
 *  台球工厂类:享元工厂类,使用单例设计
  */
public class BilliardsFactory {
    private final String  TAG "test";
    private BilliardsFactory(){};
    private HashMap<String,Billiards>  map null ;
    private static class BilliardsFactoryHolder{
        public static BilliardsFactory  instance new BilliardsFactory();
    }
    public static BilliardsFactory getInstance(){
        return BilliardsFactoryHolder. instance;
    }

    /**
     *  @param  key
      @return  享元对象
      */
    public Billiards getBilliards(String key){
        Log. d( TAG, "getBilliards is start key :"+key);
        if ( null ==  map){
            map new HashMap<String,Billiards>();
        }
        boolean flag =  map.containsKey(key);
        Log. d( TAG, "flag : "+flag);
        if (flag){
            Billiards billiards =  map.get(key);
            return billiards;
        } else {
            if ( "red".equals(key)){
                map.put(key, new redBilliards( "red"));
            } else if ( "black".equals(key)){
                map.put(key, new blackBilliard( "black"));
            } else {
                Log. d( TAG, "not matching objects");
                return null;
            }
        }
        return  map.get(key);
    }

}

客户端调用:
//  获取享元工厂对象
BilliardsFactory instance = BilliardsFactory. getInstance();
//  获取红色台球
Billiards red = instance.getBilliards( "red");
Billiards red1 = instance.getBilliards( "red");
//  判断两个红色球是不是同一个对象
boolean isSame = red == red1;
Log. d( TAG, "isSame = "+isSame);

log输出结果:
 getBilliards is start key :red
 flag : false
 getBilliards is start key :red
 flag : true
 isSame = true

       从上述log可以看出,在获取第一个红球的时候,享元池里边没有这个对象,所以就new 了一个红色台球,并放入享元池(hashmap集合),但当第二次调用getBilliards方法的时候发现还是红球,所以直接就在享元池里边获取这个对象,因此最终这两个红色台球是用的同一个对象。

下面如果还需要表示每个台球的位置,则需要增加一个坐标类,这个类是非共享的类,相当于UnshareConcreteFlyweight
代码:
/**
 * Created by **
 *  坐标类:外部状态类( UnshareConcreteFlyweight
  */
public class Coordinates {
    private int  x;
    private int  y;

    public Coordinates( int x ,  int y){
        this. = x;
        this. = y;
    }

    public int getX() {
        return  x;
    }

    public int getY() {
        return  y;
    }

    public void setX( int x) {
        this. = x;
    }

    public void setY( int y) {
        this. = y;
    }
}

对抽象享元类做一点点修改:
/**
 * Created by yangjun on 2016/8/10.
 *  台球类:抽象享元类
  */
public abstract class Billiards {
    private final String  TAG "test";
    private String  color;
    public Billiards(String color){
        this. color = color;
    }
    public void display(Coordinates coordinates){
        Log. d( TAG, " 台球颜色: "+ this. color);
        Log. d( TAG, " 台球坐标: "+ "x = "+coordinates.getX()+ ",y = "+coordinates.getY());
    }

}

客户端代码:
//  获取享元工厂对象
BilliardsFactory instance = BilliardsFactory. getInstance();
//  获取红色台球
Billiards red = instance.getBilliards( "red");
Billiards red1 = instance.getBilliards( "red");
//  判断两个红色球是不是同一个对象
boolean isSame = red == red1;
Log. d( TAG, "isSame = "+isSame);
red.display( new Coordinates( 1, 2));
red1.display( new Coordinates( 3, 5));

log输出:
 getBilliards is start key :red
 flag : false
 getBilliards is start key :red
 flag : true
 isSame = true
 台球颜色:red
 台球坐标:x = 1,y = 2
 台球颜色:red
 台球坐标:x = 3,y = 5


优点:
  • 享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。
  • 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。

缺点:
  • 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
  • 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

适用环境:
  • 一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此,应当在多次重复使用享元对象时才值得使用享元模式。

扩展:
      一、享元模式又分为单纯享元模式和复合享元模式
  • 单纯享元模式:在单纯享元模式中,所有的享元对象都是可以共享的,即所有抽象享元类的子类都可共享,不存在非共享具体享元类。
          单纯享元模式UML图:

  • 复合享元模式:将一些单纯享元使用组合模式加以组合,可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
         复合享元模式UML图:

     二、与其它模式联用
  • 在享元模式的享元工厂类中通常提供一个静态的工厂方法用于返回享元对象,使用简单工厂模式来生成享元对象。
  • 在一个系统中,通常只有唯一一个享元工厂,因此享元工厂类可以使用单例模式进行设计。
  • 享元模式可以结合组合模式形成复合享元模式,统一对享元对象设置外部状态。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值