­

圖解Java設計模式之享元模式

  • 2020 年 3 月 26 日
  • 筆記

展示網站項目需求

小型的外包項目,給客戶 A 做一個產品展示網站,客戶 A 的朋友感覺效果不錯,也希望做這樣的產品展示網 站,但是要求都有些不同:

  1. 有客戶要求以新聞的形式發布
  2. 有客戶人要求以部落格的形式發布
  3. 有客戶希望以微信公眾號的形式發布

傳統方案解決網站展現項目

  1. 直接複製粘貼一份,然後根據客戶不同要求,進行訂製修改
  2. 給每個網站租用一個空間
  3. 方案設計示意圖

傳統方案解決網站展現項目 – 問題分析

1)需要的網站結構相似度很高,而且都不是高訪問量網站,如果分成多個虛擬空間來處理,相當於一個相同網站的實例對象很多,造成伺服器的資源浪費。 2)解決思路 :整合到一個網站中,共享其相關的程式碼和數據,對於硬碟、記憶體、CPU、資料庫空間等伺服器資源都可以達成共享,減少伺服器資源。 3)對於程式碼來說,由於是一份實例,維護和擴展都更加方便 4)解決上面問題的思路可以使用享元模式

享元模式基本介紹

1)享元模式(Flyweight Pattern)也叫蠅量模式 :運用共享技術有效地支援大量細粒度的對象 2)常用於系統底層開發,解決系統的性能問題。像資料庫連接池,裡面都是創建好的連接對象,在這些連接對象中有我們需要的則直接拿來用,避免重寫創建,如果沒有我們需要的,則創建一個 3)享元模式能夠解決重複對象的記憶體浪費的問題,當系統中有大量相似對象,需要緩衝池時。不需總是創建新對象,可以從緩衝池裡拿。這樣可以降低系統記憶體,同時提高效率。 4)享元模式經典的應用場景就是池技術了,String常量池、資料庫連接池、緩衝池等等都是享元模式的應用,享元模式是池技術的重要實現方式。

享元模式原理類圖

說明 : 1)FlyWeight是抽象的享元角色,它是產品的抽象類,同時定義出對象的外部狀態和內部狀態的介面或實現。 2)ConcreteFlyWeight是具體的享元角色,是具體的產品類,實現抽象角色定義相關業務 3)UnSharedConcreteFlyWeight是不可共享的角色,一般不會出現在享元工廠 4)FlyWeightFactory享元工廠類,用於構建一個池容器(集合),同時提供從池中獲取對象方法

內部狀態和外部狀態

比如圍棋、五子棋、跳棋,它們都有大量的棋子對象,圍棋和五子棋只有黑白兩色,跳棋顏色多一點,所以棋子顏色就是棋子的內部狀態;而各個棋子之間的差別就是位置的不同,當我們落子後,落子顏色是定的,但位置是變化的,所以棋子坐標就是棋子外部狀態 1)享元模式提出了兩個要求 :細粒度和共享對象。這裡就涉及到內部狀態和外部狀態了,即將對象的資訊分為兩個部分 :內部狀態和外部狀態。 2)內部狀態指對象共享出來的資訊,存儲在享元對象內部且不會隨環境的改變而改變。 3)外部狀態指對象得以依賴的一個標記,是隨環境改變而改變的、不可共享的狀態。 4) 舉個例子:圍棋理論上有361個空位可以放棋子,每盤棋都有可能有兩三百個棋子對象產生,因為記憶體空間有 限,一台伺服器很難支援更多的玩家玩圍棋遊戲,如果用享元模式來處理棋子,那麼棋子對象就可以減少到只有兩個實例,這樣就很好的解決了對象的開銷問題。

享元模式解決網站展現項目

1)應用實例要求 使用享元模式完成,前面提出的網站外包問題 2)類圖

3)程式碼實現

package com.example.demo.flyweight;    public abstract class WebSite {    	/**  	 * 抽象方法  	 */  	public abstract void use(User user);  }  package com.example.demo.flyweight;    public class ConcreteWebSite extends WebSite{    	/**  	 * 網站發布的形式(類型)  	 */  	private String tyep;    	/**  	 * 構造器  	 * @param type  	 */  	public ConcreteWebSite(String type) {  		// TODO Auto-generated constructor stub  		this.tyep = type;  	}    	@Override  	public void use(User user) {  		// TODO Auto-generated method stub  		System.out.println("網站的發布形式為 :" + tyep + " 在使用中 。。 使用者是 " + user.getName());  	}    }  package com.example.demo.flyweight;    import java.util.HashMap;  import java.util.Map;    import javax.activation.MailcapCommandMap;    /**   * 網站工廠類,根據需要返回一個網站   * @author zhaozhaohai   *   */  public class WebSiteFactory {    	/**  	 * 集合,充當池的作用  	 */  	private Map<String, ConcreteWebSite> poolMap = new HashMap<String, ConcreteWebSite>();    	/**  	 * 根據網站的類型,返回一個網站,如果沒有就創建一個網站,並放入池中,並返回  	 * @param type  	 * @return  	 */  	public WebSite getWebSiteCategory(String type) {  		if (!poolMap.containsKey(type)) {  			// 就創建一個網站,並放入池中  			poolMap.put(type, new ConcreteWebSite(type));  		}  		return (WebSite)poolMap.get(type);  	}    	/**  	 * 獲取網站分類的總數(池中有多少個網站類型)  	 * @return  	 */  	public int getWebSiteCount() {  		return poolMap.size();  	}  }  package com.example.demo.flyweight;    public class User {    	private String name;    	public String getName() {  		return name;  	}    	public void setName(String name) {  		this.name = name;  	}    	public User(String name) {  		super();  		this.name = name;  	}    }  package com.example.demo.flyweight;    public class Client {    	public static void main(String[] args) {  		// TODO Auto-generated method stub  		// 創建一個工廠類  		WebSiteFactory factory = new WebSiteFactory();  		// 客戶要一個以新聞形式發布的網站  		WebSite webSite = factory.getWebSiteCategory("新聞");  		webSite.use(new User("tom"));  		// 客戶要一個以部落格形式發布的網站  		WebSite webSite2 = factory.getWebSiteCategory("部落格");  		webSite2.use(new User("jack"));  		// 客戶要一個以部落格形式發布的網站  		WebSite webSite3 = factory.getWebSiteCategory("部落格");  		webSite3.use(new User("titm"));  	}    }

享元模式在 JDK – Integer的應用源碼分析

小結 :

  1. 在valueOf方法中,先判斷值是否在IntegerCache中,如果不在,就創建新的Integer(new),否則,就從緩衝池返回。
  2. valueOf方法,就使用到享元模式
  3. 如果使用valueOf方法得到一個Integer實例,範圍在-128 – 127,執行速度比new快。

享元模式的注意事項和細節

1)在享元模式中,「享」就是表示共享,「元」表示對象 2)系統中有大量對象,這些對象消耗大量記憶體,並且對象的狀態大部分可以外部化時,我們就可以考慮選用享元模式 3)用唯一標識碼判斷,如果在記憶體中有,則返回這個唯一標識所標識的對象,用HashMap/HashTable存儲 4)享元模式大大減少了對象的創建,降低了程式記憶體的佔用,提高效率 5)享元模式提高了系統的複雜度。需要分離出內部狀態和外部狀態,而外部狀態具有固化特性,不應該隨著內部狀態的改變而改變,這是我們使用享元模式需要注意的地方。 6)使用享元模式時,注意劃分分布狀態和外部狀態,並且需要有一個工廠類加以控制。 7)享元模式經典的應用場景是需要緩衝池的場景,比如 String常量池、資料庫連接池。