享元模式
模式介紹
享元模式可以理解為一個共享池的概念,即將一個對象緩存起來,下次再用的時候直接在緩存中獲取,這樣就不用重新創建對象,達到了節省內存、優化程序效率的優點。比如我們常用的String 和 數據庫的連接池都是運用了該模式的思想。
應用場景
當程序中需要大量的細粒度對象,這些對象內容相似,並且可以按照兩種狀態區分的時候,就可以使用該模式進行設計。
優缺點
-
優點
- 由於創建的對象都被緩存了起來,所以在此請求相同的對象的時候,就不用在重新創建對象,直接從緩存中獲取該對象即可。
- 節省了內存開銷和程序的效率。
-
缺點
- 增加系統的理解難度。
- 需要將對象分為兩種狀態並且根據這兩種狀態(內部、外部)來控制是否進行對象的創建。
- 需要維護一個共享池,可以理解為工廠模式的實現。
例子介紹
比如我們現在有一個需求,需要在一塊畫布上隨機位置,展示一個圓,這個圓分為四種顏色,分別為 “紅、藍、黃、綠” , 如果使用普通的做法來做就是,客戶端發起請求,服務端接收后,根據請求的顏色去創建相應的圓形對象,然后賦予坐標。
package cn.hsh.study.flyweight.ordinary;
/**
* @author shaohua
* @date 2021/4/22 19:41
*/
public class Circular {
private String color;
private int x;
private int y;
public Circular(String color, int x, int y) {
this.color = color;
this.x = x;
this.y = y;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
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;
}
@Override
public String toString() {
return "Circular{" +
"color='" + color + '\'' +
", x=" + x +
", y=" + y +
'}';
}
}
package cn.hsh.study.flyweight.ordinary;
/**
* @author shaohua
* @date 2021/4/22 19:41
*/
public class Client {
public static void main(String[] args) {
Circular red = new Circular("red", 1, 10);
System.out.println(red);
}
}
結果如下:
綜上我們可以看到,客戶端每次請求的時候都會創建一個新的對象,如果請求了1000次紅色圓形那么就會創建1000個圓形對象出來,這個可以發現,除了坐標不同,1000個對象顏色是一樣的,這里我們就可以將 顏色 和 坐標,分為內部狀態和外部狀態,內部狀態為顏色可以進行共享但是不可以修改,外部狀態就是坐標,不可以共享,但是可以隨着客戶端的調用而修改。 看代碼 ↓
package cn.hsh.study.flyweight;
/**
* @author shaohua
* @date 2021/4/20 19:40
*/
public class Circular {
private String color;
private int x;
private int y;
public String getColor() {
return color;
}
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;
}
public Circular(String color) {
this.color = color;
}
@Override
public String toString() {
return "Circular{" +
"color='" + color + '\'' +
", x=" + x +
", y=" + y +
'}';
}
}
首先定義實體對象類,這個可以繼承圖形抽象類,但是我這里為了節省就直接寫死了一個類。
package cn.hsh.study.flyweight.factory;
import cn.hsh.study.flyweight.Circular;
import java.util.HashMap;
import java.util.Map;
/**
* @author shaohua
* @date 2021/4/20 19:41
*/
public class FlyWeightFactory {
private static Map<String, Circular> pools = new HashMap<String, Circular>(16);
public static Circular factory(String color, int x, int y){
Circular circular;
if(pools.containsKey(color)){
System.out.println("檢測到共享池中存在該顏色直接返回");
circular = pools.get(color);
} else {
circular = new Circular(color);
}
circular.setX(x);
circular.setY(y);
pools.put(color, circular);
return circular;
}
}
注意 這里是整個享元模式的核心,也就是我們上面說的工廠類。每次請求的時候使用內部狀態(顏色)為key,然后外部狀態根據客戶端可以隨意修改。最后返回。
package cn.hsh.study.flyweight;
import cn.hsh.study.flyweight.factory.FlyWeightFactory;
import java.util.LinkedList;
import java.util.List;
/**
* @author shaohua
* @date 2021/4/19 18:45
*/
public class Client {
public static void main(String[] args) {
Circular red = FlyWeightFactory.factory("red", 1, 10);
Circular blue = FlyWeightFactory.factory("blue", 2, 20);
Circular yellow = FlyWeightFactory.factory("yellow", 3, 30);
Circular green = FlyWeightFactory.factory("green", 4, 40);
Circular red1 = FlyWeightFactory.factory("red", 5, 50);
Circular blue1 = FlyWeightFactory.factory("blue", 6, 60);
List<Circular> circulars = new LinkedList<Circular>();
circulars.add(red);
circulars.add(blue);
circulars.add(yellow);
circulars.add(green);
circulars.add(red1);
circulars.add(blue1);
for (Circular circular : circulars) {
System.out.println(circular.toString());
}
System.out.println(red == red1);
System.out.println(blue == blue1);
}
}
這是客戶端調用,直接看結果 ↓
可以看到,每個顏色的圓形在第一次調用的時候都會緩存到共享池中,第二次調用的時候返回的對象是共享池中被創建好了的對象,只是修改了坐標(x, y) 屬性而已,對象還是同一個。所以就做到了不用每次請求都去創建一個對象,即節省了內存的開支,也優化了程序的效率。
個人觀點
該模式在單線程下可以正常使用,一旦用在並發高的需求上可能會在客戶端賦予外部狀態的時候出現並發問題,所以該模式需要謹慎使用。
總結
-
在以下情況下可以使用享元模式
- 一個系統有大量相同或者相似的對象,由於這類對象的大量使用,造成內存的大量耗費;
對象的大部分狀態都可以外部化,可以將這些外部狀態傳入對象中(細粒度對象);
使用享元模式需要維護一個存儲享元對象的享元池,而這需要耗費資源,因此,應當在多次重復使用享元對象時才值得使用享元模式。
- 一個系統有大量相同或者相似的對象,由於這類對象的大量使用,造成內存的大量耗費;
-
模式的優點
- 它可以極大減少內存中對象的數量,使得相同對象或相似對象在內存中只保存一份;
- 享元模式的外部狀態相對獨立,而且不會影響其內部狀態,從而使得享元對象可以在不同的環境中被共享。
-
模式的缺點
- 享元模式使得系統更加復雜,需要分離出內部狀態和外部狀態,這使得程序的邏輯復雜化;
- 為了使對象可以共享,享元模式需要將享元對象的狀態外部化,而讀取外部狀態使得運行時間變長。