场景
享元模式
简介
当一个软件系统在运行时产生的对象数量太多,将导致运行代价过高,带来系统性能下降等问题。
例如在一个文本字符串中存在很多重复的字符,如果每一个字符都用一个单独的对象来表示,将会占用
较多的内存空间,那么我们如何去避免系统中出现大量相同或相似的对象,同时又不影响客户端程序。
通过面向对象的方式对这些对象进行操作?
享元模式正为解决这一类问题而诞生。享元模式通过共享技术实现相同或相似对象的重用,
在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象,
这个对象可以出现在一个字符串的不同地方,相同的字符对象都指向同一个实例,在享元模式中,
存储这些共享实例对象的地方称为享元池(Flyweight Pool)。我们可以针对每一个不同的字符创建一个享元对象,
将其放在享元池中,需要时再从享元池取出。
享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区分了内部
状态(Intrinsic State)和外部状态(Extrinsic State)。
下面将对享元的内部状态和外部状态进行简单的介绍:
(1) 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。如字符的内容,
不会随外部环境的变化而变化,无论在任何环境下字符“a”始终是“a”,都不会变成“b”。
(2) 外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,
并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与另一个外部状态
之间是相互独立的。如字符的颜色,可以在不同的地方有不同的颜色,例如有的“a”是红色的,
有的“a”是绿色的,字符的大小也是如此,有的“a”是五号字,有的“a”是四号字。而且字符的颜色和大
小是两个独立的外部状态,它们可以独立变化,相互之间没有影响,客户端可以在使用时将外部状态
注入享元对象中。
正因为区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中,享元池中
的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对
象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。
享元模式结构图
享元模式角色
Flyweight(抽象享元类):
通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),
同时也可以通过这些方法来设置外部数据(外部状态)。
ConcreteFlyweight(具体享元类):
它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,
为每一个具体享元类提供唯一的享元对象。
UnsharedConcreteFlyweight(非共享具体享元类):
并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;
当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
FlyweightFactory(享元工厂类):
享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,
享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;
当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),
返回新创建的实例并将其存储在享元池中。
在享元模式中引入了享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,
首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
应用实例
通过对围棋软件进行分析,发现在围棋棋盘中包含大量的黑子和白子,它们的形状、大小都一模一样,只是出现的位置不同而已。
如果将每一个棋子都作为一个独立的对象存储在内存中,将导致该围棋软件在运行时所需内存空间较大,
如何降低运行代价、提高系统性能是一个问题。为了解决这个问题,决定使用享元模式来设计该围棋软件的棋子对象。
IgoChessman充当抽象享元类,BlackIgoChessman和WhiteIgoChessman充当具体享元类,IgoChessmanFactory充当享元工厂类。
注:
博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
实现
1、新建围棋棋子类作为抽象享元类
//围棋棋子类:抽象享元类
abstract class IgoChessman {public abstract String getColor();public void display(Coordinates coordinates){System.out.println("棋子颜色:"+this.getColor()+",棋子位置:"+coordinates.getX()+coordinates.getY());}
}
这里棋子的位置作为外部状态。
附坐标位置类
//坐标类:外部状态类
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;}
}
2、新建黑色棋子类作为具体享元类
//黑色旗子类:具体享元类
public class BlackIgoChessman extends IgoChessman{@Overridepublic String getColor() {return "黑色";}
}
3、新建白色棋子类作为具体享元类
//白色旗子类:具体享元类
public class WhiteIgoChessman extends IgoChessman{@Overridepublic String getColor() {return "白色";}
}
4、新建围棋棋子工厂类作为享元工厂类,使用单例模式进行设计
//围棋棋子工厂类:享元工厂类,使用单例模式进行设计
public class IgoChessmanFactory {private static IgoChessmanFactory instance = new IgoChessmanFactory();//使用Hashtable来存储享元对象,充当享元池private static Hashtable ht;private IgoChessmanFactory(){ht = new Hashtable();IgoChessman black,white;black = new BlackIgoChessman();ht.put("b",black);white = new WhiteIgoChessman();ht.put("w",white);}//返回享元工程类的唯一实例public static IgoChessmanFactory getInstance(){return instance;}//通过key来获取存储在Hashtable中的享元对象public static IgoChessman getIgoChessman(String color){return (IgoChessman) ht.get(color);}
}
5、客户端调用示例
public class Client {public static void main(String[] args) {IgoChessman black1,black2,black3,white1,white2;IgoChessmanFactory factory;//获取享元工厂对象factory = IgoChessmanFactory.getInstance();//通过享元工厂获取三颗黑子black1 = factory.getIgoChessman("b");black2 = factory.getIgoChessman("b");black3 = factory.getIgoChessman("b");System.out.println("判断两颗黑子是否相同:"+(black1 == black2));//通过享元工厂获取两颗白子white1 = factory.getIgoChessman("w");white2 = factory.getIgoChessman("w");System.out.println("判断两颗白子是否相同:"+(white1 == white2));//显示棋子black1.display(new Coordinates(1,1));black2.display(new Coordinates(2,1));black3.display(new Coordinates(3,2));white1.display(new Coordinates(4,3));white2.display(new Coordinates(5,2));}
}
6、总结
当系统中存在大量相同或者相似的对象时,享元模式是一种较好的解决方案,它通过共享技术实现相
同或相似的细粒度对象的复用,从而节约了内存空间,提高了系统性能。相比其他结构型设计模式,
享元模式的使用频率并不算太高,但是作为一种以“节约内存,提高性能”为出发点的设计模式,它在
软件开发中还是得到了一定程度的应用。
享元模式的主要优点如下:
(1) 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
(2) 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
主要缺点
(1) 享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
(2) 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
适用场景
(1) 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
(2) 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
(3) 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,
因此,应当在需要多次重复使用享元对象时才值得使用享元模式。