# Design Patterns & Architecture Patterns ## Design Patterns ### 1. Flyweight #### 前言 Flyweight在拳擊中是最輕的量級,稱為蠅量級。有兩個名字: * 輕量模式 * 享元模式 好處是節省記憶體(RAM),功用是讓多個物件共享同一份狀態,而這份狀態會事先被儲存起來,物件需要使用的時候直接提取來用。 壞處是程式碼較難做調整,因為物件走向已經被固定而很難產生變化,程式看起來較不值觀、架構也較複雜。除此之外,物件導向原本的封裝特性消失,變為是從外部引入。 與快取記憶體的差別在,快取是以空間換取時間,將計算結果儲存在特定空間,而輕量模式是以彈性換取空間。 * Flyweight:所有具象 Flyweight 子類別的共同介面 * ConcreteFlyweight:Flyweight 的具象子類別,就是要共享的元件類別。共享元件本身有內部的資料或狀態的話,都是在這裡維護。 * FlyweightFactory:負責創建與管理共享元件 (如例子中的名片)。通常會確認共享元件是否已存在,存在就直接給 Client 用,否則就創建新的共享元件。 * Client:取得共享元件並使用的角色,通常會結合外部資訊來使用共享元件 (如例子中的員工資訊) #### 舉例 [舉例說明by iT邦幫忙](https://ithelp.ithome.com.tw/articles/10222509) 有一款遊戲會出現非常大量的類別「Chicken」,當遊戲中的小雞數量成長到一定程度,系統會因為記憶體溢出而當機,以下是小雞的類別內容 ![](https://i.imgur.com/g65vhBc.png) 其中任何一隻Chicken的isCute都是true,導致程式用到大量的重複浪費。 解決辦法就是讓這類重複性高的屬性變為另一個物件,如此一來當要使用的時候再去呼叫這個新建出來的物件就好,不再需要大量記憶體。 ![](https://i.imgur.com/QM3CWZi.png) 這時Chicken中原本有的三個屬性會變為兩種類型: * Intrinsic State(內存狀態) * 屬於被共享的狀態,屬於flyweight * 在這就是指color和isCute * Extrinsic State(外存狀態) * 屬於非共享的狀態 * 在這就是指name ![](https://i.imgur.com/K7nRNHd.png) [舉例說明by Design Pattern murmur](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwiExJiQzYnwAhV1y4sBHTwqBDUQFjABegQIBRAD&url=http%3A%2F%2Fcorrupt003-design-pattern.blogspot.com%2F2017%2F01%2Fflyweight-pattern.html&usg=AOvVaw3rSmGLNQ9MCq6XsE8yfGJH) 公司中有個儲存員工資訊的類別「Employee」,這類別中有個屬性「NameCard」擁有高重複性資訊,比方說版面設計、字體等等都一樣,只有內容差異。同樣為了避免浪費記憶體,將名片變為flyweight。 ![](https://i.imgur.com/McZyGxV.png) ##### **類別建立** ```java= // 名片是共享元件 // 提供一個介面讓子類別實作 public abstract class NameCard { // 名片要顯示的資訊 protected String mId; protected String mDept; protected String mTel; public abstract void show(Employee e); } ``` ##### **名片的一般與主管兩類** ```java= public class GeneralNameCard extends NameCard { @Override public void show(Employee e){ // 從外部資源取得資料 mId = e.getId(); mDept = e.getDept(); mTel = e.getTel(); // 顯示名片內容 System.out.println(...) } } public class ManagerNameCard extends NameCard { // 管理階層的名片會多顯示職稱 // 假設這是共享元件自己要管理的內部資源 // 這邊只是範例, 把值寫死 protected String mTitle = "Manager"; @Override public void show(Employee e){ // 從外部資源取得資料 mId = e.getId(); mDept = e.getDept(); mTel = e.getTel(); // 顯示名片內容 // 會多顯示職稱 System.out.println(...) } } ``` ##### **檢查共享的flyweight是否存在** ```java= // 創建與管理共享元件的類別 public class NameCardFactory { // 範例用 Map 來儲存已創建的共享元件 // 當然可以有不同的實作 private static Map<Font, WeakReference<NameCard>> mNameCards = new WeakHashMap<Integer, WeakReference<NameCards>>(); public static final int TYPE_GENERAL = 0; public static final int TYPE_MANAGER = 1; public static NameCard getNameCard(int employeeType){ if(employeeType == TYPE_GENERAL){ return getGeneralNameCard(); } else if(employeeType == TYPE_MANAGER){ return getManagerNameCard(); } else{ // Error hanlding. } } // 確認共享元件是否存在 // 不存在就創建並保留 private static NameCard getGeneralNameCard(){ if(mNameCards.get(TYPE_GENERAL) == null){ mNameCards.put(TYPE_GENERAL, new GeneralNameCard()); } return mNameCards.get(TYPE_GENERAL).get(); } private static NameCard getManagerNameCard(){ if(mNameCards.get(TYPE_MANAGER) == null){ mNameCards.put(TYPE_MANAGER, new GeneralNameCard()); } return mNameCards.get(TYPE_MANAGER).get(); } } ``` ##### 主程式 ```java= public class Main { public static void main(String[] args){ // 假設 Employee 相關類別已存在 Employee generalEmployee1 = new GeneralEmployee(...); // 取得共享元件 NameCard card1 = NameCardFactory.getGeneralNameCard(); card1.display(generalEmployee1); Employee generalEmployee2 = new GeneralEmployee(...); // 取得共享元件 NameCard card2 = NameCardFactory.getGeneralNameCard(); // 這邊的結果會是 true System.out.println(card1 == card2); } } ``` ### 2. [Singleton](https://skyyen999.gitbooks.io/-study-design-pattern-in-java/content/singleton.html) 被稱為單例模式,程式碼短且實行簡單,但有不少語言差異造成的小細節。 單例模式只接受物件本身的實例,因為物件的建構子設為private,因此不能在其他程式中new出來。這種寫法因為一開始就已經建立物件,因此也稱為貪婪單例模式(Greed Singleton)。 ##### 簡單格式範例 ```java= public class SingletonGreed { // 一開始就建立物件,這樣只要一直回傳這個物件就是簡單的singleton private static SingletonGreed instance = new SingletonGreed(); // private constructor,這樣其他物件就沒辦法直接用new來取得新的實體 private SingletonGreed(){} // 因為constructor已經private,所以需要另外提供方法讓其他程式調用這個類別 public static SingletonGreed getInstance(){ return instance; } } ``` ##### **類別定義** ```java= public class Singleton { private static Singleton instance; private Singleton(){ // 這裡面跑很了多code,建立物件需要花費很多資源 } public static Singleton getInstance(){ // 第一次被呼叫的時候再建立物件 if(instance == null){ instance = new Singleton(); } return instance; } } ``` ##### **主程式** ```java= /** * 單例模式測試 */ public class SingletonTest extends Thread { String myId; public SingletonTest(String id) { myId = id; } // 執行緒執行的時候就去呼叫Singleton.getInstance() public void run() { Singleton singleton = Singleton.getInstance(); if(singleton != null){ // 用hashCode判斷前後兩次取到的Singleton物件是否為同一個 System.out.println(myId+"產生 Singleton:" + singleton.hashCode()); } } public static void main(String[] argv) { /* // 單執行緒的時候,s1與s2確實為同一個物件 Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println("s1:"+s1.hashCode() + " s2:" + s2.hashCode()); System.out.println(); */ // 兩個執行緒同時執行 Thread t1 = new SingletonTest("執行緒T1"); // 產生Thread物件 Thread t2 = new SingletonTest("執行緒T2"); // 產生Thread物件 t1.start(); // 開始執行t1.run() t2.start(); } } ``` Singleton會面對的問題是在多個執行緒下被呼叫到,兩個執行緒都執行到new的話會造成建立兩個物件,對應這個狀況會將程式碼多加一些保險: ##### **新·類別定義** ```java= public class Singleton { private static Singleton instance; private Singleton(){ // 這裡面跑很了多code,建立物件需要花費很多資源 } // 多執行緒時使用synchronized保證Singleton一定是單一的 public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } } ``` 差別就在public static Singleton getInstance() 變成了public static **synchronized** Singleton getInstance() synchronized需要指定物件,當程式運行到synchronized的區域時,物件會被鎖定(Lock)直到離開該區域。在鎖定期間,鎖定同一物件的其他synchronized區域,會因為無法取得物件的Lock而等待。 待synchronized區域執行完釋放Lock後,其他鎖定同一物件的synchronized區域中,Java會讓其中一個區塊取得該鎖定物件的Lock而可以執行。其他鎖定同一物件的synchronized區域就繼續等。 但整個getInstance都被鎖定會降低不少執行效能,可以改為只鎖定創造物件的過程已加快速度。 ##### **新·類別定義-2** ``` java= public class Singleton { private static Singleton instance; private Singleton(){ // 這裡面跑很了多code,建立物件需要花費很多資源 } // 多執行緒時,當物件需要被建立時才使用synchronized // 保證Singleton一定是單一的,增加程式效能 public static Singleton getInstance(){ if(instance == null){ synchronized(Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } } ``` ## Architecture Patterns ### 1. Microservices [AWS](https://aws.amazon.com/tw/microservices/) 微型服務是一種架構式和組織式的軟體開發方法,其中軟體由小型獨立服務組成,並透過**定義良好的API進行通訊**。這些服務由小型的獨立團隊所擁有。 微型服務架構可讓應用程式更輕鬆擴展並加快開發速度,不僅促進創新,更縮短新功能的上市時間。 #### 巨型與微型服務架構 ![](https://i.imgur.com/pr6GzNS.png) 在巨型架構中,所有程序皆緊密結合,並會以單一服務執行。這表示如果某個應用程式的其中一個程序需求激增,就必須擴展整個架構。隨著程式碼基底增加,要新增或改善巨型應用程式的功能也變得更加複雜。這種複雜性局限了實驗的可能,更難落實新點子。由於許多獨立且緊密結合的程序會提高對單一程序故障的影響,因此巨型架構會增加應用程式可用性的風險。 在微型服務架構中,應用程式會建立為**獨立元件**,並會以服務的形式執行個別應用程式程序。這些服務使用輕量型API,透過定義良好的界面進行通訊。服務係針對商業功能所建立,且每項服務皆可執行單一功能。因為每項服務皆獨立運作,因此可以個別更新、部署和擴展,以滿足應用程式特定功能的需求。 [iT邦幫忙](https://ithelp.ithome.com.tw/articles/10228461) 微型服務目的是將單體式架構(Monolithic Architecture)依照功能拆分成小塊,達到更好的可重用性(reusability)與靈活度(flexibility)。 #### Microservices vs Monolithic Architecture 目前單體式架構(Monolithic Architecture)仍然是主流的軟體架構。單體式架構簡單來說就是一個「一站式」的應用程式,在一段請求到回應的過程中,經過的各式各樣元件(component)都放在同一個地方(用同一個語言、在同一個地方部署)。 好處是能讓服務和服務之間的呼叫比較簡單,開發也較輕鬆;壞處是當享要擴展某個服務時,整個程式都會被擴展而導致資源浪費。且其中一個服務不可用時,其他服務可能也會受到牽連。 Microservices 則是一種將不同元件從一個大統一的架構中拆出來的模式。比如說,原本在一個 Monolithic Application 中有用戶認證、髒話過濾、圖片壓縮等等元件,這些元件的存在形式會是插件(plugin)或是函式庫(library),而在 Microservices 的架構中,就會是一個個服務(service)。 #### Libraries and Services 一個引入許多libraries跟一個由許多不同microservices所組成的應用程式有什麼不一樣?第一,當你改動一個library,哪怕只是一小部分,你都必須要重新部署整個應用程式,而如果是使用 microservices,你只要重新部署該服務就好了。相信使用monolithic architecture的人都有過這種覺得很麻煩的經驗:明明就只改了一行的程式碼,卻要讓整個應用程式重新跑一系列的測試部署流程。 再來,當我們使用libraries時它會被我們載入記憶體中,成為一個function call。當我們使用一個 service,它則是在這個程序(process)之外的元件,必須使用web service request或是remote procedure call來溝通。 #### Microservices vs Modularization 如果單純將一個Monolithic application模組化(modularized)呢?實務上會碰到幾個困難點: 1. 這些模組通常會傾向被包進太多的 context—這會導致開發上的困難,因為這些模組之間的交互關係不是有明確規範的,所以開發人員必須要自己記得模組與模組之間的關係,當應用程式變大或是時間屹久就會變得沒有效率。 2. 這些模組通常界線很難分明。 這些難處只有良好的 documentation 與 discipline 才能好好預防。 那 service 的好處是什麼?因為它必須要依靠 web service request 或是 remote procedure call,這強迫我們為每個元件訂出清楚明確的界線,並且讓服務與服務之間有一致的介面。有了這個強而有力的限制我們就再也不用依賴開發者的良心發現(?)來確保架構(這一方面)的品質了。 ###### tags: `Intellingent Agent`