享元模式
舉例分析1:
展示網站項目需求
小型的外包項目,給客戶A做一個產品展示網站,客戶A的朋友感覺效果不錯,也希
望做這樣的產品展示網站,但是要求都有些不同:
- 有客戶要求以新聞的形式發佈
- 有客戶人要求以博客的形式發佈
- 有客戶希望以微信公衆號的形式發佈
舉例分析2:
在大學時代,估計每個人都去圖書館借過書。借書的流程很簡單,如果書架上有這本書直接拿走,到借閱機上借閱就好了,如果沒有,可以到圖書管理處去拿一本新書。對於整個圖書館來說,書其實就是共享的,但是我們會發現其實每次借的書都是那些破舊一點的書,而不是新書,這是因爲學生太多了,如果我們每一次借書都拿出來一本新書,那整個圖書館估計會放不下,對於我們借書的流程和圖書共享的方式就是享元模式。============================================================
傳統方案解決網站展現項目
- 直接複製粘貼一份,然後根據客戶不同要求,進行定製修改
- 給每個網站租用一個空間
- 方案設計示意圖
傳統方案解決網站展現項目-問題分析
- 需要的網站結構相似度很高,而且都不是高訪問量網站,如果分成多個虛擬空間來
處理,相當於一個相同網站的實例對象很多,造成服務器的資源浪費 - 解決思路:整合到一個網站中,共享其相關的代碼和數據,對於硬盤、內存、CPU、
數據庫空間等服務器資源都可以達成共享,減少服務器資源 - 對於代碼來說,由於是一份實例,維護和擴展都更加容易
- 上面的解決思路就可以使用 享元模式 來解決
基本介紹
如果在一個系統中存在多個相同的對象,那麼只需要共享一份對象的拷貝,而不必爲每一次使用都創建新的對象。目的是提高系統性能。
- 享元模式(Flyweight Pattern) 也叫 蠅量模式: 運
用共享技術有效地支持大量細粒度的對象 - 常用於系統底層開發,解決系統的性能問題。像
數據庫連接池,裏面都是創建好的連接對象,在
這些連接對象中有我們需要的則直接拿來用,避
免重新創建,如果沒有我們需要的,則創建一個
3) 享元模式能夠解決重複對象的內存浪費的問題,
當系統中有大量相似對象,需要緩衝池時。不需
總是創建新對象,可以從緩衝池裏拿。這樣可以
降低系統內存,同時提高效率 - 享元模式經典的應用場景就是池技術了,String常
量池、數據庫連接池、緩衝池等等都是享元模式
的應用,享元模式是池技術的重要實現方式
享元模式的原理類圖
1)享元工廠(Llibrary):用於創建具體享元類,維護相同的享元對象。當請求對象已經存在時,直接返回對象,不存在時,在創建對象。在例子中的解釋就是圖書館,保存了所有的書,當學生借書時,有就拿走,沒有買一本新書。這裏面其實是使用了單例模式的。
2) FlyWeight 是抽象的享元角色, 他是產品的抽象類, 同時定義出對象的外部狀態和內部狀態(後面介紹) 的接口或實現
3) ConcreteFlyWeight 是具體的享元角色,是具體的產品類,實現抽象角色定義相關業務
4) UnSharedConcreteFlyWeight 是不可共享的角色,一般不會出現在享元工廠
代碼編寫:
FlyWeight :定義需要共享的對象業務接口。享元類被創建出來總是爲了實現某些特定的業務邏輯.
/**
* 定義抽象享元類(Book)
*
* 抽象享元(Book):定義需要共享的對象業務接口。享元類被創建出來總是爲了實現某些特定的業務邏輯.
*/
public interface Book {
public void borrow();
}
ConcreteFlyWeight :實現抽象享元類的接口,完成某一具體邏輯。
/**
* @author 孫一鳴 on 2020/2/13
* 具體享元(ConcreteBook):實現抽象享元類的接口,完成某一具體邏輯。在這裏表示可以被借出。
*
*/
public class ConcreteBook implements Book{
//被借出的書名
public String name;
public ConcreteBook(String name) {
this.name = name;
}
@Override
public void borrow() {
System.out.println("圖書館借出一本書,書名爲:"+this.name);
}
}
享元工廠(Llibrary):圖書館,保存了所有的書,當學生借書時,有就拿走,沒有買一本新書。
/**
* 享元工廠
*
* @author 孫一鳴 on 2020/2/13
*/
public class Library {
//圖書館維護一個圖書列表
private Map<String,Book> bookPools = new HashMap<String, Book>();
private static Library libraryFactory = new Library();
//圖書館只有一個
public static Library getInstance(){
return libraryFactory;
}
//圖書館外借圖書
public Book libTo(String bookname){
Book order = null;
//如果書架 有,取出這本書
if(bookPools.containsKey(bookname)){
order = bookPools.get(bookname);
}
else {
order = new ConcreteBook(bookname);
bookPools.put(bookname,order);
}
return order;
}
//圖書館書架上書的數量
public int getAllBooks(){
return bookPools.size();
}
}
學生類:
/**
* @author 孫一鳴 on 2020/2/13
*/
public class Student {
private static List<Book> books =new ArrayList<>();
private static Library library;
private static void studentBorrow(String name){
books.add(library.libTo(name));
}
public static void main(String[] args) {
library = Library.getInstance();
studentBorrow("JAVA編程思想");
studentBorrow("JAVA核心卷一");
studentBorrow("JAVA從入門到精通");
System.out.println("後兩本沒學會,又借一次");
studentBorrow("JAVA核心卷一");
studentBorrow("JAVA從入門到精通");
//把每一本書借出去
for (Book book: books){
book.borrow();
}
System.out.println("學生一共借了"+books.size());
System.out.println("圖書館實際借出"+library.getAllBooks());
}
}
結果截圖:
內部狀態和外部狀態
比如圍棋、五子棋、跳棋,它們都有大量的棋子對象,圍棋和五子棋只有黑白兩色,跳棋顏色多一
點,所以棋子顏色就是棋子的內部狀態;而各個棋子之間的差別就是位置的不同,當我們落子後,
落子顏色是定的,但位置是變化的,所以棋子座標就是棋子的外部狀態
- 享元模式提出了兩個要求:細粒度和共享對象。這裏就涉及到內部狀態和外部狀態
了,即將對象的信息分爲兩個部分:內部狀態和外部狀態 - 內部狀態指對象共享出來的信息,存儲在享元對象內部且不會隨環境的改變而改變
- 外部狀態指對象得以依賴的一個標記,是隨環境改變而改變的、不可共享的狀態。
- 舉個例子:圍棋理論上有361個空位可以放棋子,每盤棋都有可能有兩三百個棋子對象產生,因爲內存空間有限,一臺服務器很難支持更多的玩家玩圍棋遊戲,如果用享元模式來處理棋子,那麼棋子對象就可以減少到只有兩個實例,這樣就很好的解決了對象的開銷問題
享元模式的注意事項和細節
- 在享元模式這樣理解,“享”就表示共享,“元”表示對象
- 系統中有大量對象,這些對象消耗大量內存,並且對象的狀態大部分可以外部化時,我們就可以考慮選用享元模式
- 用唯一標識碼判斷,如果在內存中有,則返回這個唯一標識碼所標識的對象,用
HashMap/HashTable存儲 - 享元模式大大減少了對象的創建,降低了程序內存的佔用,提高效率
- 享元模式提高了系統的複雜度。需要分離出內部狀態和外部狀態,而外部狀態具有固化特性,不應該隨着內部狀態的改變而改變,這是我們使用享元模式需要注意的地方.
- 使用享元模式時,注意劃分內部狀態和外部狀態,並且需要有一個工廠類加以控制。
- 享元模式經典的應用場景是需要緩衝池的場景,比如 String常量池、數據庫連接池
享元模式的優點:
(1)節省內存空間,對於可重複的對象只會被創建一次,對象比較多時,就會極大的節省空間。
(2)提高效率,由於創建對象的數量減少,所以對系統內存的需求也減小,使得速度更快,效率更高。
享元模式的缺點
其實對於享元類有內部狀態和外部狀態,其區分就是圖書館的書一部分可以外借(外部狀態),一部分不可外借(內部狀態),兩個狀態的劃分對於書籍管理來說優點複雜化了。
享元模式與單例模式的區別
(1)享元設計模式是一個類有很多對象,而單例是一個類僅一個對象。
(2)享元模式是爲了節約內存空間,提升程序性能,而單例模式則主要是出於共享狀態的目的。