1. 程式人生 > >「補課」進行時:設計模式(21)——享元模式

「補課」進行時:設計模式(21)——享元模式

![](https://cdn.geekdigging.com/DesignPatterns/java_design_pattern.jpg) ## 1. 前文彙總 [「補課」進行時:設計模式系列](https://www.geekdigging.com/category/%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f/) ## 2. 享元模式 ### 2.1 定義 享元模式(Flyweight Pattern)很簡單,它解決的需求也很直接,同時它也是池技術的重要實現方式,先看下它的定義: Use sharing to support large numbers of fine-grained objects efficiently.(使用共享物件可有效地支援大量的細粒度的物件。) ### 2.2 通用類圖 ![](https://cdn.geekdigging.com/DesignPatterns/21/Flyweight_UML.png) - Flyweight 抽象享元角色:它是一個產品的抽象類, 同時定義出物件的外部狀態和內部狀態的介面或實現。 - ConcreteFlyweight 具體享元角色:具體的一個產品類, 實現抽象角色定義的業務。 - unsharedConcreteFlyweight 不可共享的享元角色:不存在外部狀態或者安全要求(如執行緒安全) 不能夠使用共享技術的物件, 該物件一般不會出現在享元工廠中。 - FlyweightFactory 享元工廠:它的職責非常簡單, 就是構造一個池容器, 同時提供從池中獲得物件的方法。 ### 2.3 通用程式碼 抽象享元角色: ```java public abstract class Flyweight { // 內部狀態 private String intrinsic; // 外部狀態 protected final String extrinsic; // 要求享元角色必須接受外部狀態 protected Flyweight(String extrinsic) { this.extrinsic = extrinsic; } // 定義業務操作 abstract void operate(); public String getIntrinsic() { return intrinsic; } public void setIntrinsic(String intrinsic) { this.intrinsic = intrinsic; } } ``` 具體享元角色: ```java public class ConcreteFlyweight1 extends Flyweight{ protected ConcreteFlyweight1(String extrinsic) { super(extrinsic); } @Override void operate() { } } public class ConcreteFlyweight2 extends Flyweight{ protected ConcreteFlyweight2(String extrinsic) { super(extrinsic); } @Override void operate() { } } ``` 享元工廠: ```java public class FlyweightFactory { // 定義一個池容器 private static HashMap pool = new HashMap<>(); // 享元工廠 public static Flyweight getFlyweight(String Extrinsic) { // 需要返回的物件 Flyweight flyweight = null; // 在池中沒有該物件 if(pool.containsKey(Extrinsic)) { flyweight = pool.get(Extrinsic); } else { // 根據外部狀態建立享元物件 flyweight = new ConcreteFlyweight1(Extrinsic); // 放置到池中 pool.put(Extrinsic, flyweight); } return flyweight; } } ``` ### 2.4 優缺點 享元模式是一個非常簡單的模式, 它可以大大減少應用程式建立的物件, 降低程式記憶體的佔用, 增強程式的效能, 但它同時也提高了系統複雜性, 需要分離出外部狀態和內部狀態, 而且外部狀態具有固化特性, 不應該隨內部狀態改變而改變, 否則導致系統的邏輯混亂。 ## 3. 一個小例子 享元模式很簡單,上面的通用程式碼其實就是一個很好的示例,類似於 Java 中的 String 常量池,沒有的物件建立後存在池中,若池中存在該物件則直接從池中取出。 我這裡還是再舉一個簡單的例子,比如接了我一個小型的外包專案,是做一個產品展示網站,後來他的朋友們也希望做這樣的網站,但要求都有些不同,我們當然不能直接複製貼上再來一份,有人希望是視訊站,有人希望是圖文站等等,而且因為經費原因不能每個網站租用一個空間。 這種事情在生活中很長見,不過大多數情況都是直接 copy 一份程式碼,再做做改動,但是在享元模式中,就不存在這種情況啦~~~ 網站抽象類: ```java public abstract class WebSite { abstract void use(); } ``` 具體網站類: ```java public class ConcreteWebSite extends WebSite { private String name; public ConcreteWebSite(String name) { this.name = name; } @Override void use() { System.out.println("網站分類:" + name); } } ``` 網路工廠類: ```java public class WebSiteFactory { private HashMap pool = new HashMap<>(); //獲得網站分類 public WebSite getWebSiteCategory(String key) { if(!pool.containsKey(key)) { pool.put(key, new ConcreteWebSite(key)); } return pool.get(key); } //獲得網站分類總數 public int getWebSiteCount() { return pool.size(); } } ``` Client 客戶端: ```java public class Client { public static void main(String[] args) { WebSiteFactory factory = new WebSiteFactory(); WebSite fx = factory.getWebSiteCategory("視訊站"); fx.use(); WebSite fy = factory.getWebSiteCategory("視訊站"); fy.use(); WebSite fz = factory.getWebSiteCategory("視訊站"); fz.use(); WebSite fa = factory.getWebSiteCategory("圖文站"); fa.use(); WebSite fb = factory.getWebSiteCategory("圖文站"); fb.use(); WebSite fc = factory.getWebSiteCategory("圖文站"); fc.use(); System.out.println("網站分類總數為:" + factory.getWebSiteCount()); } } ``` 執行結果: ```java 網站分類:視訊站 網站分類:視訊站 網站分類:視訊站 網站分類:圖文站 網站分類:圖文站 網站分類:圖文站 網站分類總數為:2 ``` 可以看出,雖然我們做了 6 個網站,但網站分類只有 2 個。 這樣基本算是實現了享元模式的共享物件的目的,但是這裡實際上沒有體現物件間的不同。 我們再加入一個使用者類: ```java public class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } ``` 然後對 `WebSite` 和 `ConcreteWebSite` 的 `use()` 方法進行修改,新增 User 引數: ```java public abstract class WebSite { abstract void use(User user); } public class ConcreteWebSite extends WebSite { private String name; public ConcreteWebSite(String name) { this.name = name; } @Override void use(User user) { System.out.println("網站分類:" + name + " 使用者:" + user.getName()); } } ``` 最後修改一下 Client 類: ```java public class Client { public static void main(String[] args) { WebSiteFactory factory = new WebSiteFactory(); WebSite fx = factory.getWebSiteCategory("視訊站"); fx.use(new User("tom")); WebSite fy = factory.getWebSiteCategory("視訊站"); fy.use(new User("cat")); WebSite fz = factory.getWebSiteCategory("視訊站"); fz.use(new User("nginx")); WebSite fa = factory.getWebSiteCategory("圖文站"); fa.use(new User("apache")); WebSite fb = factory.getWebSiteCategory("圖文站"); fb.use(new User("netty")); WebSite fc = factory.getWebSiteCategory("圖文站"); fc.use(new User("jboss")); System.out.println("網站分類總數為:" + factory.getWebSiteCount()); } } ``` 最終結果: ```java 網站分類:視訊站 使用者:tom 網站分類:視訊站 使用者:cat 網站分類:視訊站 使用者:nginx 網站分類:圖文站 使用者:apache 網站分類:圖文站 使用者:netty 網站分類:圖文站 使用者:jboss 網站分類總數為:2 ``` 這樣就可以協調內部與外部狀態,哪怕接手了上千個網站的需求,只要要求相同或類似,實際開發程式碼也就是分類的那