分類
eclipse plugins

安裝並設定CheckStyle plugin on eclipse(Kepler)

1. Install CheckStyle:

open eclipse(Kepler) -> Help -> install new software -> add -> type name : CheckStyle ,
type Location : http://eclipse-cs.sourceforge.net/update -> select checkstyle and press next
 

2. Config CheckStyle(option)

open eclipse -> Window -> preferences -> checkstyle -> config checkstyle settings
 

3. Create customize checkstyle rule(option)

open eclipse -> Window -> preferences -> checkstyle -> copy default rule
-> adjust rule for customize config file
 
 

分類
Anti-Pattern

Constance Interface Pattern(常數介面模式)

Constance Interface Pattern(CIP)(常數介面模式)
這是個 Anti-Pattern,應該使用其他方法取代它。
CIP 的定義如下
一個介面,該介面沒有任何方法,只有 final static 的常數。e.g.,

public interface ConstanceInterface {
    public static final int CONSTANCE_INT_ZERO = 0;
    public static final int CONSTANCE_INT_ONE = 1;
    public static final int CONSTANCE_INT_TWO = 2;
}

 
想使用這些常數的類別可以藉由實作該介面來取得這些常數,e.g.,

public class ConstanceInterfaceImpl implements ConstanceInterface{
    public int getConstanceInterfaceZero(){
        return CONSTANCE_INT_ZERO;
    }
}

 
CIP的缺點為若常數只提供給某些類別使用,那麼應該定義在該類別中,不需提供公共的介面。
反之,若常數是提供給多數類別使用,那麼可以改用 Utility Class 或是 enum。
Utility Class

public final class ConstanceUtil {
    public static final int CONSTANCE_INT_ZERO = 0;
    public static final int CONSTANCE_INT_ONE = 1;
    public static final int CONSTANCE_INT_TWO = 2;
    private ConstanceUtil(){
        throw new AssertionError();
    }
}

 
Enum

public enum ConstanceEnum {
    CONSTANCE_INT_ZERO(0), CONSTANCE_INT_ONE(1), CONSTANCE_INT_TWO(2);
    private final int mNumber;
    private ConstanceEnum(int number) {
        mNumber = number;
    }
    public int getNumber() {
        return mNumber;
    }
}

 
介面應該回歸於最原始的使用需求,定義型態。
除了定義型態以外不適合用於其他用途。
 
 

分類
Android Java Uncategorized

工具類別的實體化限制(Prevents Utility class instantiated)

工具類別(Utility Class)類似於 java.lang.Math,該類別提供公共靜態方法給外界使用。
因此工具類別並不需要該實體的產生,但編譯器會提供給每個類別一個對外的預設建構式。
因此我們必須修改預設建構式避免外部可以實體化工具類別。

public final class ExceptionPrinter
{
    private ExceptionPrinter(){
        //Prevents utility class being instantiated
        throw new AssertionError();
    }
    public static void printError(String TAG, Exception e)
    {
        StringWriter errorMessage = new StringWriter();
        e.printStackTrace(new PrintWriter(errorMessage));
        Log.e(TAG, errorMessage.toString());
    }
}

 
 
 

分類
Anti-Pattern

Telescoping Constructor Pattern (伸縮建構式)

Telescoping Constructor Pattern (伸縮建構式)
首先這是個反模式(Anti-Pattern),在參數大於等於4個的情況下考慮使用 Builder Pattern 來替換。
伸縮建構式出現在具有多個參數建構式的類別中,如

public class Account {
    private String mName;
    private String mAge;
    private String mAddress;
    private String mTelephoneNumber;
    public Account(String name) {
        this(name, "unknow");
    }
    public Account(String name, String age) {
        this(name, age, "unknow");
    }
    public Account(String name, String age, String address) {
        this(name, age, address, "unknow");
    }
    public Account(String name, String age, String address,
            String telephoneNumber) {
        mName = name;
        mAge = age;
        mAddress = address;
        mTelephoneNumber = telephoneNumber;
    }
    public void showInfo() {
        System.out.println("Name:" + mName + " Age:" + mAge + " Address:"
                + mAddress + " TelephoneNumber:" + mTelephoneNumber);
    }
}

 
客戶端可以依照需要呼叫不同的建構式,如

        Account foxx = new Account("Foxx");
        foxx.showInfo();
        Account peter = new Account("Peter", "23");
        peter.showInfo();

Console

Name:Foxx Age:unknow Address:unknow TelephoneNumber:unknow
Name:Peter Age:23 Address:unknow TelephoneNumber:unknow

 
該模式有幾個缺點,
1.客戶端難以了解不同參數以及不同建構式代表的意義。
2.客戶端有可能搞混參數順序而傳錯參數。
 
可以考慮使用 Builder Pattern 來替換伸縮建構式。

public class Account {
    private String mName;
    private String mAge;
    private String mAddress;
    private String mTelephoneNumber;
    public static class AccountBuilder {
        private String mName;
        private String mAge;
        private String mAddress;
        private String mTelephoneNumber;
        public AccountBuilder(String name) {
            mName = name;
        }
        public AccountBuilder age(String age) {
            mAge = age;
            return this;
        }
        public AccountBuilder address(String address) {
            mAddress = address;
            return this;
        }
        public AccountBuilder telephoneNumber(String telephoneNumber) {
            mTelephoneNumber = telephoneNumber;
            return this;
        }
        public Account build() {
            return new Account(this);
        }
    }
    private Account(AccountBuilder builder) {
        mName = builder.mName;
        mAge = builder.mAge;
        mAddress = builder.mAddress;
        mTelephoneNumber = builder.mTelephoneNumber;
    }
    public void showInfo() {
        System.out.println("Name:" + mName + " Age:" + mAge + " Address:"
                + mAddress + " TelephoneNumber:" + mTelephoneNumber);
    }
}

其中 AccountBuilder 即是 Builder Pattern 的主角,使用其建構式(第14行)來規定必須要傳入的參數。
接著建立其他方法(age() , address(), telephoneNumber())來設定屬性,這些方法因為具有明確的名稱可以讓客戶端容易了解方法的意圖。
最後藉由 build 方法回傳 Account 實體。
 
客戶端呼叫

Account foxx = new Account.AccountBuilder("Foxx").age("unknow").address("unknow").telephoneNumber("unknow").build();
foxx.showInfo();

Console

Name:Foxx Age:unknow Address:unknow TelephoneNumber:unknow

 
Note
為了確保各個屬性的正確,可以在build方法加入檢查機制。

public static AccountBuilder{
        ...
        public Account build() {
            if(mAge == null){
                throw new IllegalArgumentException();
            }
            return new Account(this);
        }
        ...
}

 
該檢查機制會在客戶端沒有呼叫age()方法而產生Account實體丟出Exception。

Account peter = new Account.AccountBuilder("Peter").build();

Console

Exception in thread "main" java.lang.IllegalArgumentException

 
缺點:
1.必須先建立 builder 角色,效能可能會受影響。
2.Builder Pattern 的內容比 telescoping constructor 還長,因此適用於4個以上的參數。
 

分類
Architectural Pattern

MVC Pattern(Model View Controller)

定義

分為三個部分,模型(Model)包含核心功能和數據。視圖(View)展示訊息。控制器(Controller)處理使用者輸入。
View 和 Controller 共同構成使用者介面(UI)。變更傳播機制確保了使用者介面和模型之間的一致性。

主要設計思想

一般來說修改使用者介面的機會往往大於修改業務邏輯的機會,在修改業務邏輯時不必變動使用者介面的代碼(關注點分離)。

因此需要把使用者介面和業務邏輯分離。
1.相同的訊息在不同介面有不同的顯示。
2.應用程式的顯示和行為必須立即反應數據的改變。
3.使用者介面應該易於改變。
4.可以移植使用者介面而不影響核心功能。

解決方案

MVC 將應用程序分為 個部份,輸入處理輸出。
Model 封裝了數據以及核心功能。
View 向使用者顯示訊息,View Model 獲得數據,一個 Model 可能有多個 View
每個 View 都有一個相關的 ControllerController 接受輸入,該輸入通常為外部輸入e.g.,滑鼠,鍵盤事件,
而事件被轉換為 Model View 的請求。使用者僅透過 Controller 與系統交互。
MVC 的分離讓同一個 Model 可以有多個 View。如果使用者透過一個 View 的 Controller 去改變了 Model,那麼所有相依於該 Model 的 View 都應該反應這種變化。
因此一旦 Model 的數據發生變化,Model 要通報所有 View。而 View 從 Model 取得新數據並顯示變化。這可以透過 Observer Pattern 來達成。

結構

1.Model 包含核心功能,封裝相應數據並提供可存取數據的函式讓外部存取,這些數據讓View 來使用。
變更傳播機制維護一個註冊表。所有的 View 和 Controller 註冊他們有關變更的待通知的需求。Model 狀態的改變觸發變更傳播機制。
2.View 向使用者顯示訊息,不同的 View 用不同的方式呈現 Model 的訊息。
每個 View 定義一個被變更播機制觸發的更新過程。當更新過程被調用時,View 就會從Model 取得並顯示新的數據。
初始化階段,所有的 View 都與 Model 相關,並向變更傳播機制註冊。每個 View 將建立一個對應的 ControllerView 和 Controller 的關係是一對一的。
View 通常會提供讓 Controller 操作其顯示的功能。這對不影響 Model 狀態的動作是有必要的。如捲動畫面。
3.Controller 接受使用者的輸入事件,事件如何被發送到 Controller 是取決於使用者平台。Controller 接受事件後會轉發為對 View 或是 Model 的請求。
如果 Controller 的行為依賴於 Model,那麼 Controller 也必須向 Model 註冊自己並實現更新過程。

動態特性

1.初始化步驟:
建立 Model 實體,初始化 Model 內部數據。
建立 View 實體,需要取得 Model 的實體作為本身初始化參數。
View 加入 Model 的變更傳佈機制。
View 初始化 Controller , View 向 Controller 的初始化過程傳入 Model 和其本身當作參數。
Controller 加入 Model 的變更傳播機制。
應用程式開始處理輸入事件。
2.如何處理輸入:
Controller 接受使用者輸入並轉換事件,啟動 Model 的服務過程。
Model 執行請求並改變了內部數據。
Model 調用更新過程,通知所有註冊的 View 和 Controller
每個有註冊的 View 向 Model 取得更新的數據並顯示。
每個有註冊的 Controller 向 Model 更新的數據並執行相對應動作。

實作:

步驟16為基本實現,步驟79為增加靈活的實用性。
1.建立 Model 實現核心功能。
除了核心功能外,Model 會持有 observer 的集合,以及加入和刪除 observer 方法。
最後加上通知所有的 observer 狀態更新的方法。

public class RandomModel
{
    private static final int RANDOM_NUMBER_MAX = 10000;
    private int mRandomNumber;
    private ArrayList<IObserver> mObservers = new ArrayList<IObserver>();
    public void rollOnce()
    {
        mRandomNumber = new Random().nextInt(RANDOM_NUMBER_MAX);
        notifyObservers();
    }
    public int getRandomNumber()
    {
        return mRandomNumber;
    }
    public void addObserver(IObserver observer)
    {
        mObservers.add(observer);
    }
    public void removeObserver(IObserver observer)
    {
        if(mObservers.contains(observer)){
            mObservers.remove(observer);
        }
    }
    private void notifyObservers()
    {
        int sizeOfObservers = mObservers.size();
        for (int i = 0; i < sizeOfObservers ; ++i) {
            mObservers.get(i).update(this);
        }
    }
}

2.實現變更傳播機制
建立 observer 介面,observer 會有一個 update 方法,該方法會讓所有的 observer 子類別複寫。

public interface IObserver {
    public void update(RandomModel model);
}

3.建立 View
View 除了提供所有的可見元素之外,還必須實作 observer 介面並複寫 update 方法,在update 方法中藉由 Model 取得新數據以更新相關的可見元素。
另外在 View 的建構式中必須註冊變更傳播機制,以及建立 Controller

public class RandomView implements ActionListener, IObserver {
    private static final int VIEW_HEIGHT = 65;
    private static final int VIEW_WIDTH = 400;
    private static final String VIEW_TITLE = "RandomView";
    private static final String NAME_OF_ROLL_BUTTON = "Roll it";
    private RandomModel mRandomModel;
    private RandomController mRandomController;
    private JFrame mViewFrame;
    private JPanel mViewPanel;
    private JButton mButtonRollOnce;
    private JTextField mTextField;
    public RandomView(RandomModel randomModel) {
        mRandomModel = randomModel;
        mRandomModel.addObserver(this);
        mRandomController = new RandomController(mRandomModel,this);
        initLayoutComponents();
    }
    public void initLayoutComponents() {
        mViewPanel = new JPanel(new GridLayout());
        mViewFrame = new JFrame(VIEW_TITLE);
        mButtonRollOnce = new JButton(NAME_OF_ROLL_BUTTON);
        mButtonRollOnce.addActionListener(this);
        mTextField = new JTextField();
        mViewFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mViewFrame.setSize(new Dimension(VIEW_WIDTH, VIEW_HEIGHT));
        mViewPanel.add(mButtonRollOnce);
        mViewPanel.add(mTextField);
        mViewFrame.add(mViewPanel);
        mViewFrame.setVisible(true);
    }
    @Override
    public void actionPerformed(ActionEvent event) {
        if (event.getSource() == mButtonRollOnce) {
            mRandomController.startRollOnce();
        }
    }
    @Override
    public void update(RandomModel model) {
        int randomNumber = model.getRandomNumber();
        mTextField.setText(String.valueOf(randomNumber));
        System.out.println("in RandomView update");
    }
}

4.實作 Controller
Controller 必須實作 observer 介面並複寫 update 方法。
Controller 會將使用者輸入轉換為對 Model 或是 View 的事件請求。

public class RandomController implements IObserver{
    private RandomModel mRandomModel;
    private RandomView mRandomView;
    public RandomController(RandomModel model, RandomView view){
        mRandomView = view;
        mRandomModel = model;
        mRandomModel.addObserver(this);
    }
    public void startRollOnce(){
        mRandomModel.rollOnce();
    }
    @Override
    public void update(RandomModel model) {
        System.out.println("in RandomController update");
    }
}

5.設計並實作 View 和 Controller 關係
通常 View 和 Controller 之間為一對一。

6.實現 MVC 的準備。

public class Main
{
    public static void main(String[] args){
        RandomModel randomModel = new RandomModel();
        RandomView randomView = new RandomView(randomModel);
        //otherView with tha same Model
        //OtherView otherView = new OtherView(RandomModel);
    }
}

7.建立動態 View
如果應用程序具有動態打開和關閉 View 的功能,那麼可以建立一個用來管理 View 的元件。

8.可更換 Controller
View 可支援更換不同的 Controller,這種情況通常是對同一個 View 的不同操作。
e.g.,預設的 Controller 予許使用者進行操作,但在一些情況下禁止使用者進行任何操作(read only),為此可以在 View 中建立替換 Controller 的方法。

public void setController(OtherController controller){
        mRandomController = controller;
}

9.層次化 View 和 Controller
MVC 實現了可重用的 View 和 Controller,對於經常層次化使用的使用者接口元素(button, menu, editor)也是如此,
如果應用程序的使用者接口主要靠結合事先定義好的 View 對象來建立,那麼可以用組合模式來建立層次式靜態 View
如果同時啟動多個 View,那麼可能就有好幾個 Controller 同時關心事件,如果事件以某種順序分配到所有的 Controller 來處理,可以使用責任鏈模式管理事件的委託。

變體

Document View Pattern
View 結合了 Controller 的任務,兩者合併 。

已知應用

MFC
ET++

效果

優點:
1.同一 Model 的多個 View
2.同步化 View
3.可更換的 View 和 Controller
缺點:
增加複雜性
View 和 Controller 的緊密連結
View 的效率可能低落

分類
Architectural Pattern

Layered Architecture Pattern(分層架構模式)

定義:

能夠被分解為子任務組,其中每個子任務處於一個特定的抽象層次上。
 

範例:

網路協議(iso 7)
與採用單一模組來相比,分層方法是另一個更好的設計方式,優點有
1.幫助團隊開發,支持增量編碼和測試。
2.使用半獨立元件讓後期的元件交換更容易。
3.更好的實現技術可以透過簡單的重寫代碼分別加入。
 

環境:

一個需要分解的大系統。
 

問題:

系統的明顯特色是混合低層與高層的問題且高層依賴低層操作。
系統的一部份處理低層問題,如硬體(傳感器輸入,從文件中讀取或線路讀取電子信號)。另一部份處理高層問題如,使用者介面。
層之間的通信有兩個方向,一個為從高層到低層的請求,另一個為低層到高層的通知,通知可以是對請求的應答或事件的輸入數據。
系統往往需要一些與其垂直相交的水平層。這些操作處在同一個抽象層,但彼此之間很大的部份是獨立的。
關注點:
1.後期代碼的改動應該不影響整個系統。應該被限制在一個元件內且不影響其他元件。
2.介面應該是穩定的,甚至可以用標準來限定。
3.系統各個元件應該可以被替換。元件可以被別的實現方法來替代且不影響系統其他部份。
4.之後可能會建立其他系統,這些系統具有和目前設計一樣的底層問題。
5.相似職責應該分組以提高可理解性和可維護性。每個元件應該是具有內聚力的。
 

解決方案:

將系統分為適當層次,從最底層(1)開始,按照適當順序放置,將抽象層j放在j-1層的上層,直到最高層(n)
不需要說明某層j是不是需要分解為更複雜的子系統。重要的是在某層中所有用到的元件必須工作在同一層
 

結構:

Layer主要特徵為第j層的服務只被j+1層使用。每個獨立層都要防止被較高層直接訪問。
 

動態特性:

1.從上往下:客戶向 層n 發出請求,層無法完成該請求,於是調用層 n-1 相應的子任務,直到第1層,第1層完成後向上層反饋回去直到層n,從上而下通常稱為請求。
2.從下往上:一個驅動設備(1)探測到輸入之後,把輸入轉換成內部格式並報告給第2層。
由下而上通常稱為通知。
3.描述請求只達到某層的範圍,如果層n-1 可以達到需求,一個來自於頂層(n)的請求可能只需
達到相鄰層即可。
4.彼此能互相通信的層nan bn 彼此是獨立的2個層,一個請求從 an發出,沿著 a
n -1 移動直到 a 1a的最底層),接著傳遞給 b 1 (b的最底層)再沿著 b 層往上直到 b
n完成請求的回應沿著反方向傳遞。
 

實現:

以下的方法並非對所有的案例都是最好的,有時候採用從下而上或是溜溜球法會更好,所有的步驟並不是必須的,要看實際應用來判斷。
1.定義抽象準則來將任務分解成層,抽象準則往往來自平台概念上的區隔,如特定領域,概念複雜度等等。
e.g., 一個棋類遊戲可分為
遊戲整體策略
中盤戰術
基本移動
遊戲基本單位
e.g., 一般軟件可分為
用戶可見元素
特定應用模組
公共服務
操作系統介面
操作系統
硬體
 
2.根據抽象準則定義抽象層數。每個抽象層次對應模式中的一層,有時候抽象層次到層的映射是不明顯的
在確定是否針對特定點分為2個層或是合併為1個層時需要多加注意,過多的層會增加開銷,過少的層會導致不好的設計。
3.命名每個層並指定任務。
最高層的任務是整個系統的任務,所有其他層的任務只作為最高層的助手。
4.指定服務。
最重要的原則為層間必須嚴格的彼此分離。不可以有跨層存取的情況。
把較多的服務放在高層往往比低層來的好。
5.細化分層
重複步驟14。在考慮隱含層及其服務之前往往不可能精確定義抽象準則。
通常是錯誤定義組件及其服務,後來再根據他們的使用關係強加上一層結構。
e.g., 一個新元件可能要求不只一個層的服務,違反了嚴格分層的原則。溜溜球法:重複交替從上而下以及從下而上的設計過程。
6.為每個層指定一個接口
j 對層j+1 應該是一個黑盒設計,建立一個接口以提供所有層j 的服務並把該接口封裝在一個 facade 對象中。
7.建立獨立層
(獨立層為特定的某層)
如果一個獨立層很複雜,則它應該被分成幾個獨立元件。
8.指定相鄰層之間的通訊
層間通訊最常用的為推模組(push model)。當層j 請求層 j-1 的服務時,任何要求的訊息都要作為服務調用的一部分來傳輸。相反的拉模組(pull model)也很常用,使用的場合是底層自行從高層獲取可利用訊息,但拉模組會引入附加層和相鄰更高層的相依關係,要避免這層關係可以使用 callback
9.分離鄰接層
高層關心相鄰的底層,但底層並不關心用戶的身份。代表單向連接,當修改層j的服務,層j的改變可以忽略層j+1 的存在,上述的情況發生在從上而下。
若是從下而上,可以使用callback,高層要註冊底層的callback 方法。
10.設計錯誤處理策略
layer 來說錯誤處理比較麻煩,因為一個錯誤可以在它出現的層裡處理也可以送到高層。送到高層對於低層而言必須將錯誤轉換成對高層有意義的描述。根據經驗,盡可能在最低層處理錯誤。這可以防止高層被大量的錯誤以及錯誤處理代碼干擾。至少應該試著把相似錯誤類型歸類於更一般的錯誤類型,並僅傳播這些更一般的錯誤類型。
 

9.變體

1.鬆散分層系統(Relaxed Layered System)
層之間的約束較少,每個層可以使用比它低層的所有服務,而不僅是相鄰層。
可以增加使用的靈活性和性能,但代價是維護性。適用於要求性能高的系統。
2.通過繼承分層(Layering Through Inheritance):
底層作為基本類別,高層繼承自底層。
優點是高層可以根據需要修改底層的服務,缺點為高層和底層透過繼承綁定,底層若有修改高層必須重新編譯(脆弱基類問題)
 

10.已知使用

1.虛擬機虛擬機可以說是一種低層次的實現,它將低層次的細節或各類的硬體與高層次分離開來。
2.API:  API可以說是一個封裝底層常用功能的層。
3.訊息系統(Information System)IS 一開始往往使用2層式體系結構,底層是一個數據庫存有公司的特殊數據,頂層為用戶介面。

用戶介面數據庫

因為用戶邏輯會摻雜商業邏輯,因此可以再分一層為商業邏輯層

用戶介面商業邏輯
數據庫

但越來越多的數據庫需要支援,因此再新增一層領域層來對不同數據庫增加支援

用戶介面商業邏輯
領域層
數據庫

4. Windows NT
該操作系統是根據微核模式來建構。NT執行程序元件對應微核模式的微核元件。NT執行程序是一個鬆散分層系統。

系統服務層:子系統和NT執行系統之間的接口層。資源管理器層:包含對象管理器,安全引用監視器,過程管理器,I/O管理器,虛擬儲存管理器等等。
內核層:一些基本功能,中斷和意外處理,多處理器同步,線程調度和分配。
HAL(硬體抽象層):隱藏不同處理器之間的硬體差異。
硬體層:硬體

 

11.效果:

分層體系模式的優點
1.加強層的復用:如果一個層定義良好的抽象介面,可在多個環境中重用。
2.增加標準化:定義清楚的層可以促進標準化的開發。
3.限制相依性:層之間的改變被限制在該層中,不會影響其他的層,也增加了可移植性,測試性。
單層可被簡易的替換,可採用 Adapter pattern 或是 Bridge Pattern
4.增加可替換性:但如果想對2個介面以及服務不完全相配的層之間切換,則必須在2個層之間加上建立一個隔離層。
 
層的缺點:
1.更改行為的重複:如果不得不在許多層上做相當數量的重複工作以適應局部變動。
2.降低效率:分層體系效率通常低於整塊模組的效率。如果最高層有很大的程度依賴最底層,所有數據都必須通過中間層的轉換。
3.不必要的工作:如果底層的服務重複執行了多餘的工作,而這些工作並非高層所需。
4.難以確認分層的正確度:層數太少的分層不能完全發揮分層體系的優勢(可重用性,可更改性,可移植性)。
 

分類
Thread

Single Threaded Execution Pattern(單線程執行模式)

Description :

Single Threaded Execution 代表以一個線程執行的意思,也稱critical section(臨界區)
該 Pattern 用來限制只能讓一個線程進行,也是多線程的基礎。
 

Role

Shared Resource (SR)(共享資源):
SR是會被多線程共同存取的類別,該類別的方法可以分為2類:
1. Safe method : 多線程同時存取也不會出錯的方法。
2. UnSafe method : 多線程同時存取會出錯的方法,需要加上防護(synchronized)限制單線程去存取。e.g.,

        BankRunnable bankRunnable = new BankRunnable();
        Thread thread1 = new Thread(bankRunnable,"1");
        Thread thread2 = new Thread(bankRunnable,"2");
        thread1.start();
        thread2.start();

bankRunnable 就是 SR 的角色,因為它會被 thread1 thread2 共同存取。
 

public class BankRunnable implements Runnable{
    private Bank mBank;
    public BankRunnable(Bank bank) {
        mBank = bank;
    }
    public BankRunnable(){
        mBank = new Bank("in BankRunnable",1000);
    }
    @Override
    public void run() {
            makeDraw();
    }
  private synchronized void makeDraw() {
        System.out.println("Thread in makeDraw:"+Thread.currentThread().getName());
        while (mBank.getMoney() >= 100) {
            try {
                Thread.sleep(new Random().nextInt(500));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mBank.withDraw(100);
            System.out.println("Thread:"+Thread.currentThread().getName()+" "+mBank.getName() + " " + mBank.getMoney());
            if (mBank.getMoney() < 0) {
                System.out.println("over draw");
            }
        }
    }
}

BankRunnable 的方法中,makeDraw 即為 unsafe method , 其他的方法即是 safe method
對於 unsafe method 必須加上 synchronized 來保證同步化。
 

When to use??

滿足以下 個條件
1. 多線程。
2. 出現 shared resource
3. shared resource 的狀態(屬性)會發生變化,若狀態(屬性)不會發生變化,不需要使用synchronized
 
 

Note :

1.使用 synchronized 需要注意死鎖(deadlock),死鎖為2個線程分別獲取鎖定,互相等待對方解鎖的情況。
發生死鎖,程序會無法進行下去。
但要達到死鎖也必須滿足下列其一:
        1.具有多個shared resource
        2.線程鎖定一個shared resource,還沒解鎖前就去鎖定另一個shared resource
        3.取得 shared resource 的參與者順序不固定。
只要破壞上述條件其一就可避免死鎖。
 
2.unsafe method 複寫問題,若有 subclass 繼承 BankRunnable 並複寫了 makeDraw() 方法,但沒有加上
synchronized來修飾,同步化的問題又會出現,可以使用 final 來修飾superclass避免繼承。
 
3. 原子操作:
synchronized 的方法同時只有一個線程可以操作,因此這個方法為原子的操作。
java的基本類型(除了longdouble以外)和物件的賦值和引用也都是原子的。
在宣告屬性時加上volatile 代表所有對該屬性的操作都是不可分割的。
 
4.因此 single threaded execution pattern 可以簡單的描述為以下步驟
1.找出多線程的 shared resource
2.找出shared resource unsafe method 方法。
3.unsafe method 方法加上 synchronized
4.考慮死鎖或其他效率問題。
 
 
 
 

分類
Java

Enum(列舉) in java

列舉(enum) 為一組有限集合(固定的常量)的類型,每個列舉成員都為靜態 final 的特徵。
因為列舉為單例(Singleton),因此外部無法創造列舉的實例,也不能進行擴展。
有以下幾種常用的情況
 

1.使用 enum 取代狀態常量

狀態常量通常為一組相似的常量用來表示某種狀態,並根據這些常量來判斷進行動作。e.g.

public class ErrorHandler {
    public static final int ALARM_ARRAY_DATA_ERROR = 1;
    public static final int SET_ALARM_ARRAY_ERROR = 2;
    public static final int OUT_OF_BOUNDARY_ERROR = 3;
...
    private void checkErrorCode(int errorCode){
        switch (errorCode) {
            case ErrorHandler.ALARM_ARRAY_DATA_ERROR:
                ...
                break;
            case ErrorHandler.SET_ALARM_ARRAY_ERROR:
                ...
                break;
            case ErrorHandler.OUT_OF_BOUNDARY_ERROR:
                ...
                break;
            }
    }
}

 
第3到5行定義一組代表不同的錯誤狀態常量,並在第9行的方法中判斷錯誤狀態並進行相對動作。
這種狀態常量稱為(int enum pattern),具有幾種缺點。
1. 無法百分之百確定傳入switch的數值範圍處於定義的範圍之內。
2. 若想印出錯誤狀態,必須另外再寫方法。
使用 enum 取代 狀態常量。e.g.

public class ErrorHandler{
...
   public enum ErrorType{
        ALARM_ARRAY_DATA_ERROR,
        SET_ALARM_ARRAY_ERROR,
        OUT_OF_BUNDARY_ERROR
    }
...
    public void checkErrorCode(ErrorType errorType){
        switch (errorType) {
            case ALARM_ARRAY_DATA_ERROR:
                ...
                break;
            case SET_ALARM_ARRAY_ERROR:
                ...
                break;
            case OUT_OF_BOUNDARY_ERROR:
                ...
                break;
            }
    }
...
}

enum 的命名跟隨 class 的命名,大寫開頭,其成員由於具有 static final 的特性故使用全大寫加上底線命名。
如果 ErrorType 只會被使用在 ErrorHandler 中,存取權限為 private。若會被多個 class 使用就獨立為一個 public 類別。

2.將方法或是屬性加到 enum。

用於想讓 enum 的成員具有其他資訊,ErrorType 也具有數值的表示。

    public enum ErrorType{
        ALARM_ARRAY_DATA_ERROR(0),
        SET_ALARM_ARRAY_ERROR(1),
        OUT_OF_BUNDARY_ERROR(2);
        private int mNumber;
        private ErrorType(int number){
            mNumber = number;
        }
        public int getNumber(){
            return mNumber;
        }
    }

 
 

3.常用的 enum 方法。

    public void showAllEnumMembersByValues() {
        for(ErrorHandler.ErrorType errorType: ErrorHandler.ErrorType.values()){
            System.out.println("errorType.toString():"+errorType.toString());
        }
    }
    public static void checkMemberNameInEnum(){
        ErrorHandler.ErrorType.valueOf(ErrorHandler.ErrorType.ALARM_ARRAY_DATA_ERROR.toString());
        ErrorHandler.ErrorType.valueOf("no this member");
    }

第2行 values() 會依照宣告的順序傳回 enum 的成員們。
第3行 toString() 預設的情況下會回傳成員本身名稱。
第8及第9行 valuesOf() 會檢查傳入的字串是否符合成員的名稱,是的話回傳該成員,否的話丟出 illegalArgumentException。
因此第9行會丟出例外。
 

4.應該使用列舉的狀況為需要一組固定常量的時候,也包含真實世界的現象,如星期的天數,月份等等。

 

5.不要使用 ordinal() 來取得 enum 成員的順序。

ordinal() 會回傳每個 enum 成員在該 enum 中的位置,但若改變其他成員的順序,ordinal() 回傳值會不如預期。
如果想要確實的取得 enum 成員的相關數值,那就另外新增一個屬性並用該屬性來保存(參考第2點)。
 

6.若有相同的方法,但每個 enum member 的實現都各有不同,可以新增 abstract method 再讓各 member 去實現。

public enum ErrorType {
        ALARM_ARRAY_DATA_ERROR(0) {
            @Override
            String getNote() {
                return "alarm error because...";
            }
        },
        SET_ALARM_ARRAY_ERROR(1) {
            @Override
            String getNote() {
                return "set alarm array error because ...";
            }
        },
        OUT_OF_BUNDARY_ERROR(2) {
            @Override
            String getNote() {
                return "out of bundary error because...";
            }
        };
        private int mNumber;
        private ErrorType(int number) {
            mNumber = number;
        }
        abstract String getNote();
        public int getNumber() {
            return mNumber;
        }
    }

第27行的 getNote() 對於每個 enum member 來說都不相同,我們把它設定為 abstract 並讓 member 去分別實作它。
 

7. EnumSet 的使用,EnumSet 的使用方式和一般的 Set 沒有甚麼不同。

差別在於 EnumSet 只能接受 enum 的成員加入,優點是EnumSet 的效率相當高。

    public void testEnumSet(){
        EnumSet<ErrorHandler.ErrorType> errorTypes = EnumSet.noneOf(ErrorHandler.ErrorType.class);
        errorTypes.add(ErrorType.ALARM_ARRAY_DATA_ERROR);
        errorTypes.addAll(EnumSet.of(ErrorType.ALARM_ARRAY_DATA_ERROR, ErrorType.OUT_OF_BUNDARY_ERROR));
        errorTypes.remove(ErrorType.ALARM_ARRAY_DATA_ERROR);
    }

第2行 EnumSet 建構式比較特別,參數必須指定 enum class。
 

8. EnumMap 的使用與 EnumSet 類似,其鍵值必須來自 enum 的 member。

其他的使用方式就如同一般的 Map。

EnumMap<ErrorHandler.ErrorType , String > errorTypes = new EnumMap<ErrorHandler.ErrorType, String>(ErrorHandler.ErrorType.class);
        errorTypes.put(ErrorType.ALARM_ARRAY_DATA_ERROR, ErrorType.ALARM_ARRAY_DATA_ERROR.toString());
        System.out.println("errorTypes.get(ErrorType.ALARM_ARRAY_DATA_ERROR):"+errorTypes.get(ErrorType.ALARM_ARRAY_DATA_ERROR));
        errorTypes.clear();

第1行建構式必須指定 enum class。
 
 
 
 
 
 
 
 

分類
Refactoring

Null Object Pattern

當代碼中過度出現需要檢查 null (空值) 的情況,可以考慮使用 Null Object Pattern 取代相關的判斷式。

 
Account 為代表帳號的類別, e.g.

public class Account {
    private String mName;
    public Account(String name) {
        mName = name;
    }
    public String getName() {
        return mName;
    }
}

 
在客戶端需要常常檢查 Account 是否為空值才能進行下一步動作。e.g.

Account account = bank.getAccount();
if(account != null)
{
     //use account do something
}

 
重複檢查是否為 null,也是一種 Duplicated Code 的壞味道。
首先建立 NullAccount 來代表 Account 的 Null Object e.g.

public Account {
...
    private class NullAccount extends Account {
        private NullAccount(String name) {
            super(name);
        }
        @Override
        public String getName() {
            return "No name , This is NullAccount";
        }
    }
...
}

這裡使用了內部類別,是因為 NullAccount 為 Account 的一種型態,當然也可以轉換成獨立的 public 類別。
第11行修改了原有的方法,只要是在Account中所有的功能都必須被覆寫,以區別 NullAccount。
接著在 Account 加入讓外部取得 NullAccount 的方法 e.g.

public class Account {
...
    public static Account getNullAccount() {
            return new Account(null).new NullAccount(null);
    }
...
}

 
最後完整的 Account 如下

public class Account {
    private String mName;
    public Account(String name) {
        mName = name;
    }
    public String getName() {
        return mName;
    }
    public static Account getNullAccount() {
        return new Account(null).new NullAccount(null);
    }
    private class NullAccount extends Account {
        private NullAccount(String name) {
            super(name);
        }
        @Override
        public String getName() {
            return "No name , This is NullAccount";
        }
    }
}

 
修改 “所有” 會回傳 Account 的位置讓其返回 NullAccount,注意是所有會返回 Account 位置都必須修改!!
如原本 Bank 的 getAccount() 為

public class Bank {
...
    public Account getAccount()
    {
        return mAccount;
    }
...
}

改為

public class Bank {
...
    public Account getAccount()
    {
        return (mAccount == null) ? Account.getNullAccount() : mAccount;
    }
...
}

 
再也不需要檢查從 Bank 取的 Account 是否為 null 了

Account account = bank.getAccount();
String name = account.getName();

 
Note :
1. 使用 Null Object Pattern 只有在大量檢查空值的情況下才有用,若外部檢查的位置不多,使用這個 Pattern 的實質效益不大。
2. 以上為 Null Object Pattern 的基本型態,事實上 Null Object 本身可以修改為 Singleton Pattern
為了避免產生重複的實體,讓外部存取單一實體即可。
Singleton Null Object Pattern
修改 NullAccount e.g.

    private static class NullAccount extends Account {
        private static NullAccount sUniqueInstance = new NullAccount(null);
        private NullAccount(String name) {
            super(name);
        }
        public static NullAccount getInstance() {
            return sUniqueInstance;
        }
        @Override
        public String getName() {
            return "No name , This is NullAccount";
        }
    }

 
修改 Account 對外的介面 e.g.

public Account {
...
    public static Account getNullAccount() {
            return NullAccount.getInstance();
    }
...
}

如此產生的 Null Object 實體都是相同的。
 
3. Null Object Pattern 其實為 Special Case Pattern 的其中一種。
Special Case Pattern 請參考 patterns of enterprise application architecture(企業應用架構模式)。
 

分類
Android Uncategorized

get() method in AsyncTask

AsyncTask 提到了基本的用途,這篇紀錄關於 get() 的內容和實驗結果。
AsyncTask 的 get() 方法在 google develop 上的描述如下

public final Result get ()
Waits if necessary for the computation to complete, and then retrieves its result.
-------------------------------------------------------------------------------------------------------------------------
public final Result get (long timeout, TimeUnit unit)
Waits if necessary for at most the given time for the computation to complete, and then retrieves its result.

使用 get(long timeout, TimeUnit unit) 通常是用來作 timeout 的用途。
有幾個的重點如下:

1. get() 會 block UI thread,無論使用哪種方式都不可避免,因此要避免使用在 UI thread 上 。

public class TimeOutActivity extends Activity implements OnClickListener, IActivity
{
    public static final String TAG = TimeOutActivity.class.getSimpleName();
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        runTestTask();
    }
    private void runTestTask()
    {
        TestTask task = new TestTask();
        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        try {
            task.get(3, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }
    class TestTask extends AsyncTask<Void, Void, Void>
    {
        @Override
        protected Void doInBackground(Void... params)
        {
            for(int i=0; i<10; ++i){
                Log.d(TAG,"i:"+i);
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }

第18行呼叫  task.get(3, TimeUnit.SECONDS會讓 UI thread block 3秒,
無論第16行使用 AsyncTask.THREAD_POOL_EXECUTOR 或是 AsyncTask.SERIAL_EXECUTOR 都會讓 UI Thread block。
 

2. get() 作為 Timeout 用途可以用在非 UI thread 上,但要小心執行 AsyncTask 的參數選擇

(AsyncTask.THREAD_POOL_EXECUTOR or AsyncTask.SERIAL_EXECUTOR)

public class TimeOutActivity extends Activity implements OnClickListener, IActivity
{
    public static final String TAG = TimeOutActivity.class.getSimpleName();
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        executeCountTimeTask();
    }
    class TestTask extends AsyncTask<Void, Void, Void>
    {
        @Override
        protected Void doInBackground(Void... params)
        {
            executeCountTimeTask();
            return null;
        }
        private void executeCountTimeTask()
        {
            SimpleTask simpleTask = new SimpleTask();
            simpleTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
            try {
                simpleTask.get(3, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
        }
    }
    class SimpleTask extends AsyncTask<Void, Void, Void>
    {
        @Override
        protected Void doInBackground(Void... params)
        {
            for (int i = 0; i < 10; ++i) {
                Log.d(TAG, "i:" + i);
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }
}

第14和第39行分別不同的 AsyncTask,第14行的 TestTask為外層的 Task,其 executeCountTimeTask() 會再建立另一個 task(SimpleTask)
因為第26行的參數為 AsyncTask.THREAD_POOL_EXECUTOR,代表 Task 上的任務是可以被並行執行。
也就是說雖然第28行呼叫了 get() 方法,但因為 Task 任務是並行的,因此 get() 並不會 block 第26行的動作。
LogCat如下

 i:0
 i:1
 i:2
 java.util.concurrent.TimeoutException
 i:3
 i:4
 i:5
 i:6
 i:7
 i:8
 i:9

可以看到 get() 和 executeOnExecutor() 並不會互相干擾,TimeException 的訊息也會順利發出。
另外若是executeOnExecutor的動作在 Timeout 的時間(3 seconds)內完成,TimeException不會發出。
若是將第26行的參數改為 AsyncTask.SERIAL_EXECUTOR,代表 Task 上的任務是循序執行。

simpleTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);

也就是說 get() 會先執行,等到執行完畢才會進行 executeOnExecutor() 的動作。
LogCat如下

//wait 3 seconds
java.util.concurrent.TimeoutException
 i:0
 i:1
 i:2
 i:3
 i:4
 i:5
 i:6
 i:7
 i:8
 i:9

等待3秒後才會丟出 TimeoutException ,接著才會執行 executeOnExecutor()
 
其實使用這種 double asynctask 的作法未竟完善,
1. new AsyncTask() 的動作建議要在 UI thread 上,但 SimpleTask 的建構式卻是在 TestTask中建立。
3. 若要做 Timeout 的機制,其實可以在 SimpleTask 的 onPreExecute() 建立另一個計算時間的機制(CountDownTimer),如下

    class SimpleTask extends AsyncTask<Void, Void, Void>
    {
        @Override
        protected Void doInBackground(Void... params)
        {
            for (int i = 0; i < 10; ++i) {
                Log.d(TAG, "i:" + i);
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
        @Override
        protected void onPreExecute()
        {
            super.onPreExecute();
            new CountDownTimer(3000, 1000) {
                @Override
                public void onTick(long millisUntilFinished)
                {
                }
                @Override
                public void onFinish()
                {
                    Log.d(TAG,"timeout !!!");
                }
            };
        }
    }

一樣可以達到 Timeout 作用。