文章目录
一、简介
1.1 引入
一个类中的成员变量表明这个类所创建的对象具有的属性,而我们可能会使用程序的中某一个类创建多个对象,你们这些对象之间可能会存在一部分属性值是完全相同的。现在我们创建一个Car类,然后用这个类创建carA和carB两辆同型号的轿车他们的长宽高相同,但颜色和功率不同
我们创建的carA和carB两个实例对象,这两个对象长宽高完全相同,但他们占有不同的内存空间,所以我们用Car类实例化的对象越多,浪费的内存空间也就越多
因为他们有着不同的内存空间,那么他们就可能会修改自己的属性值,那么我们就不能保证相同型号的汽车的长宽高是一致的
我们能否考虑让Car类创建的所有实例对象共享相同的height,width和length属性呢?答案是可以的,我们可以将Car类在红的height,width和length封装到一个新的类中
这样Car类创建的若干个实例对象都只要给color和power分配不同的内存空间,让这些对象可以共享同一个carData对象
1.2 定义
享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
- 享元模式又称轻量级模式,它是一种对象结构型模式,一般会结合工厂模式一起使用
- 享元模式要求能够共享的对象必须是细粒度对象
- 系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用
外部状态和内部状态
享元模式的关键是使用一个享元对象为其他对象提供共享的状态,从而保证使用享元的对象不能更改享元的数据
享元:享元对象其实就是一个共享对象,"引入"中所提到的CarData类的实例就是一个享元对象
- 内部状态:内部状态指的是对象共享出来的信息,由享元对象所维护,不会随环境的改变而改变(不变的、可以在许多其他对象中重复使用的数据的成员变量)
- 外部状态:外部状态随环境改变而改变、不可以共享的状态。外部状态在需要使用时通过客户端传入享元对象。外部状态必须由客户端保存。( 包含每个对象各自不同的数据的成员变量)
二、模式原理
2.1 模式组成
- Flyweight(抽象享元)
是一个接口或抽象类,声明了具体享元类的公共方法。这些方法既可以向外界提供享元对象的内部数据(内部状态),也可以通过将外部数据作为参数传递给内部数据 - ConcreteFlyweight(具体享元)
实现抽象享元类,该类的实例称为享元对象,简称享元。具体享元类的成员变量就是享元对象的内部状态 - UnsharedConcreteFlyweight(非共享的具体享元)
是不可共享的外部状态,它以参数的形式注入具体享元的相关方法中 - FlyweightFactory(享元工厂)
享元工厂是一个类,该类负责创建和管理享元对象。各种类型的具体享元对象存储在一个享元池中,当用户请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象
2.2 UML类图
三、实例
3.1 实例概况
以围棋为例,围棋分为黑子和白子,黑子和白字之间有着诸多相似的属性,除去颜色外,每颗棋子不同的是在棋盘上的位置。因此,我们可以分析出围棋对象的内部状态和外部状态
内部状态:比如说,棋子的颜色,大小,质量(近似相等),制造商等等
外部状态:棋子在棋盘中的位置
3.2 步骤
- 步骤一:创建抽象享元类,定义抽象方法
//抽象享元:这是一个抽象类
public abstract class Flyweight {
// 定义了一个抽象方法,用来输出棋子的颜色和坐标
public abstract void show(Location loc);
}
- 步骤二:定义两个具体享元类,重写父类的抽象方法
//具体享元类:白棋
public class WhiteChessman extends Flyweight {
public String color = "white";
public String getColor() {
return color;
}
// 重写父类的抽象方法,将外部状态Location对象传给内部对象,一起输出
public void show(Location loc) {
// TODO Auto-generated method stub
System.out.println(getColor() + ":" + loc.getLoc());
}
}
//具体享元类:黑棋
public class BlackChessman extends Flyweight {
public String color = "black";
public String getColor() {
return color;
}
// 重写父类的抽象方法,将外部状态Location对象传给内部对象,一起输出
public void show(Location loc) {
// TODO Auto-generated method stub
System.out.println(getColor() + ":" + loc.getLoc());
}
}
- 步骤三:创建非共享的具体享元类,设置棋子的坐标
//非共享的具体享元类:设定棋子的坐标
public class Location {
public int x;
public int y;
// 设定坐标
public Location(int x, int y) {
setX(x);
setY(y);
}
// 获取棋子的坐标
public String getLoc() {
return "x:" + getX() + ",y:" + getY();
}
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;
}
}
- 步骤四:创建享元池,向池中添加两个具体享元实例
import java.util.HashMap;
//享元工厂了
public class FlyweightFactory {
// HashMap用来存放不同的具体享元实例,每个具体享元实例只有一个,可以和单例模式配合使用
public HashMap<String, Flyweight> flyweights = new HashMap<>();
public FlyweightFactory() {
// 直接向HashMap中添加两个具体享元实例
flyweights.put("white", new WhiteChessman());
flyweights.put("black", new BlackChessman());
}
// 根据传入的color,在HashMap中匹配对应颜色的棋子
public Flyweight getFlyweight(String color) {
return flyweights.get(color);
}
}
- 步骤五:调用享元工厂生产白棋和黑棋,不论生成多少白棋,在没有设置坐标前都是一样的
public class Client {
public static void main(String[] args) {
//生成一个享元工厂
FlyweightFactory factory = new FlyweightFactory();
//获取白棋
Flyweight white1 = factory.getFlyweight("white");
Flyweight white2 = factory.getFlyweight("white");
white1.show(new Location(1,2)); //给白棋传一个坐标
white2.show(new Location(2,4)); //给白棋传一个坐标
//每个白棋如果除去坐标以后,他们是完全相同的
System.out.println(white1.hashCode() == white2.hashCode());
System.out.println("--------------");
//获取黑棋
Flyweight black1 = factory.getFlyweight("black");
Flyweight black2 = factory.getFlyweight("black");
black1.show(new Location(1,2));
black2.show(new Location(3,5));
//每个黑棋如果除去坐标以后,他们是完全相同的
System.out.println(black1.hashCode() == black2.hashCode());
System.out.println("--------------");
//黑棋和白棋是不同的
System.out.println(white1.hashCode() == black1.hashCode());
}
}
//执行结果
white:x:1,y:2
white:x:2,y:4
true
--------------
black:x:1,y:2
black:x:3,y:5
true
--------------
false
3.3 UML类图
四、优缺点
4.1 优点
- 使用享元可以节省内存的开销,每个具体享元类只需创建一个实例
- 享元模式的具体享元可以使用方法的参数接受外部状态中的数据,但外部状态数据不会干扰到享元中的内部数据,使得享元对象可以在不同的环境中被共享
4.2 缺点
- 享元模式使得系统变得复杂,因为需要分离出内部状态和外部状态,这使得程序的逻辑复杂化
- 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长
五、应用场景
- 一个系统中有大量的对象,这些对象之间部分属性本质上是相同的,这时可以用享元封装相同的部分
- 对象的大部分状态都可以变为外部状态,可以将这些外部状态传入享元中
- 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式
六、与其他模式的关系
- 你可以使用享元模式实现组合模式树的共享叶节点以节省内存
- 享元展示了如何生成大量的小型对象, 外观模式则展示了如何用一个对象来代表整个子系统
- 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元就和单例模式类似了。 但这两个模式有两个根本性的不同
- 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同
- 单例对象可以是可变的。 享元对象是不可变的