# 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」,當遊戲中的小雞數量成長到一定程度,系統會因為記憶體溢出而當機,以下是小雞的類別內容

其中任何一隻Chicken的isCute都是true,導致程式用到大量的重複浪費。
解決辦法就是讓這類重複性高的屬性變為另一個物件,如此一來當要使用的時候再去呼叫這個新建出來的物件就好,不再需要大量記憶體。

這時Chicken中原本有的三個屬性會變為兩種類型:
* Intrinsic State(內存狀態)
* 屬於被共享的狀態,屬於flyweight
* 在這就是指color和isCute
* Extrinsic State(外存狀態)
* 屬於非共享的狀態
* 在這就是指name

[舉例說明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。

##### **類別建立**
```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進行通訊**。這些服務由小型的獨立團隊所擁有。
微型服務架構可讓應用程式更輕鬆擴展並加快開發速度,不僅促進創新,更縮短新功能的上市時間。
#### 巨型與微型服務架構

在巨型架構中,所有程序皆緊密結合,並會以單一服務執行。這表示如果某個應用程式的其中一個程序需求激增,就必須擴展整個架構。隨著程式碼基底增加,要新增或改善巨型應用程式的功能也變得更加複雜。這種複雜性局限了實驗的可能,更難落實新點子。由於許多獨立且緊密結合的程序會提高對單一程序故障的影響,因此巨型架構會增加應用程式可用性的風險。
在微型服務架構中,應用程式會建立為**獨立元件**,並會以服務的形式執行個別應用程式程序。這些服務使用輕量型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`