1. 程式人生 > >設計模式是什麼鬼(享元)

設計模式是什麼鬼(享元)

//作者:凸凹裡歐

元,始也,本初,根源之意,計算機中的二進位制“元”其實就1和0,這兩個東西組合起來有無窮無盡的可能,這便形成了計算機中的大千世界,正如“陰”和“陽”為萬物之首一樣,這也是為什麼稱其為二元。顧名思義,享元就是共享本元的意思,然而這個模式的英文叫做Flyweight,能飛起來一般的重量,輕量級的意思,“享元”其實並非意譯,但這並不影響其對這個模式的最佳詮釋。

我們來看一個例項,比如我們要開發一款RPG遊戲,遊戲地圖通常非常大,而且有各種各樣,有草地、沙漠、荒原,水路等等,在寫程式碼之前,我們先思考下應該怎樣去建模。

對於這種地圖,我們載入一整張圖片來做地圖?如果地圖太大,圖片載入相當卡頓吧?而且大片地圖上其實都是重複的圖片素材,整圖載入設計也有失靈活性。再仔細觀察下,這地圖無非就是很多小圖片(元)拼起來的哦,這不就是類似於我們裝修時貼馬賽克嘛?

這可簡單了!我們應該有個磚塊類,持有“圖片”,“位置”等屬性資訊,然後例項化這些磚塊再呼叫其“繪製”方法把圖片顯示在地圖某位置上即可。二話不說開始寫程式碼。

 1 public class Tile {
 2  private String image;//地磚所用的圖片材質
 3  private int x, y;//地磚所在座標
 4  public Tile(String image, int x, int y) {
 5  this.image = image;
 6  System.out.print("從磁碟載入[" + image + "]圖片,耗時半秒。。。");
 7  this
.x = x; 8 this.y = y; 9 } 10 public void draw() { 11 System.out.println("在位置[" + x + ":" + y + "]上繪製圖片:[" + image + "]"); 12 } 13 }

程式碼看起來非常簡單,第3行的地磚材質圖片我們用String來模擬代替,第7行初始化時我們把圖片載入到記憶體,比如說這個IO操作要耗費半秒時間,好了我們先測試繪製第一行磚塊,執行一下。

 1 public class Client {
 2  public static void main(String[] args) {
3 //以繪製第一行為例 4 new Tile("河流", 10, 10).draw(); 5 new Tile("河流", 10, 20).draw(); 6 new Tile("石路", 10, 30).draw(); 7 new Tile("草坪", 10, 40).draw(); 8 new Tile("草坪", 10, 50).draw(); 9 new Tile("草坪", 10, 60).draw(); 10 new Tile("草坪", 10, 70).draw(); 11 new Tile("草坪", 10, 80).draw(); 12 /* 執行結果 13 從磁碟載入[河流]圖片,耗時半秒。。。在位置[10:10]上繪製圖片:[河流] 14 從磁碟載入[河流]圖片,耗時半秒。。。在位置[10:20]上繪製圖片:[河流] 15 從磁碟載入[石路]圖片,耗時半秒。。。在位置[10:30]上繪製圖片:[石路] 16 從磁碟載入[草坪]圖片,耗時半秒。。。在位置[10:40]上繪製圖片:[草坪] 17 從磁碟載入[草坪]圖片,耗時半秒。。。在位置[10:50]上繪製圖片:[草坪] 18 從磁碟載入[草坪]圖片,耗時半秒。。。在位置[10:60]上繪製圖片:[草坪] 19 從磁碟載入[草坪]圖片,耗時半秒。。。在位置[10:70]上繪製圖片:[草坪] 20 從磁碟載入[草坪]圖片,耗時半秒。。。在位置[10:80]上繪製圖片:[草坪] 21 */ 22 } 23 }

有沒有發現問題?每載入一張圖都要耗費掉半秒鐘,才畫了8張地磚圖就4秒鐘流逝了,如果構建整張地圖得多少時間?這就像是在慢性自殺,如此效率嚴重影響了遊戲的使用者體驗,光卡頓在地圖載入這給漫長的過程就已經讓玩家失去興趣了。

相信大家一定想到了《設計模式是什麼鬼(原型)》模式吧?對,我們把相同的圖共享出來,用克隆的方式代替物件圖例項化的過程,從而加快初始化速度。再想想,共享元貌似沒什麼問題,速度也加快了,但物件數量貌似還是個嚴重問題,每一個小物件圖都要對應一個物件,這麼個小遊戲用得著那麼大的記憶體開銷麼,搞不好甚至會造成記憶體溢位,嗯,設計模式一定還是有問題。

沿著共享的思路我們再看下到底需不需要這麼多物件?這些物件不同的地方在於其座標的不同,再就是材質的不同,也就是圖的不同了,能不能從這些物件裡抽取出來一些共同點呢?首先每個圖的座標都不一樣,是沒辦法共享的,但是材質圖是重複出現的,是可以共享的,同樣的材質圖會在不同的座標位置上重複出現,那麼這個材質圖是可以做成共享元的。

既然座標不能共享,那就不做為材質類的共享元屬性,由客戶端維護這些座標並作為引數傳入好了,而且這些材質都有繪製能力,那就先定義一個介面吧。

1 public interface Drawable {
2 
3 void draw(int x, int y);//繪製方法,接收地圖座標。
4 
5 }

當然,我們也可以用抽象類抽出更多的屬性和方法代替介面,使子類變得簡單,這裡為了清晰說明問題就用介面。接下來是材質類們,統統實現這個繪製介面。

 1 public class Water implements Drawable {
 2 
 3 private String image;//河流圖片材質
 4 
 5 public Water() {
 6 
 7 this.image = "河流";
 8 
 9 System.out.print("從磁碟載入[" + image + "]圖片,耗時半秒。。。");
10 
11 }
12 
13 @Override
14 
15 public void draw(int x, int y) {
16 
17 System.out.println("在位置[" + x + ":" + y + "]上繪製圖片:[" + image + "]");
18 
19 }
20 
21 }

注意第6行因為是河流材質類,所以初始化我們直接載入河流圖片素材,這就是類內部即將做共享的“元”資料了,也叫做“內蘊狀態”,至於“外蘊狀態”就是座標了,只作為引數從外部傳入不做共享。接下來是草地、石子路等等。

 1 public class Grass implements Drawable {
 2  private String image;//草坪圖片材質
 3  public Grass() {
 4  this.image = "草坪";
 5  System.out.print("從磁碟載入[" + image + "]圖片,耗時半秒。。。");
 6  }
 7  @Override
 8  public void draw(int x, int y) {
 9  System.out.println("在位置[" + x + ":" + y + "]上繪製圖片:[" + image + "]");
10  }
11 }
 1 public class Stone implements Drawable {
 2  private String image;//石路圖片材質
 3  public Stone() {
 4  this.image = "石路";
 5  System.out.print("從磁碟載入[" + image + "]圖片,耗時半秒。。。");
 6  }
 7  @Override
 8  public void draw(int x, int y) {
 9  System.out.println("在位置[" + x + ":" + y + "]上繪製圖片:[" + image + "]");
10  }
11 }
 1 public class House implements Drawable {
 2  private String image;//房子圖片材質
 3  public House() {
 4  this.image = "房子";
 5  System.out.print("從磁碟載入[" + image + "]圖片,耗時一秒。。。");
 6  }
 7  @Override
 8  public void draw(int x, int y) {
 9  System.out.println("將圖層切到最上層。。。");//房子蓋在地上,所以切換到頂層圖層。
10  System.out.println("在位置[" + x + ":" + y + "]上繪製圖片:[" + image + "]");
11  }
12 }

注意上面這個的房子類有所不同,它有自己特有的繪製行為方法,也就是在地板圖層之上繪製房子,覆蓋掉下面的地板,使其變得更加立體。這也就是為什麼我們非要用介面或抽象類來做引用,使實現類可以有自己獨特的行為方式,多型的好處立竿見影。接下來就是實現“元之共享”的關鍵了,我們來做一個簡單工廠類,看程式碼。

 1  public class Factory {//圖件工廠
 2  private Map<String, Drawable> images;//相簿
 3  public Factory() {
 4  images = new HashMap<String, Drawable>();
 5  }
 6  public Drawable getDrawable(String image) {
 7  //快取裡如果沒有圖件,則例項化並放入快取。
 8  if(!images.containsKey(image)){
 9  switch (image) {
10  case "河流":
11  images.put(image, new Water());
12  break;
13  case "草坪":
14  images.put(image, new Grass());
15  break;
16  case "石路":
17  images.put(image, new Stone());
18  }
19  }
20  //快取裡必然有圖,直接取得並返回。
21  return images.get(image);
22  }
23 }

這個圖件工廠維護著所有元物件的相簿,構造方法於第5行會初始化一個雜湊圖的快取”池“,當客戶端於第8行需要例項化圖件的時候,我們先觀察這個相簿池裡存在不存在已例項化過的圖件,也就是看有無已做共享的圖元,如果沒有則例項化並加入相簿共享池供下次使用,這便是”元之共享“的祕密了。巧奪天工的設計一氣呵成,已經迫不及待去運行了。

 1 public class Client {
 2  public static void main(String[] args) {
 3  //先例項化圖件工廠
 4  Factory factory = new Factory();
 5  //以第一行為例
 6  factory.getDrawable("河流").draw(10, 10);
 7  factory.getDrawable("河流").draw(10, 20);
 8  factory.getDrawable("石路").draw(10, 30);
 9  factory.getDrawable("草坪").draw(10, 40);
10  factory.getDrawable("草坪").draw(10, 50);
11  factory.getDrawable("草坪").draw(10, 60);
12  factory.getDrawable("草坪").draw(10, 70);
13  factory.getDrawable("草坪").draw(10, 80);
14  /*執行結果
15  從磁碟載入[河流]圖片,耗時半秒。。。在位置[10:10]上繪製圖片:[河流]
16  在位置[10:20]上繪製圖片:[河流]
17  從磁碟載入[石路]圖片,耗時半秒。。。在位置[10:30]上繪製圖片:[石路]
18  從磁碟載入[草坪]圖片,耗時半秒。。。在位置[10:40]上繪製圖片:[草坪]
19  在位置[10:50]上繪製圖片:[草坪]
20  在位置[10:60]上繪製圖片:[草坪]
21  在位置[10:70]上繪製圖片:[草坪]
22  在位置[10:80]上繪製圖片:[草坪]
23  */
24  }
25 }

可以看到,我們拋棄了利用new關鍵字肆意妄為地製造物件,而是改用這個圖件工廠去幫我們把元構建並共享起來。顯而易見,我們看到執行結果中每次例項化物件會耗費半秒時間,再次請求物件時就不再會載入圖片耗費時間了,也就是從共享圖池直接拿到了,不再造次。更妙的是,如果畫完整個地圖只需要例項化需要用到的某些元素材而已,即使是那個大房子圖件也只需要例項化一次就夠了。至此,CPU速度,記憶體輕量化同時做到了優化,整個遊戲使用者體驗得到了極大的提升。

享元的精髓當然重點不止於”享“,更重要的是對於元的辨識,例如那個從外部客戶端傳入的座標引數,如果我們依然把座標也當作共享物件元資料(內蘊狀態)的話,那麼這個結構將無元可享,大量的物件就如同世界上沒有相同的兩片樹葉一樣多不勝數,最終會導致相簿池被撐爆,享元將變得毫無意義。所以,對於整個系統資料結構的分析、設計、規劃顯得尤為重要。

內外相濟,裡應外合,以不變應萬變的化繁為簡,元,萬變不離其宗,享之。

相關推薦

設計模式是什麼

//作者:凸凹裡歐 元,始也,本初,根源之意,計算機中的二進位制“元”其實就1和0,這兩個東西組合起來有無窮無盡的可能,這便形成了計算機中的大千世界,正如“陰”和“陽”為萬物之首一樣,這也是為什麼稱其為二元。顧名思義,享元就是共享本元的意思,然而這個模式的英文叫做Flyweight,能飛起來一般的重量,輕

軟體設計模式學習十五模式

> 當系統中存在大量相同或相似的物件時,享元模式是一種較好的解決方案,它通過共享技術實現相同或相似的細粒度物件的複用,從而節約記憶體空間。享元模式提供了一個享元池用於儲存已經建立好的享元物件,並通過享元工廠類將享元物件提供給客戶端使用。 ## 模式動機 使用面向物件技術開發時,很多情況下需要在系統

typescript設計模式之組合

abr .html hbm msl gfw targe bgm egg fff js%E4%B8%AD%E7%9A%84%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E5%85%A5%E9%97%A8 http://music.baidu.co

Java 設計模式系列十六觀察者模式(Observer)

for out 其中 如果 observer 業務 ets 同時 hang Java 設計模式系列(十六)觀察者模式(Observer) 觀察者模式是對象的行為模式,又叫發布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽

Java 設計模式系列二三訪問者模式(Vistor)

聚集 哪些 true 由於 clas .com concrete 為什麽 type Java 設計模式系列(二三)訪問者模式(Vistor) 訪問者模式是對象的行為模式。訪問者模式的目的是封裝一些施加於某種數據結構元素之上的操作。一旦這些操作需要修改的話,接受這個操作的數

JAVA設計模式——代理靜態代理

具體實現 使用 *** inter ali pro eal 成功 dem 定義 為其它的對象提供一種代理,以控制這個對象的訪問 使用場景 當不想直接訪問某個對象的時候,就可以通過代理 1.不想買午餐,同事幫忙帶 2.買車不用去廠裏,去4s店 3.去代理點買火車票,不用去車站

JAVA設計模式——代理動態代理

生成 底層 exe 加載器 use 有一個 隨筆 for oca   傳送門:JAVA設計模式——代理(靜態代理)   序言:   在學習Spring的時候,我們知道Spring主要有兩大思想,一個是IoC,另一個就是AOP,對於IoC,依賴註入就不用多說了,而對於Spri

PHP 流行設計模式示例經過驗證

《設計模式簡介》 設計模式:提供了一種廣泛的可重用的方式來解決我們日常程式設計中常常遇見的問題。設計模式並不一定就是一個類庫或者第三方框架,它們更多的表現為一種思想並且廣泛地應用在系統中。它們也表現為一種模式或者模板,可以在多個不同的場景下用於解決問題。設計模式可以用於加速開發,並且將很多大的想

23種設計模式十九備忘錄模式python_c++實現) .md

23種設計模式之(十九)備忘錄模式(Memento) 本文主要介紹23種設計模式之備忘錄模式,附詳細python/c++示例程式碼。 概念 應用場景 注意事項 程式碼示例 總結 程式碼連結 備忘錄模式(Memento) 概念 備忘錄模式,是行為模式之一,它

23種設計模式二十三迭代器模式python_c++實現) .md

23種設計模式之(二十三)迭代器模式(Iterator) 本文主要介紹23種設計模式之迭代器模式,附詳細python/c++示例程式碼。 概念 應用場景 注意事項 程式碼示例 總結 程式碼連結 迭代器模式(Iterator) 概念 迭代模式,是行為模式之一

【JAVA設計模式】12.模式

享元模式將物件資訊分為兩類,一個是不受物件具體資訊影響,即可共享的物件資訊,另一個是不同物件間獨立的資訊。 由於存在可共享的資訊,則可設計一個工廠類,工廠類內部儲存了大量物件,所有物件都有一個共同的資訊,例如:產品類別。 先建立一個享元介面: interface Fly

Java進階篇設計模式之七 ----- 模式和代理模式

前言 在上一篇中我們學習了結構型模式的組合模式和過濾器模式。本篇則來學習下結構型模式最後的兩個模式, 享元模式和代理模式。 享元模式 簡介 享元模式主要用於減少建立物件的數量,以減少記憶體佔用和提高效能。這種型別的設計模式屬於結構型模式,它提供了減少物件數量從而改善應用所需的物件結構的方式。 用通俗的話來

javascript設計模式-Constructor構造器模式

Constructor是一種在記憶體已分配給該物件的情況下,用於初始化新建立物件的特殊方法。Object構造器用於建立特定型別的物件–準備好物件以備使用,同事接收構造器可以使用引數,以在第一次建立物件時,設定成員屬性和方法值。 物件建立 創新新物件,在jav

設計模式筆記之模式的使用

簡介:享元模式(Flyweight Pattern)主要用於減少建立物件的數量,以減少記憶體佔用和提高效能。享元模式嘗試重用現有的同類物件,如果未找到匹配的物件,則建立新物件。 意圖:運用共享技術有效地支援大量細粒度的物件。 主要解決:在有大量物件時,有可能會造成記憶體溢

PHP設計模式系列二十三:訪問者模式

訪問者模式 表示一個作用於某物件結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。 模式結構 抽象訪問者角色(Visitor):為該物件結構(Object

JavaOO設計模式概要試用markdown

Java 面向物件設計(OOAD) OOAD 面向物件的分析與設計。 程式設計方法 軟體工程學:是指導計算機軟體開發和維護的一門工程學科。採用工程化的方法開發維護。 軟體工程三要素 方法 :完成軟體開發各項任務的技術方法

模板方法設計模式擴充套件Hook Method

package Template_Method_Pattern; import java.util.Scanner; /** * 模板方法模式的擴充套件 * 鉤子方法(Hook Method) * @author dd * */ public cl

設計模式示例C++實現

最近參加了軟考,在準備考試過程中發現其中給出設計類圖讓通過指定的設計模式進行設計的題目對於練習C++和軟體開發是一種不錯的的方式,因為學校中的C++課程只講C++語言,雖然也講了封裝、繼承、多型等特性,但是缺少相應的練習,使得沒有深入的認識和理解。同時類似於程式

設計模式之13--模式

享元模式學習筆記 對於有時候系統中需要產生大量物件的情況,會造成系統性能下降,而這些物件之間的差別可能很小。比如圍棋中的棋子(之間唯一不同的也就是座標),又或者是火車票(相同的都是始發站和目的站,不同的是票的座位)。那麼如果可以做到只儲存一份物件在系統中,

23種設計模式之_模式

設計模式的熟練掌握,能夠更容易理解系統的底層架構實現。 一、什麼是享元模式   享元模式(Flyweight Pattern):以共享的方式高效的支援大量的細粒度物件。通過複用記憶體中已存在的物件,降低系統建立物件例項的效能消耗。   享元的英文