【Java 设计模式 · 结构型】享元模式(Flyweight Pattern)

结构型模式关注如何将现有类或对象组织一起形成更加强大的结构。

一、概念

享元模式(FlyWeight Pattern):运用共享技术有效地支持大量细粒度对象的复用。

享元模式的内外部状态:

  • 内部状态(Intrinsic State):存储在享元对象内部并且不会随着环境改变而改变的状态,内部状态可以共享。
  • 外部状态(Extrinsic State):随环境变化而变化的、不可共享的状态。通常由客户端保存,并在享元对象被创建后,需要使用的时候再传入享元对象内部。

正因为区别了内外部状态,可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以共享的,当需要的时候就将对象从享元池中取出,实现对象的复用。

通过想去出的对象注入不同的外部状态可以得到一系列相似的对象,而它们在内存中只存在一份。

二、结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pwpUQzbl-1635853614033)(C:\Users\64454\Desktop\ABC\结构型模式\FlyWeight\结构.jpg)]

  • Flyweight(抽象享元类):定义为接口或抽象类,声明了具体享元类的公共方法。
  • ConCreteFlyweight(具体享元类):实现了抽象享元类,存储内部状态。通常结合单例模式提供唯一的享元对象。
  • UnsharedConcreteFlyweight(非共享具体享元类):并非所有抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类。
  • FlyweightFactory(享元工厂类):用于创建、管理享元对象,将各种类型的享元对象存储在享元池中,一般设计为“K-V”映射集合,可结合工厂模式设计。

Flyweight(抽象享元类)

public abstract class Flyweight {
	public abstract void operation(String extrinsicState);
}

ConCreteFlyweight(具体享元类)

public class ConcreteFlyweight extends Flyweight {
    //内部状态 intrinsicState 作为成员变量,同一个享元对象的内部状态是一致的
    private String intrinsicState;
    
    public ConcreateFlyweight(String intrinsicState) {
		this.intinsicState = intrinsicState;
    }
    
    //外部状态 extrinsicState 在使用时由外部设置,不保存在享元对象中,即使是同一个对象,在每一次调用时可以传入不同的外部状态
    public void operation(String extrincState) {
		//实现业务方法
    }
}

UnsharedConcreteFlyweight(非共享具体享元类)

public class UnsharedConcreteFlyweight extends Flyweight {
    public void operation(String extrinsicState) {
		//实现业务方法
    }
}

FlyweightFactory(享元工厂类)

public class FlyweightFactory {
	//定义一个 HashMap 用于存储享元对象,实现享元池
    private HashMap flyweights = new HashMap();
    
    public Flyweight getFlyweight(String key) {
        //如果对象存在,则直接从享元池获取
        if (flyweights.containsKey(key)) {
        	return (Flyweight)flyweights.get(key);
        }
        //如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
        else {
            Flyweight fw = new ConcreteFlyweight();
            flyweights.put(key,fw);
            return fw;
        }
    }
}

三、举例

软件公司需开发围棋软件,现需对黑白子进行定义。围棋棋子大小、形状一致,为节省存储空间、提供性能,使用享元模式设计棋子类:

围棋棋子类:充当抽象享元类

public abstract class IgoChessman {
    public abstract String getColor();

    public void display(Coordinates coordinates) {
        System.out.println("棋子颜色:" + this.getColor() + "棋子位置" + coordinates.getX() + "," + coordinates.getY());
    }
}

黑色棋子类:充当具体享元类

public class BlackIgoChessman extends IgoChessman {
    @Override
    public String getColor() {
        return "黑色";
    }
}

白色棋子类:充当具体享元类

public class WhiteIgoChessman extends IgoChessman {
    @Override
    public String getColor() {
        return "白色";
    }
}

围棋棋子工厂类:充当享元工厂类,使用单例模式对其进行设计

public class IgoChessmanFactory {
    private static IgoChessmanFactory instance = new IgoChessmanFactory();
    private static HashMap pool;    //使用 HashMap 来存储享元对象,充当享元池

    private IgoChessmanFactory() {
        pool = new HashMap();
        IgoChessman black, white;
        black = new BlackIgoChessman();
        pool.put("b",black);
        white = new WhiteIgoChessman();
        pool.put("w",white);
    }

    //返回享元工厂类的唯一实例
    public static IgoChessmanFactory getInstance(){
        return instance;
    }

    //通过 Key 获取存储在 HashMap 中的享元对象
    public static IgoChessman getIgoChessman(String color) {
        return (IgoChessman) pool.get(color);
    }
}

棋子位置类:充当外部状态类

public class Coordinates {
    private int x;
    private int y;

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

    public int getX() {
        return x;
    }

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

    public int getY() {
        return y;
    }

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

测试代码:

IgoChessman black1, black2, black3, white1, white2;
IgoChessmanFactory factory;

//获取享元工厂对象
factory = IgoChessmanFactory.getInstance();

//通过享元工厂获取 3 颗黑子
black1 = factory.getIgoChessman("b");
black2 = factory.getIgoChessman("b");
black3 = factory.getIgoChessman("b");


//通过享元工厂获取 2 颗白子
white1 = factory.getIgoChessman("w");
white2 = factory.getIgoChessman("w");

//判断两颗棋子是否相同
System.out.println("判断两颗黑子是否相同:" + (black1 == black3));
System.out.println("判断两颗白子是否相同:" + (white1 == white2));

//显示棋子,同时设置棋子的坐标位置
black1.display(new Coordinates(1,2));
black2.display(new Coordinates(3,4));
black3.display(new Coordinates(1,3));
white1.display(new Coordinates(2,5));
white2.display(new Coordinates(2,4));

测试结果:
测试结果

四、特点

☯ 优点

  • 享元模式可以减少内存中对象的数量,使得相同/相似对象在内存中只保存一份。节约系统资源,提高性能。
  • 外部状态相对独立,不会影响内部状态。

☯ 缺点

  • 因需要分离内外部状态,使得系统逻辑复杂化。
  • 为使对象共享,需将部分状态外部化。

☯ 适用

  • 一个系统有啊大量相同/相似对象,造成大量内存浪费
  • 对象的大部分状态都可以外部化,可以传入对象
  • 维护享元池需要消耗系统资源,应在需要多次重复使用想元对象时使用享元模式

享元模式的注意事项和细节:

  1. 享元模式:“享”:共享,“元”:对象
  2. 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
  3. 用唯一标识码判断,如果内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable 存储
  4. 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
  5. 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们 使用享元模式需要注意的地方
  6. 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制(内部状态)
  7. 享元模式经典的应用场景是需要缓存池的场景,比如String常量池、数据库连接池

五、Integer 源码探索

//如果 Integer.valueOf(x) x 在 -128 ~ 127 之间,就使用享元模式返回

Integer x = Integer.valueOf(127);	//得到x的实例,类型Integer
Integer y = new Integer(127);		//得到y的实例,类型Integer
Integer z = Integer.valueOf(127);	//...
Integer w = new Integer(127);		//...

System.out.println(x.equals(y));	//比较大小:true
System.out.println(x == y);	// false
System.out.println(x == z);	// true
System.out.println(w == x);	// false
System.out.println(w == y);	// false
//如果 Integer.valueOf(x) x 未在 -128 ~ 127 之间,则使用new创建对象
Integer x = Integer.valueOf(200);
Integer y = Integer.valueOf(200);
System.out.println(x == y);	// false

valueOf源码:

本方法总是将 -128 ~ 127 的数值进行缓存,如果是其他值则不进行此操作。

valueOf源码

Cache缓存池(享元池):

按范围(-128 ~ 127,即low ~ high)存储缓存数值

Cache缓存池(享元池)

数组大小:
数组大小


小结:

  1. 在 valueOf 方法中,先判断数值是否在 Integer 的cache中:如果不在就创建新的Integer对象;否则,就直接从缓存池中返回
  2. valueOf 方法,部分应用享元模式
    img-GHMdja3v-1635853614037)]

Cache缓存池(享元池):

按范围(-128 ~ 127,即low ~ high)存储缓存数值

[外链图片转存中…(img-B3lniWg6-1635853614039)]

数组大小:

[外链图片转存中…(img-I2hBl0qH-1635853614040)]


小结:

  1. 在 valueOf 方法中,先判断数值是否在 Integer 的cache中:如果不在就创建新的Integer对象;否则,就直接从缓存池中返回
  2. valueOf 方法,部分应用享元模式
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值