享元模式(实现对象的复用)
运用共享技术有效的支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,粒度变化都很小,可以实现对象的多次的复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此被称为轻量级模式,是一种对象结构性模式。
(1)内部状态:内部状态是存储在享元对象内部并且不会随着环境改变而改变的状态,内部状态可以共享,比如字符串的内容,不会随着外部变化而变化,无论在任何环境下,字符‘a’始终是‘a’,都不会改成‘b’
(2)外部状态是随着环境改变而改变的,不可以共享的状态。享元对象的外部状态通常由客户端保存,并且在享元对象被创建后,需要使用的时候,再传入到享元对象的内部。一个外部状态与另外一个之间是相互独立的。如字符的颜色,可以在不同的地方有不同的颜色,例如有的’a’是红色的,有的’a’是绿色的,字符的大小也是如此,而且大小和颜色是独立外部状态,相互之间没有影响,客户端可以在使用时将外部状态注入享元对象中。
正是因为区分了内部状态和外部状态,可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候从享元池中读取,实现对象的复用,通过取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份
角色说明
(1)Flyweight(抽象享元类)通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据,同时也可以通过这些方法来设置外部状态。
(2)ConcreteFlyweight(具体享元类)实现了抽象享元类,其实例也称为享元对象,提供了存储空间,通常,可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
(3)UnshareConcreteFlyweight(非共享具体享元类),并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可以设计成非共享具体享元类,当需要一个非共享具体享元类的对象时,可以直接通过实例化创建。
(4)FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象。针对抽象享元类编程,将各种具体类型的具体享元对象存储在一个享元池中,享元池一般设计为一个键值对的集合,可以结合工厂模式进行设计,当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已经创建的实例或者创建一个新的实例(如果不存在的话,返回新创建的实例并将其存储在享元池中)。
package com.learn.designmode.mode.flyweight;
import java.util.HashMap;
/**
* 享元工厂类
*/
public class FlyweightFactory {
// 定义一个HashMap用于存储享元对象,实现享元池
private HashMap<String,Object> flyweights = new HashMap<String,Object>(16);
public Flyweight getFlyweight(String key){
if (flyweights.containsKey(key)){
return (Flyweight) flyweights.get(key);
}else {
Flyweight flyweight = new ConcreteFlyweight("a");
flyweights.put(key,flyweight);
return flyweight;
}
}
}
/**
* 抽象享元类
*/
class Flyweight{
// 内部状态intrinsicState 作为成员变量,同一个享元对象其内部状态是一致的
private String intrinsicState;
public Flyweight(String intrinsicState){
this.intrinsicState = intrinsicState;
}
// 外部状态extrinsicState在使用时,由外部设置,不保存在享元对象中,即使是用一个对象,在每一次调用时可以传入不同的外部状态
public void operation(String extrinsicState){
}
}
/**
* 具体享元类
*/
class ConcreteFlyweight extends Flyweight{
public ConcreteFlyweight(String intrinsicState) {
super(intrinsicState);
}
// 外部状态extrinsicState在使用时,由外部设置,不保存在享元对象中,即使是用一个对象,在每一次调用时可以传入不同的外部状态
@Override
public void operation(String extrinsicState){
}
}
完整的解决方案
package com.learn.designmode.mode.flyweight.demo;
import java.util.HashMap;
/**
* 抽象享元类
*/
public abstract class IgoChessman {
abstract String getColor();
public void display(){
System.out.println("棋子颜色 " + getColor());
}
}
// 黑子
class BlackIgoChessman extends IgoChessman{
@Override
String getColor() {
return "黑色";
}
}
// 白子
class WhiteIgoChessman extends IgoChessman{
@Override
String getColor() {
return "白色";
}
}
/** 围棋棋子工厂类,享元工厂类
* 通过单例模式进行设计
*/
class FlyweightFactory{
private static FlyweightFactory flyweightFactory = new FlyweightFactory();
private HashMap<String,Object> hashMap = new HashMap<>();
private FlyweightFactory(){
IgoChessman black,white;
black = new BlackIgoChessman();
white = new WhiteIgoChessman();
hashMap.put("black",black);
hashMap.put("white",white);
}
public static FlyweightFactory getInstance(){
return flyweightFactory;
}
public IgoChessman getChessman(String key){
return (IgoChessman) hashMap.get(key);
}
}
package com.learn.designmode.mode.flyweight.demo;
public class Client {
public static void main(String[] args) {
IgoChessman black1,black2,black3,white1,white2;
FlyweightFactory flyweightFactory = FlyweightFactory.getInstance();
black1 = flyweightFactory.getChessman("black");
black2 = flyweightFactory.getChessman("black");
black3 = flyweightFactory.getChessman("black");
System.out.println("判断两颗黑子是否相同" + (black1 == black2));
white1 = flyweightFactory.getChessman("white");
white2 = flyweightFactory.getChessman("white");
System.out.println("判断两颗白字是否相同" + (black1 == black2));
// 显示棋子
black1.display();
black2.display();
black3.display();
white1.display();
white2.display();
}
}
以上并用到的内部状态为棋子的地址 ,外部状态为棋子的颜色,也只是简单的外部状态,我们可以有更复杂的外部状态
带外部状态的解决方案
package com.learn.designmode.mode.flyweight.demo;
import lombok.Data;
/**
* 坐标类
*/
@Data
public class Coordinates {
private int x;
private int y;
public Coordinates(int x ,int y){
this.x = x;
this.y = y;
}
}
package com.learn.designmode.mode.flyweight.demo;
import java.util.HashMap;
/**
* 抽象享元类
*/
public abstract class IgoChessman {
abstract String getColor();
private Coordinates coordinates;
public void display(Coordinates coordinates){
System.out.println("棋子颜色 " + getColor() + ",位置" + coordinates.getX() + "," + coordinates.getY());
}
}
// 黑子
class BlackIgoChessman extends IgoChessman{
@Override
String getColor() {
return "黑色";
}
}
// 白子
class WhiteIgoChessman extends IgoChessman{
@Override
String getColor() {
return "白色";
}
}
/** 围棋棋子工厂类,享元工厂类
* 通过单例模式进行设计
*/
class FlyweightFactory{
private static FlyweightFactory flyweightFactory = new FlyweightFactory();
private HashMap<String,Object> hashMap = new HashMap<>();
private FlyweightFactory(){
IgoChessman black,white;
black = new BlackIgoChessman();
white = new WhiteIgoChessman();
hashMap.put("black",black);
hashMap.put("white",white);
}
public static FlyweightFactory getInstance(){
return flyweightFactory;
}
public IgoChessman getChessman(String key){
return (IgoChessman) hashMap.get(key);
}
}
package com.learn.designmode.mode.flyweight.demo;
public class Client {
public static void main(String[] args) {
IgoChessman black1,black2,black3,white1,white2;
FlyweightFactory flyweightFactory = FlyweightFactory.getInstance();
black1 = flyweightFactory.getChessman("black");
black2 = flyweightFactory.getChessman("black");
black3 = flyweightFactory.getChessman("black");
System.out.println("判断两颗黑子是否相同" + (black1 == black2));
white1 = flyweightFactory.getChessman("white");
white2 = flyweightFactory.getChessman("white");
System.out.println("判断两颗白字是否相同" + (black1 == black2));
// 显示棋子
black1.display(new Coordinates(1,2));
black2.display(new Coordinates(4,4));
black3.display(new Coordinates(7,9));
white1.display(new Coordinates(8,10));
white2.display(new Coordinates(2,19));
}
}
单纯享元模式和复合享元模式
标准的享元模式结构图中既包含可以共享的具体享元类,也包含不可以共享的非共享具体享元类。但是在实际使用过程中,有时候会用到两种比较特殊的享元模式,单纯享元模式和复合享元模式。
1.单纯享元模式
单纯享元模式中,所有具体享元类都可以是共享的,不存在非共享的具体享元类,单纯享元类的结构如图所示。
我们上述内容就是单纯享元模式。
2.复合享元模式
将一些单纯享元对象使用组合模式加以组合,可以形成复合享元模式。这样的复合享元对象本身不能共享,但是他们可以分解成单纯享元对象,显然后者可以共享。结构如下
通过复合享元模式,可以确保复合享元类CompositeConcreteFlyweight中所包含的每个单纯享元类ConcreteFlyweight都具有相同的外部状态,而这些单纯享元的内部状态往往可以不同,如果希望给多个内部状态不同享元对象设置相同的外部状态,可以考虑使用复合享元模式。
关于享元模式的几点补充
1.与其他模式的联用
享元模式通常需要和其他模式一起联用,几种常见的联用方式如下:
(1)享元工厂类通常只提供一个静态工厂方法返回享元对象,一般使用简单工厂就够了(简单工厂)
(2)单例模式对享元工厂的持有唯一的实例,
(3)享元模式可以结合组合模式形成复合享元模式,统一对多个享元对象设置外部状态。
2.享元模式与String类
JDK库中的String就使用了享元模式。
总结
优点
(1)可以极大的减少内存中对象的数量,使得相同或相似对象中在内存中只保存一份,从而节省资源。
(2)享元模式的外部状态相对独立,而且不会影响其他的内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点
(1)系统将变得复杂,需要分离出内部状态和外部状态。
(2)为了使对象可以共存,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变的更长。
适用场景
(1)一个系统中存在大量相同或者相似的对象,造成内存大量耗费。
(2)对象的大部分状态都可以外部化,将这些外部状态传入对象中。
(3)在使用享元模式需要维护一个存储享元对象的享元池,需要耗费一定的资源,因此需要多次反复的使用享元对象才使用。