顯示具有 設計模式 標籤的文章。 顯示所有文章
顯示具有 設計模式 標籤的文章。 顯示所有文章

Multi-Tenant Data Architecture 分享 (多租戶的資料設計架構) part1

最近同事分享了一篇好文章:Multi-Tenant Data Architecture,在這裡紀錄一下自己閱讀的感想。在這一篇中,先針對三種架構進行介紹,比較其優缺點和適用環境,下一篇再來描述如何針對不同的需求,選擇一個最適合你的設計架構。

  • 什麼是多租戶(Multi-Tenant)?
從字面上來看,就是有多個租用的客戶。在SaaS的雲端平台架構中,多租戶技術指的是大量的用戶共同使用、分享一組軟硬體資源。這這裡要分享的是在多租戶技術中,資料隔離的架構設計。

[設計模式] Strategy Pattern(策略模式) Part 2

Strategy Pattern(策略模式) Part 1裡面我們提到,如果我們裡用繼承父類別的方法,就會造成子類別被強迫實作他不需要的方法,有沒有更好的方式呢?

我們觀察到,每個角色共同擁有的功能是【攻擊】,只是攻擊的武器不同,因此,我們可以把攻擊這個方法獨立抽出來。先建立一個叫做Attack的Interface:


public interface Attack
{
    void useWeapon();
}


接著我們針對三個武器建立三個Class,分別是KnifeBehavior、SwordBehavior和AxeBehavior,同時這三個類別要去實作Attack這個介面:


public class KnifeBehavior implements Attack
{
     public void useWeapon()
     {
          System.out.println("Attack by Knife");
     }
}



public class SwordBehavior implements Attack
{
     public void useWeapon()
     {
          System.out.println("Attack by Sword");
     }
}



public class AxeBehavior implements Attack
{
     public void useWeapon()
     {
          System.out.println("Attack by Axe");
     }
}


接著,我們可以在原來角色的類別(Character)中,增加一個 setFightAction(Attack attack);的方法,這個方法可以讓實作他的角色類別依照需要來動態的產生不同的攻擊方法,因此,我們原本的角色類別變成:

public abstract class Character {
    public abstract void fight();
    public abstract void action();
    public abstract void char();
    public abstract void setFightAction(Attack attack);
}

如此一來,實作角色類別的個別角色(King、Queen和Knight)的程式就會變成:

public class King extends Character {

     Attack attack;

     @Override
     public void action() {}

     @Override
     public void chat() {}

     @Override
     public void fight() {
          f.useWeapon();
     }

     @Override
     public void setFightAction(Attack attack) {
          this.attack = attack;
     }
}


最後,我們要使用每個角色時,只需要這樣寫:

public class PlayGame {
    public static void main(String [] a) {
        King kevingo = new King();
        kevingo.setFightAction(new KnifeBehavior());
        kevingo.fight();

        Queen mary = new Queen();
        mary.setFightAction(new SwordBehavior());
        mary.fight();

        Knight tom = new Knight();
        tom.setFightAction(new KnifeBehavior());
        tom.fight();
    }
}


往後,當我們需要有新的攻擊方式時,只需要重新建立一個新的攻擊類別,而如果皇后要改拿其他的武器時,也只要在setFightAction中指定新的攻擊類別,真是太好了!

再回顧一下Strategy Pattern的定義:

  • 策略模式在於觀察程式中變動的部分,並且將他們抽離出來封裝成可互相抽換的演算法家族。這個模式讓你即使任意更換實作的演算法,也不會變動到去使用者他們的客戶端程式。

在這個範例中,我們把不同型態的攻擊方法抽出來變成一組家族,建立一個Interface叫做Attack,同時根據不同的攻擊方法來建立不同的類別,往後我們只要利用set方法就可以輕鬆的替換攻擊的方式,這就是策略模式的用途所在。

當然這個模式不是沒有缺點,最顯而易見缺點就是他必須要維護比較多的類別,當實作的策略變多的時候,維護起來就會比較吃力囉。

[設計模式] Strategy Pattern(策略模式) Part 1

策略模式在Head First Design Pattern這本書中,是最先被介紹到的設計模式,可見得他應該是很重要且很基本的模式之一。以下就讓我們來記錄學習的心得並且分享筆記給大家。策略模式的基本定義如下:

Define a family of algorithms, encapsulate each one, and make them interchangeable. [The] Strategy [pattern] lets the algorithm vary independently from clients that use it.

【中文版】策略模式在於觀察程式中變動的部分,並且將他們抽離出來封裝成可互相抽換的演算法家族。這個模式讓你即使任意更換實作的演算法,也不會變動到去使用者他們的客戶端程式。

在這篇文章中,我們先看看如果不使用策略模式時,撰寫出來的程式是如何,接著在下一篇文章中進行一個對比。

讓我們考量一個情境(此情境延伸自Head First Design Pattern的練習),假設我們需要設計一組遊戲角色,我們可能會先定義一個角色類別,每一個遊戲角色都可以攻擊(fight)、行動(action)和交談(chat):


public abstract class Character {
    public abstract void fight();
    public abstract void action();
    public abstract void char();
}

這是一個很簡單的角色類別,加上abstract修飾子,讓繼承Character的class必須強迫去實作fight、walk和talk這三個方法。接著就可以真正創造我們的角色,假設我們有國王(King)、皇后(Queen)和騎士(Knight)這三個角色,那我們會這樣定義三個類別:

public class King extends Character {

     @Override
     public void action() {
         System.out.println("King walk");
     }

     @Override
     public void chat() {
         System.out.println("King chat");
     }

     @Override
     public void fight() {
          System.out.println("King fight by hand");
     }
}



public class Queen extends Character {

     @Override
     public void action() {
         System.out.println("Queen walk");
     }

     @Override
     public void chat() {
         System.out.println("Queen chat");
     }

     @Override
     public void fight() {
          System.out.println("Queen fight by hand");
}



public class Knight extends Character {

     @Override
     public void action() {
         System.out.println("Knight walk");
     }

     @Override
     public void chat() {
         System.out.println("Knight chat");
     }

     @Override
     public void fight() {
          System.out.println("Knight fight by hand");
}

角色都創建好了,看起來好像都沒有問題,但假設某天我們的遊戲改版了,使用者可以購買武器來殺敵,我們需要擴充程式來滿足這樣的需求。所以老闆說需要增加【寶劍】、【斧頭】、【匕首】等三種不同的新的攻擊模式,慘了,我們需要在角色類別裡面增加三個方法:


public abstract void fightBySword();
public abstract void fightByKnife();
public abstract void fightByAxe();


但是,問題來了,所有繼承角色這個類別的子類別都必須要實作這三個方法,但是假設皇后不能用斧頭只能用匕首、國王只能用寶劍...,這樣一來就很麻煩,我們必須要在個別角色裡面新增這三個方法,然後有些方法是空的...,多麼不好的寫法!

所以,利用繼承是會有缺點的,改變父類別的內容會連帶的更動到所有繼承他的子類別。在下一篇中我們來看看Strategy Pattern可以帶來什麼改變。

[設計模式] Singleton Pattern (單例模式) Part2 (解決多執行緒問題) - Update Volatile關鍵字

[2011-1-1補充] volatile關鍵字


感謝馮大哥留言,在運用Double-Checked locking的作法時,在Java 5.0之後必須要加上volatile的修飾子在物件的變數宣告上,也就是變成:
  •  private volatile static Singleton instance;
這樣子的寫法。原因在於,如果不加上volatile這個關鍵字時,在Java中由"順序"的問題,還是有可能會發生問題。最顯而易見的錯誤在於,當A執行緒進入getInstance()方法後,並且得到if(instance==null)為true的結果,進入到同步區,要執行Singleton()建構子內的敘述,但還沒有執行完時,執行緒B就進入getInstance()方法中,並且得到instance不是null的結果(因為在記憶體空間中已經配置的位置給instance了),如此一來就會造成執行緒B的物件內容並沒有成功的被指派,A和B之間就會有不同步的情形了。

詳細的過程可以參考這篇文章:Double-checked Locking (DCL) and how to fix it

我覺得寫的還蠻清楚的,另外我也加了相關閱讀在最下方,歡迎大家參考,這幾篇對於Double Check Locking的機制都寫得蠻清楚的,值得一看囉。
==

在Singleton Pattern (單例模式) Part1中的【Lazy Instantization】方法可以說是最基礎的方式,但是在多執行緒的程式當中,這樣的寫法還是有可能會產生不同的實體,也就是這樣的寫法並不是一個thread-safe的方式,我們可以寫一個小程式來檢查看看:


public class SingletonTest extends Thread {

    String threadName = "";

    public SingletonTest(String name) {
        threadName = name;
    }

    public void run() {
        for(int i=0 ; i<10 ; i++) {
            try {
                Thread.sleep((int)500);
                Singleton instance1 = Singleton.getInstance();
                System.out.println(threadName + " ==> " + instance1.hashCode());

            } catch (InterruptedException e) {
                e.printStackTrace();
        }
    }
}

    public static void main(String [] a) {
        Thread t1 = new SingletonTest("thread-1");
        Thread t2 = new SingletonTest("thread-2");

        t1.start();
        t2.start();
    }
}


兩個threads可能會產生相同的實體(相同的hashCode)或是不同的實體(但不一定每次都會發生),原因在於兩個threads可能同時呼叫Singleton.getInstance()時,在進行if(instance==null)判斷時,結果都可能會是true,以至於都產生一個新的實體,這樣就會違背Singleton Pattern的宗旨了,所以我們必須進一步的修改這個模式。

修改的想法是,當A、B兩個執行緒同時呼叫Singleton.getInstance()這個方法來取得物件的實體時,必須要先等對方結束之後才呼叫,才不會造成問題。換句話說,getInstance()這個方法在Java中只要加上Synchronized這個修飾子就可以解決了,如此一來,整個getInstance()方法就變成一個同步等待區,可以避免多執行緒造成重複實體的問題。

public class Singleton {

    private static Singleton instance;

    private Singleton () { }

    public static Synchronized Singleton getInstance() {
        if(instance==null)
            instance = new Singleton();
        return instance;
    }
}


上面提到,某個執行緒必須等到另外一個執行緒離開之後才能存取getInstance()這個method,當執行緒一多的時候,可以想像的會造成效率的低落。所以又有進一步的改良寫法,那就是採用雙重上鎖(Double-Checked locking)的方式。

雙重上鎖的概念是,一開始先檢查instance這個變數是不是null,如果不是null代表之前已經實體化了,直接回傳就好,如果沒有實體化時,才會進入同步等待區域。實際撰寫上只要將上面的getInstance()方法改寫成以下的形式就可以了:


public class Singleton {

    private volatile static Singleton instance;

    private Singleton () { }

    public static Singleton getInstance() {
        if(instance==null) {
            Synchronized(Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}


如此一來就可以解決多執行緒效率的問題囉!歡迎大家一起留言討論:)

【相關閱讀】
Double-checked locking and the Singleton pattern
Double-checked Locking (DCL) and how to fix it
The "Double-Checked Locking is Broken" Declaration

[設計模式] Singleton Pattern (單例模式) Part1

最近在學習Hibernate的時候,發現在原廠的Documentation裡面,使用了Singleton Pattern這樣的模式,就讓我藉由這篇文章複習一下Design Pattern吧。

設計模式在軟體開發中,指的是如何運用一個良好的思維邏輯來進行程式碼架構的規劃及撰寫。提到設計模式,一定會提到Gof等四位作者所撰寫的這本書吧:


今天想要介紹並且讓我自己複習的正是Singleton Pattern(單例模式)。Singleton Pattern在設計上是希望確保一個類別在系統運行的時候,只有一個實體(instance)產生,同時,這個類別必須要提供一個公開的方法讓其他人取得自己的實體。為什麼我們要使用者這個模式?

在某些情況下,你的程式只會希望有一個物件的實體存在,比如說,連接資料庫的物件、程式管理員的物件、處理印表機列印的類別等等。如果這些物件可以有多個實體,那每次需要這些功能的時候,我們都可以任意的new一個實體出來進行操作,這樣會造成相當恐怖的結果。

如果把Singleton Pattern實作出來,長相會是這樣


public class Singleton {

    private static Singleton instance;

    private Singleton () { }

    public static Singleton getInstance() {
      if(instance==null)
        instance = new Singleton();
      return instance;
    }
}

這段程式碼的重點有三個:

  1. 要有一個私有(private)的靜態(static)方法:用來紀錄類別的實體。因為是私有的,所以外部沒辦法直接透過Singleton.instance來存取。
  2. 類別擁有一個私有的建構子:其他的方法沒辦法直接產生這個類別的實體,說的白話一點,就是不能夠用new Singleton()這樣的方式來產生實體,這是重點所在。
  3. 擁有一個公開取得物件實體的方法:由於是私有建構子(private constructor)的原因,要產生這個類別的實體,只能靠自己類別內的方法產生。但是又必須要讓外部能夠呼叫,所以要設定為公開的靜態方法
這種方式又稱為【Lazy Instantization】,中文可以翻作【延遲實體化】。為什麼呢?因為我們可以看到,我們只有需要這個物件的時候,利用Singleton.getInstance()這樣的方法來取得Singleton這個類別的實體,不需要的時候就不會產生,所以叫做延遲的實體化方法。

要怎麼驗證呢?很簡單,寫一個小程式測試一下就好了,兩個實體的hashcode應該是相同的囉:

Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());

Lazy Instantization的方法在Single Thread的程式中是可以正確運作的,但是當你的程式有兩個或兩個以上的threads在跑的時候,就會出現問題,這個部份我們留到part2再談囉!