Flink的介紹
享元模式又稱蠅量模式或者羽量模式,屬於結構型模式;是指以共享的方式高效的支援大量細粒度物件的複用。它通過共享已經存在的物件來大幅度減少需要建立的物件數量、避免大量相似類的開銷,從而提高系統資源的利用率。
享元物件能做到共享的關鍵是區分內蘊狀態( Internal State)和外蘊狀態(External State)。
內蘊狀態是儲存在享元物件內部的,並且是不會隨環境改變而有所不同的。因此,一個享元可以具有得內蘊狀態並可以共享。
外蘊狀態是隨環境的改變而改變的、不可以共享的狀態。享元物件的外蘊狀態必須由客戶端儲存,並在享元物件被建立之後,在需要使用的時候再傳入到享元物件內部。
外蘊狀態不可以影響享元物件的內蘊狀態。換句話說,它們是相互獨立的。
根據所涉及的享元物件的內部表象不同,享元模式可以分成單純享元模式和複合享元模式兩種模式。
單純享元模式
在單純享元模式中,所有的享元物件都是可以共享的。類圖如下圖所示:
單純享元模式涉及到抽象享元角色、具體享元角色、享元工廠角色、客戶端角色以下四種角色:
- 抽象享元(Flyweight)角色:此角色是所有的具體享元類的超類,為這些類規定出需要實現的公共介面。那些需要外蘊狀態的操作可以通過呼叫商業方法以引數形式傳入。
- 具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定的介面。如果有內蘊狀態的話,必須負責為內蘊狀態提供儲存空間。享元物件的內蘊狀態必須與物件所處的周圍環境無關,從而使得享元物件可以在系統內共享的。
- 享元工廠(FlyweightFactory〉角色:本角色負責建立和管理享元角色。本角色必須保證享元物件可以被系統適當地共享。當一個客戶端物件呼叫一個享元物件的時候,享元工廠角色會檢查系統中是否已經有一個複合要求的享元物件。如果己經有了,享元工廠角色就應當提供這個已有的享元物件;如果系統中沒有一個適當的享元物件的話,享元工廠角色就應當建立一個合適的享元物件。
- 客戶端((Client)角色:本角色需要維護一個對所有享元物件的引用。
咖啡攤例子
在一個咖啡攤(Coffee Stall)所使用的系統裡,有一系列的咖啡“風味(Flavor)”。客人到攤位上購買咖啡,所有的咖啡均放在臺子上,客人自己拿到咖啡後就離開攤位。咖啡有內蘊狀態,也就是咖啡的風味;咖啡沒有環境因素,也就是說沒有外蘊狀態。
如果系統為每一杯咖啡都建立一個獨立的物件的話,那麼就需要創建出很多的細小物件來。這樣就不如把咖啡按照種類(即“風味”)劃分,每一種風味的咖啡只建立一個物件,並實行共享。
使用咖啡攤主的語言來講,所有的咖啡都可按“風味”劃分成如Capucino、Espresso等,每一種風味的咖啡不論賣出多少杯,都是全同、不可分辨的。所謂共享,就是咖啡風味的共享,製造方法的共享等。因此,享元模式對咖啡攤來說,就意味著不需要為每一份單獨調製。攤主可以在需要時,一次性地調製出足夠一天出售的某一種風味的咖啡。
單純享元模式例子的UML類圖:
抽象享元角色:
package com.charon.flyweight.simple;
/**
* @className: Order
* @description: 抽象享元角色
* @author: charon
* @create: 2022-03-23 21:44
*/
public abstract class Order {
/**
* 提供咖啡
*/
public abstract void serve();
/**
* 返回咖啡的口味
* @return
*/
public abstract String getFlavor();
}
具體享元角色:
package com.charon.flyweight.simple;
/**
* @className: Flavor
* @description: 具體享元角色
* @author: charon
* @create: 2022-03-23 21:47
*/
public class Flavor extends Order{
/**
* 咖啡口味
*/
private String flavor;
/**
* 內蘊狀態以引數方式傳入
* @param flavor
*/
public Flavor(String flavor) {
this.flavor = flavor;
}
@Override
public void serve() {
System.out.println("提供的咖啡口味:" + flavor);
}
@Override
public String getFlavor() {
return this.flavor;
}
}
享元工廠角色:
package com.charon.flyweight.simple;
/**
* @className: FlavorFactory
* @description: 享元工廠角色
* @author: charon
* @create: 2022-03-23 21:49
*/
public class FlavorFactory {
private Flavor[] flavors = new Flavor[10];
private int orderMade = 0;
private int totalFlavors = 0;
/**
* 根據口味提供咖啡
* @param flavor
* @return
*/
public Order getOrder(String flavor){
if(orderMade > 0){
for (int i = 0; i < orderMade; i++) {
if(flavor.equalsIgnoreCase(flavors[i].getFlavor())){
return flavors[i];
}
}
}
flavors[orderMade] = new Flavor(flavor);
totalFlavors++;
return flavors[orderMade++];
}
/**
* 返回建立過的風味咖啡的個數
* @return
*/
public int getTotalFlavorsMade(){
return totalFlavors;
}
}
客戶端:
package com.charon.flyweight.simple;
/**
* @className: Client
* @description: 客戶端
* @author: charon
* @create: 2022-03-23 19:52
*/
public class Client {
/**
* 賣出的咖啡總數
*/
private static Order[] flavors = new Flavor[20];
private static int orderMade = 0;
private static FlavorFactory factory;
public static void main(String[] args) {
// 建立風味工廠物件
factory = new FlavorFactory();
// 建立咖啡物件
takeOrders("Black Coffee");
takeOrders("Capucino");
takeOrders("Espresso");
takeOrders("Espresso");
takeOrders("Capucino");
takeOrders("Capucino");
takeOrders("Black Coffee");
// 將建立的咖啡賣給客人
for (int i = 0; i < orderMade; i++) {
flavors[i].serve();
}
System.out.println("賣出的咖啡總數:" + factory.getTotalFlavorsMade());
}
/**
* 提供咖啡
* @param flavor
*/
private static void takeOrders(String flavor) {
flavors[orderMade++] = factory.getOrder(flavor);
}
}
列印:
提供的咖啡口味:Black Coffee
提供的咖啡口味:Capucino
提供的咖啡口味:Espresso
提供的咖啡口味:Espresso
提供的咖啡口味:Capucino
提供的咖啡口味:Capucino
提供的咖啡口味:Black Coffee
賣出的咖啡總數:3
從上面的列印可以看出,雖然咖啡攤提供了7種咖啡,但是所有的咖啡口味卻只有三種。
複合享元模式
複合享元模式是將一些單純享元模式使用組合模式加以複合形成的。這樣的複合享元物件本身不能共享,但是他們可以分解成單純享元物件,而後者可以共享。複雜享元模式的類圖如下:
複合享元模式所涉及的角色有抽象享元角色、具體享元角色、複合享元角色、享元工廠角色,以及客戶端角色五種角色:
- 抽象享元(Flyweight)角色:此角色是所有的具體享元類的超類,為這些類規定出需要實現的公共介面。那些需要外蘊狀態的操作可以通過方法的引數傳入。抽象享元的介面使得享元變得可能,但是並不強制子類實行共享,因此並非所有的享元物件都是可以共享的
- 具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定的介面。如果有內蘊狀態的話,必須負責為內蘊狀態提供儲存空間。享元物件的內蘊狀態必須與物件所處的周圍環境無關,從而使得享元物件可以在系統內共享。有時候具體享元角色又叫做單純具體享元角色,因為複合享元角色是由單純具體享元角色通過複合而成的。
- 複合享元(UnsharableFlyweight)角色:複合享元角色所代表的物件是不可以共享的,但是一個複合享元物件可以分解成為多個本身是單純享元物件的組合。複合享元角色又稱做不可共享的享元物件。
- 享元工廠(FlyweightFactory)角色:本角色負責建立和管理享元角色。本角色必須保證享元物件可以被系統適當地共享。當一個客戶端物件請求一個享元物件的時候,享元工廠角色需要檢查系統中是否已經有一個符合要求的享元物件,如果已經有了,享元工廠角色就應當提供這個已有的享元物件;如果系統中沒有一個適當的享元物件的話,享元工廠角色就應當建立一個新的合適的享元物件。
- 客戶端(Client)角色:本角色需要自行儲存所有享元物件的外蘊狀態。
咖啡屋例子
還是上面的咖啡攤的例子,隨著業務的擴大,店家將咖啡攤升級為咖啡屋了。在屋子裡提供了很多桌子供客人坐,系統除了需要提供咖啡的口味外,還需要跟蹤咖啡被送到哪一桌上。於是,咖啡就有了桌子作為外蘊狀態了。
複合享元模式的UML類圖:
抽象享元角色:
package com.charon.flyweight.composite;
/**
* @className: Order
* @description: 抽象享元模式
* @author: charon
* @create: 2022-03-23 21:44
*/
public abstract class Order {
/**
* 提供咖啡
*/
public abstract void serve(Table table);
/**
* 返回咖啡的口味
* @return
*/
public abstract String getFlavor();
}
具體享元角色:
package com.charon.flyweight.composite;
/**
* @className: Flavor
* @description: 具體享元角色
* @author: charon
* @create: 2022-03-23 21:47
*/
public class Flavor extends Order {
/**
* 咖啡口味
*/
private String flavor;
/**
* 內蘊狀態以引數方式傳入
* @param flavor
*/
public Flavor(String flavor) {
this.flavor = flavor;
}
/**
* 將咖啡賣給客人並備註客人的座位號
* @param table
*/
@Override
public void serve(Table table) {
System.out.println("提供的咖啡口味:" + flavor + " 桌位在:" + table.getNumber());
}
@Override
public String getFlavor() {
return this.flavor;
}
}
享元工廠角色程式碼不變。
複合享元角色:
package com.charon.flyweight.composite;
/**
* @className: Table
* @description:
* @author: charon
* @create: 2022-03-23 22:11
*/
public class Table {
/**
* 桌子號碼
*/
private int number;
public Table(int number) {
this.number = number;
}
/**
* Gets the value of number
*
* @return the value of number
*/
public int getNumber() {
return number;
}
/**
* Sets the number
*
* @param number number
*/
public void setNumber(int number) {
this.number = number;
}
}
客戶端:
package com.charon.flyweight.composite;
/**
* @className: Client
* @description: 客戶端
* @author: charon
* @create: 2022-03-23 19:52
*/
public class Client {
/**
* 賣出的咖啡總數
*/
private static Order[] flavors = new Flavor[20];
private static int orderMade = 0;
private static FlavorFactory factory;
public static void main(String[] args) {
// 建立風味工廠物件
factory = new FlavorFactory();
// 建立咖啡物件
takeOrders("Black Coffee");
takeOrders("Capucino");
takeOrders("Espresso");
takeOrders("Espresso");
takeOrders("Capucino");
takeOrders("Capucino");
takeOrders("Black Coffee");
// 將建立的咖啡賣給客人,並將享元物件的外蘊狀態賦值給享元物件
for (int i = 0; i < orderMade; i++) {
flavors[i].serve(new Table(i));
}
System.out.println("賣出的咖啡總數:" + factory.getTotalFlavorsMade());
}
/**
* 提供咖啡
* @param flavor
*/
private static void takeOrders(String flavor) {
flavors[orderMade++] = factory.getOrder(flavor);
}
}
列印:
提供的咖啡口味:Black Coffee 桌位在:0
提供的咖啡口味:Capucino 桌位在:1
提供的咖啡口味:Espresso 桌位在:2
提供的咖啡口味:Espresso 桌位在:3
提供的咖啡口味:Capucino 桌位在:4
提供的咖啡口味:Capucino 桌位在:5
提供的咖啡口味:Black Coffee 桌位在:6
賣出的咖啡總數:3
享元模式的主要優點是:
- 相同物件只要儲存一份,這降低了系統中物件的數量,從而降低了系統中細粒度物件給記憶體帶來的壓力。
其主要缺點是:
- 為了使物件可以共享,需要將一些不能共享的狀態外部化,這將增加程式的複雜性。
- 讀取享元模式的外部狀態會使得執行時間稍微變長。
享元模式的應用場景
當系統中多處需要同一組資訊時,可以把這些資訊封裝到一個物件中,然後對該物件進行快取,這樣,一個物件就可以提供給多處需要使用的地方,避免大量同一物件的多次建立,降低大量記憶體空間的消耗。
享元模式其實是工廠方法模式
的一個改進機制,享元模式同樣要求建立一個或一組物件,並且就是通過工廠方法模式生成物件的,只不過享元模式為工廠方法模式增加了快取這一功能。
下面分析它適用的應用場景。享元模式是通過減少記憶體中物件的數量來節省記憶體空間的,所以以下幾種情形適合採用享元模式。
- 系統中存在大量相同或相似的物件,這些物件耗費大量的記憶體資源。
- 大部分的物件可以按照內部狀態進行分組,且可將不同部分外部化,這樣每一個組只需儲存一個內部狀態。
- 由於享元模式需要額外維護一個儲存享元的資料結構,所以應當在有足夠多的享元例項時才值得使用享元模式。