享元模式(Flyweight Pattern),通过共享技术来实现大量细粒度的对象的复用。在系统中只需要持有少量的对象就可以实现对象的多次复用。享元模式有两种状态,分别是内部状态和外部状态,内部状态在享元对象内部,是可以共享的不变的状态,而外部状态是不可共享的,由具体的应用场景来传入相应的外部状态,享元模式通常和工厂模式一起使用。
享元模式主要有如下的几个模块:
Flyweight:抽象享元对象,为具体的享元对象定义了接口方法,外部对象通过该接口以参数的形式传入,抽象享元对象可以是接口或者抽象类。
FlyweightFactory:享元工厂,主要负责创建和管理享元角色,并提供获取享元对象的接口方法。
ConcreteFlyweight:具体的享元对象,是抽象享元对象的具体实现。
UnsharedConcreteFlyweight:非共享具体享元对象,并非所有享元对象都是可共享的,可以通过非共享享元类来创建非共享享元对象。
享元模式在实际工程中应用非常广泛,比如我们经常用到的线程池、jdbc连接池等。下面我们以围棋游戏为例,来展示一下享元模式在围棋游戏中如何减少对象的数量,从而节省计算机的内存和计算资源。
在围棋游戏中,白棋180枚,黑棋181枚,总计361枚棋子,如果我们为每个棋子都创建一个对象,那一局围棋游戏我们最多需要创建361个棋子对象,这仅仅是单机游戏需要的资源,如果是网络对战模式,会同时存在成千上万局对弈,这种时候,如果还是为每个棋子都单独创建对象,对象占据的资源将会挤爆服务器。而如果我们对棋子的属性进行分析的话,可以发现棋子之间其实是相似的对象,比如,所有的棋子都可以被加到棋盘上,同时任何棋子如果被围杀都会被移除出棋盘,这些都属于棋子的公共部分,棋子之间不同的属性主要有两个,一个是棋子的归属,是属于白方还是黑方,另一个是棋子在棋盘中的位置。这种情况下,我们只需要白方和黑方分别创建一个棋子对象即可,所有的白棋共用一个白棋对象,黑棋同理,这样仅仅通过两个棋子对象就可以持有所有棋子的状态,大大节省了计算机的资源。
下面我们来看一下样例的实现,首先我们需要定义棋子接口(抽象享元接口):
public interface Go {
void addLocation(String location);
void removeLocation(String location);
void printLocations();
}
在棋子接口中我们定义了棋子的一些公共方法,比如新增一个棋子的位置(某方玩家下子),移除一个棋子的位置(对方玩家吃子),打印棋子当前状态。
棋子实现(具体享元类):
public class ConcreteGo implements Go{
private String color;
private Set locations = new HashSet<>();
public ConcreteGo(String color) {
this.color = color;
}
@Override
public void addLocation(String location) {
locations.add(location);
}
@Override
public void removeLocation(String location) {
locations.remove(location);
}
public void printLocations(){
System.out.println(color + "方棋子布局如下:");
for(String location : locations){
System.out.println("<" + location + ">");
}
}
}
工厂类(享元工厂):
public class GoFactory {
HashMap goMap = new HashMap<>();
public Go createChessman(String color){
Go chessman = goMap.get(color);
if(chessman != null){
return chessman;
}
chessman = new ConcreteGo(color);
goMap.put(color,chessman);
return chessman;
}
}
然后我们根据上面的工厂类创建黑色棋子对象和白色棋子对象,并进行对弈,最后将对弈中的棋子情况打印出来:
public class GoMain {
public static void main(String[] args){
GoFactory goFactory = new GoFactory();
Go whiteChessman = goFactory.createChessman("白");
Go blackChessman = goFactory.createChessman("黑");
whiteChessman.addLocation("5,F");
blackChessman.addLocation("6,D");
whiteChessman.addLocation("12,B");
whiteChessman.printLocations();
blackChessman.printLocations();
}
}
打印结果如下:
白方棋子布局如下:
<12,B>
<5,F>
黑方棋子布局如下:
<6,D>
1.享元模式的优点
(1)可以极大地减少内存中的对象,使得相似或者相同的对象在实际使用中指向的是同一个对象,节省大量的内存资源
(2)由于外部状态的存在,使得享元对象在不影响内部状态的情况下在不同环境中被共享
2.享元模式的缺点
享元模式使得系统更加复杂,在对需求进行分析的时候需要从逻辑上分离内部状态和外部状态
3.享元模式的应用场景
在系统中存在大量相同或者相似的对象,且这些对象可以重复利用
参考
https://blog.csdn.net/justloveyou_/article/details/55045638
https://www.cnblogs.com/Bobby0322/p/4193941.html
https://blog.csdn.net/qq_40709468/article/details/82633928