分類
Design Pattern

組合模式(Component Pattern)應用

組合模式定義:

將物件組合為樹狀結構,以表達 ”部份-整體” 的層次。組合模式讓使用者對單一物件或組合物件的存取具有統一性。
 

需求:

必須提供一個機制讓資料可以轉換成 xml ,xml格式並不複雜,但可能會出現多層嵌套的情況。e.g.
Example 1

        <result>
            <code>999</code>
            <format>
                <name>John</name>
                <id>0123345</id>
                <gender>Male</gender>
                <mobileNumber>0988767654</mobileNumber>
                <age>20</model>
            </format>
            <format>
                <name>Mary</name>
                <id>543210</id>
                <gender>Female</gender>
                <mobileNumber>0911721431</mobileNumber>
                <age>18</model>
            </format>
        </result>

Example 2

        <queryResult>success</queryResult>

Example 3

        <device>
            <id>000001</id>
            <id>000002</id>
        </device>

上面3種格式雖然看起來不同,但基本的xml組成格式是相同的。可以把1個 xml element 當作1個資料結構的單體,如下方的code

<code>999</code>

 
因此以上3種 xml 格式組成的 xml element 可以視為2種種類
第1種為 Leaf,代表本身沒有包含 child (xml element),如下面的code

<code>999</code>

第2種為 Composite,代表本身有包含其他 child (xml element),如下面的format。
format 包含了5個child(name, id, gender, mobileNumber, age),而這5個child 因為沒有再包含其他的child,因此他們都是Leaf。

            <format>
                <name>John</name>
                <id>0123345</id>
                <gender>Male</gender>
                <mobileNumber>0988767654</mobileNumber>
                <age>20</age>
            </format>

 
 

對應的資料結構

public class XmlInfo
{
    private String mTag;
    private String mValue;
    private List<XmlInfo> mChildren = null;
    public XmlInfo(String tag, String value) {
        mTag = tag;
        mValue = value;
    }
    public void addChild(XmlInfo child)
    {
        if (mChildren == null) {
            mChildren = new ArrayList<XmlInfo>();
        }
        mChildren.add(child);
    }
    public String getTag()
    {
        return mTag;
    }
    public String getValue()
    {
        return mValue;
    }
    public List<XmlInfo> getChildren()
    {
        return mChildren;
    }
}

重點就在XmlInfo,其中 mTag 對應 xml element 的 tag 內容,mValue 對應 xml element 的 value內容。
當該 xml element 是 Composite 時,mValue就填 null , 為 Leaf,mValue 就填相應數值。e.g.
1.Leaf 寫法:
queryResult 為 Leaf 。

<queryResult>success</queryResult>

建立對應資料寫法為

XmlInfo queryResult = new XmlInfo("queryResult", "success");

2.Composite 寫法:
format 為 Composite。

            <format>
                <name>John</name>
                <id>0123345</id>
                <gender>Male</gender>
                <mobileNumber>0988767654</mobileNumber>
                <age>20</age>
            </format>

建立對應資料寫法為

XmlInfo format = new XmlInfo("format",null);
format.addChild(new XmlInfo("name", "John"));
format.addChild(new XmlInfo("id", "012345"));
format.addChild(new XmlInfo("gender", "Male"));
format.addChild(new XmlInfo("mobileNumber", "0988767643"));
format.addChild(new XmlInfo("age","20"));

另外需要注意的是 XmlInfo 不完全是 Composite Pattern 的應用。
原始的 Composite Pattern 會有個父類別(Component),並讓其子類別代表不同的種類如,Leaf , Component。
所以起碼會有3個 class 一起運作。因為目前的需求不需要用到其他的類別,單一類別(XmlInfo)即可符合需求。


 
 

模擬建立資料結構的操作

Example 1

private XmlInfo makeExample1()
    {
        XmlInfo result = new XmlInfo("result" , null);
        XmlInfo resultCode = new XmlInfo("code", "999");
        result.addChild(resultCode);
        XmlInfo format1 = new XmlInfo("format", null);
        format1.addChild(new XmlInfo("name", "John"));
        format1.addChild(new XmlInfo("id", "012345"));
        format1.addChild(new XmlInfo("gender", "Male"));
        format1.addChild(new XmlInfo("mobileNumber", "0988767654"));
        format1.addChild(new XmlInfo("age", "20"));
        XmlInfo format2 = new XmlInfo("format", null);
        format2.addChild(new XmlInfo("name", "Mary"));
        format2.addChild(new XmlInfo("id", "543210"));
        format2.addChild(new XmlInfo("gender", "Female"));
        format2.addChild(new XmlInfo("mobileNumber", "0911721431"));
        format2.addChild(new XmlInfo("age", "18"));
        result.addChild(format1);
        result.addChild(format2);
        return result;
    }

Example 2

private XmlInfo makeExample2()
{
        XmlInfo queryResult = new XmlInfo("queryResult", "success");
        return queryResult;
}

Example 3

    private XmlInfo makeExample3()
    {
        XmlInfo device = new XmlInfo("device", null);
        XmlInfo id = new XmlInfo("id", "000001");
        XmlInfo id2 = new XmlInfo("id", "000002");
        device.addChild(id);
        device.addChild(id2);
        return device;
    }

 
 

轉換資料為 xml

private String parseXmlData(XmlInfo xmlData)
    {
        result.append( "<"+xmlData.getTag()+">");
        if(xmlData.getChildren() != null){
            for (int i = 0; i < xmlData.getChildren().size(); ++i) {
                parseXmlData(xmlData.getChildren().get(i));
            }
            result.append( "<"+"/"+xmlData.getTag()+">");
        }else{
            result.append(xmlData.getValue());
            result.append( "<"+"/"+xmlData.getTag()+">");
        }
        return result.toString();
    }

 
 

Test Case

Example 1

@Test
    public void testExample1()
    {
        String target = "<result>"
                + "<code>999</code>"
                + "<format>"
                + "<name>John</name>"
                + "<id>012345</id>"
                + "<gender>Male</gender>"
                + "<mobileNumber>0988767654</mobileNumber>"
                + "<age>20</age>"
                + "</format>"
                + "<format>"
                + "<name>Mary</name>"
                + "<id>543210</id>"
                + "<gender>Female</gender>"
                + "<mobileNumber>0911721431</mobileNumber>"
                + "<age>18</age>"
                + "</format>"
                + "</result>";
        assertEquals(target, parseXmlData(makeExample1()));
    }

Example 2

@Test
    public void testExample2()
    {
        String target = "<queryResult>success</queryResult>";
        assertEquals(target, parseXmlData(makeExample2()));
    }

Example 3

@Test
    public void testExample3()
    {
        String target = "<device><id>000001</id><id>000002</id></device>";
        assertEquals(target, parseXmlData(makeExample3()));
    }

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

分類
Design Pattern

單例模式中資料的同步化

單例模式的基本介紹
雖然單例模式中的 HungerSingleton 可以保證只產生一個實體,但其中的資料還是需要做好同步化的動作。
首先標準的 Hunger Singleton

public class HungerSingleton
{
    private static final HungerSingleton sUniqueInstance = new HungerSingleton();
    private List<Integer> mNumbers = new ArrayList<Integer>();
    private HungerSingleton() {
        // Prevents singleton instance being instantiated from outside
    }
    public static HungerSingleton getInstance()
    {
        return sUniqueInstance;
    }
    public void addNumber(int number)
    {
        mNumbers.add(number);
    }
    public List<Integer> getNumbers()
    {
        return mNumbers;
    }
}

唯一不同的為第5行 mNumbers , 這個 List 將拿來做測試用,確保資料的同步化是否真的實現。
接著為Thread subclass

public class TestThread extends Thread
{
    @Override
    public void run()
    {
        for (int i = 0; i < 10; ++i) {
            HungerSingleton.getInstance().addNumber(i);
        }
    }
}

相當簡單,在 run 中會取得 HungerSingleton 的實體並把數值加到 mNumbers 中。
測試 Main class

public class Main
{
    public static void main(String[] args)
    {
        Thread thread = new TestThread();
        thread.start();
        Thread thread2 = new TestThread();
        thread2.start();
        try {
            Thread.currentThread().sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (Integer number : HungerSingleton.getInstance().getNumbers()) {
            System.out.println("number:" + number);
        }
    }
}

目標為讓 HungerSingleton 的 mNumbers 內容為 2 組 0 到 10 按照順序排列的數值。
執行結果

number:0
number:0
number:1
number:1
number:2
number:3
number:2
number:4
number:3
number:5
number:5
number:4
number:7
number:6
number:8
number:9
number:6
number:7
number:8
number:9

可以看到因為多執行緒的關係造成 mNumbers 內容排序錯亂。
加上同步化機制

public class TestThread extends Thread
{
    @Override
    public void run()
    {
        synchronized (HungerSingleton.getInstance()) {
            for (int i = 0; i < 100; ++i) {
                HungerSingleton.getInstance().addNumber(i);
            }
        }
    }
}

synchronized 可以讓一個執行緒取得指定物件的鎖,並擋住其他執行緒,直到該進入的執行緒完成函式內動作退出。
執行結果

number:0
number:1
number:2
number:3
number:4
number:5
number:6
number:7
number:8
number:9
number:0
number:1
number:2
number:3
number:4
number:5
number:6
number:7
number:8
number:9

從實驗結果可以看到雖然 HungerSingleton 可以保證只產生一個實體,但其內資料的操作還是需要加上同步化的動作。
 

分類
Design Pattern

Singleton Pattern (單例模式)

Singleton Pattern :

限制類別只能產生一個實體並提供全域存取方法。


 
Singleton Pattern 大致上可以分為 2 種形式,飢餓型以及懶人型,而不論哪種形式都相當簡單。
飢餓型,在第3行宣告 sUniqueInstance 變數立即產生實體。

public class HungerSingleton
{
    private static HungerSingleton sUniqueInstance = new HungerSingleton();
    private HungerSingleton() {
    }
    public static HungerSingleton getInstance()
    {
        return sUniqueInstance;
    }
}

 


 
懶人型,在第3行宣告 sUniqueInstance 變數並不馬上建立實體,而是等到需要實體才建立(第12行)。

public class LazySingleton
{
    private static LazySingleton sUniqueInstance;
    private LazySingleton(){
    }
    public static LazySingleton getInstance()
    {
        if (sUniqueInstance == null) {
            sUniqueInstance = new LazySingleton();
        }
        return sUniqueInstance;
    }
}

 
使用 Singleton Pattern 需要注意的是多執行緒的問題。
因為使用靜態初始化的關係,HungerSingleton 可以確保只產生一個 instance。
LazySingleton 由於使用判斷 null 值來產生實體,當 2 條執行緒同時執行到第11,12行,就有可能產生 2 個 Singleton ,就不是單例了。
最簡單的解決方式可以加上 synchronized 來限制同時只能有一條執行緒進入 getInstance 方法 如下

    public static synchronized LazySingleton getInstance()
    {
        if (sUniqueInstance == null) {
            sUniqueInstance = new LazySingleton();
        }
        return sUniqueInstance;
    }

另一種比較麻煩的解決方法為DCL(double checked locking), 但這種解法有個限制就是只能支援 jvm 1.5之後的版本。

public class DCLSingleton {
    private static volatile DCLSingleton sUniqueInstance;
    private static Object mLock = new Object();
    public static DCLSingleton getInstance() {
        if (sUniqueInstance == null) {
            synchronized (mLock) {
                if (sUniqueInstance == null) {
                    sUniqueInstance = new DCLSingleton();
                }
            }
        }
        return sUniqueInstance;
    }
}

如果情況予許還是選擇 HungerSingleton 來避免可能產生多個實體的情況,但 HungerSingleton 有另外一個缺點。
因為沒有實現 lazy load , 若其本身佔用大量記憶體,會在程序開始的時候造成浪費記憶體的情況。
可以使用靜態內部類別(static inner class)來實現 lazy load + HungerSingleton

public class InnerStaticHungerSingleton
{
    private InnerStaticHungerSingleton()
    {
      //Prevents utility class being instantiated
    }
    private static class InnerInstance{
        private static final InnerStaticHungerSingleton UNIQUE_INSTANCE = new InnerStaticHungerSingleton();
    }
    public static InnerStaticHungerSingleton getInstance()
    {
        return InnerInstance.UNIQUE_INSTANCE;
    }
}

 


另外 LazySingleton 實現了 lazy load 作法。也就是為了節省記憶體使用空間,只有在變數需要實體才產生。
除了 lazy load 可以節省記憶體之外,還有另一個技術 cache 可以達到相同的效果。
simple cache in java

public class SimpleCache {
    private Map<String, Object> mCache = new HashMap<String, Object>();
    private static final String KEY_OF_DATA = "keyOfData";
    public Object getCache() {
        Object data = mCache.get(KEY_OF_DATA);
        if (data == null) {
            data = new Object();
            mCache.put(KEY_OF_DATA, data);
        }
        return data;
    }
}

簡單的 cache 實作,主要使用 Map 來達成,第一次呼叫 getCache 會進入第10行,並把 data 存入 map 中,隨後需要使用的時候再從 map 取出即可。
不需要重複產生實體,其實再複雜一點的變化型就是 Flyweight Pattern(享元模式),其作用也是解決需要大量細微性物件時,記憶體不夠的狀態。
 
 
 
 
 
 

分類
Design Pattern

使用 Adapter 封裝未完成類別(unfinished class)

Adapter Pattern

將類別的介面轉換成另一個介面,使得原本不相容的類別可以互相一起工作。

使用的情況多半是在現有的介面無法滿足需求,必須轉接物件來完成功能,其實還可以用在封裝 unfinished class。
 
Description:
你需要未完成類別(unfinished class)的功能,由於該類別還未完成,你只能先建立dummy class 來滿足需求,可使用Adapter來封裝dummy class,
讓被影響的部份降至最低。
假設有個Person類別,該類別是由其他人來實作,但由於一些狀況目前還無法完成Person類別,不過現在你就需要從Person取得String name和int age,
首先建立DummyPerson,其中只實作需要的方法,如下

package com.foxx.person.adapter;
public class DummyPerson
{
    public String getName(){
        return "John";
    }
    public int getAge(){
        return 20;
    }
}

如果不用Adapter來封裝DummyPerson,也可以直接使用DummyPerson,但有個很大的缺點,
當你在10個位置使用DummyPerson,也代表將來Person完成後,你必須在這10個位置更換Person來取代DummyPerson,
需要修改的位置越多,出錯的機率也越大。使用Adapter來封裝DummyPerson就可限制修改的範圍只在Adapter中。
 
建立PersonAdapter類別

package com.foxx.person.adapter;
public class PersonAdapter
{
    private DummyPerson mPerson = new DummyPerson();
    public String getName(){
        return mPerson.getName();
    }
    public int getAge(){
        return mPerson.getAge();
    }
}

其中會有DummyPerson型態的類別成員,而getName以及getAge方法則委託給DummyPerson來完成
 
Client端呼叫

package com.foxx.person.adapter;
public class Client
{
    public void showPersonData(){
        PersonAdapter personAdapter = new PersonAdapter();
        System.out.println(personAdapter.getName());
        System.out.println(personAdapter.getAge());
    }
}

可以看到這裡只呼叫PersonAdapter的方法,Client並不知道回傳值是誰提供的。在Person類別未完成之前,都使用PersonAdapter來提供需要的功能
 
OK~ 現在Person類別完成了,如下

package com.foxx.person.adapter;
public class Person
{
    private String mName;
    private int mAge;
    public Person(String name, int age) {
        mName = name;
        mAge = age;
    }
    public String getName()
    {
        return mName;
    }
    public int getAge()
    {
        return mAge;
    }
}

 
現在只要修改PersonAdapter即可,不必更動到客戶端。

package com.foxx.person.adapter;
public class PersonAdapter
{
    private Person mPerson;
    public PersonAdapter(){
        mPerson = new Person("John",20);
    }
    public String getName(){
        return mPerson.getName();
    }
    public int getAge(){
        return mPerson.getAge();
    }
}

 
 
 
 
 

分類
Design Pattern

使用 Factory Method Pattern 替換多建構式

Factory Method Pattern :

提供產生物件的方法, 並讓其子類別決定產生何種類型的實體, Factory Method讓物件的產生延遲到子類別中.

 
Simple Factory Pattern 相同的例子, 同一種Dialog但在不同情況下必須有不同組合模式
Factory Method Pattern 和 Simple Factory Pattern 的不同在於 Simple Factory Pattern 將產生物件的動作全部放在單個類別中(Factory),
對於建構過程簡單的物件而言已經足夠,但缺點是若需要新增其他類別就必須修改Factory中的方法, 不符合 ocp,
解決方法可以把 Factory Method Pattern 當作 Simple Factory Pattern進階版, 建立 Factory 的子類別並將產生物件的動作寫在這些子類別中
這是原本的Dialog類別

package com.foxx.simplefactory;
import android.app.Dialog;
import android.content.Context;
import android.widget.Button;
import android.widget.TextView;
public class CustomDialog extends Dialog
{
    private String mDescription;
    private TextView mUpTextView;
    private TextView mDownTextView;
    private Button mUpButton;
    private Button mDownButton;
    /**
     * Used in one button
     */
    public CustomDialog(Context context, Button upButton) {
        super(context);
    }
    /**
     * Used in two button
     */
    public CustomDialog(Context context, Button upButton, Button downButton) {
        super(context);
    }
    /**
     * Used in one button and one description
     */
    public CustomDialog(Context context, Button upButton, String description) {
        super(context);
    }
}

 
首先把原本的Factory抽象化

package com.foxx.factorymethod;
import com.foxx.simplefactory.CustomDialog;
public abstract class CustomDialogFactory
{
    protected CustomDialog mCustomDialog;
    public CustomDialog createDialog(){
        return mCustomDialog;
    }
}

 
建立對應第一個建構式的SubFactory

package com.foxx.factorymethod;
import android.content.Context;
import android.widget.Button;
import com.foxx.simplefactory.CustomDialog;
public class OneButtonDialogFactory extends CustomDialogFactory
{
    public OneButtonDialogFactory(Context context, Button upButton) {
        mCustomDialog = new CustomDialog(context, upButton);
    }
}

 
建立對應第二個建構式的SubFactory

package com.foxx.factorymethod;
import android.content.Context;
import android.widget.Button;
import com.foxx.simplefactory.CustomDialog;
public class TwoButtonDialogFactory extends CustomDialogFactory
{
    public TwoButtonDialogFactory(Context context, Button upButton, Button downButton) {
        mCustomDialog = new CustomDialog(context, upButton, downButton);
    }
}

 
建立對應第三個建構式的SubFactory

package com.foxx.factorymethod;
import android.content.Context;
import android.widget.Button;
import android.widget.TextView;
import com.foxx.simplefactory.CustomDialog;
public class OneButtonOneDescriptionDialogFactory extends CustomDialogFactory
{
    public OneButtonOneDescriptionDialogFactory(Context context, Button upButton, String description) {
        mCustomDialog = new CustomDialog(context, upButton, description);
    }
}

 
最後是MainActivity

package com.foxx.factorymethod;
import com.foxx.simplefactory.CustomDialog;
public class MainActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        CustomDialogFactory oneButtonFactory = new OneButtonDialogFactory(context, upButton);
        CustomDialog oneDialog = oneButtonFactory.createDialog();
        CustomDialogFactory twoButtonFactory = new TwoButtonDialogFactory(context, upButton, downButton);
        CustomDialog twoDialog = twoButtonFactory.createDialog();
        CustomDialogFactory oneButtonOneDescriptionFactory = new OneButtonOneDescriptionDialogFactory(context, upButton, description);
        CustomDialog oneButtonOneDescriptionDialog = oneButtonOneDescriptionFactory.createDialog();
    }
}

若之後還有第4種,第5種Dialog 可以另外新增SubFactory繼承CustomDialogFactory, 而不需要修改已經寫好的部份
任何的Dialog若有變動需要修改的話, 可到各對應的SubFactory修改即可, 不會改動到其他SubFactory
 

Class Diagram

 
factorymethod
 
 
 
 
 
 
 

分類
Design Pattern

使用Simple Factory Pattern替換多建構式

Simple Factory Pattern:

提供產生物件的方法, 而無須關注實作內容, 其物件類型可為interface, abstract class, class

 
當類別內有多建構式存在且每個建構式無法清楚的說明其用途時, 可使用Simple Factory代替建構式, 在SimpleFactory中以清楚的方法名稱說明建構式的用途
這是我在專案遇到的情況, 我必須寫出1個Dialog, 此Dialog的元件在不同的情況下會有不同的組合, 如在A情況此dialog只有1個按鈕, 在B情況會有2個按鈕
一開始考慮使用建構式來決定產生何種dialog

package com.foxx.simplefactory;
import android.app.Dialog;
import android.content.Context;
import android.widget.Button;
import android.widget.TextView;
public class CustomDialog extends Dialog
{
    private String mDescription;
    private TextView mUpTextView;
    private TextView mDownTextView;
    private Button mUpButton;
    private Button mDownButton;
    /**
     * Used in one button
     */
    public CustomDialog(Context context, Button upButton) {
        super(context);
    }
    /**
     * Used in two button
     */
    public CustomDialog(Context context, Button upButton, Button downButton) {
        super(context);
    }
    /**
     * Used in one button and one description
     */
    public CustomDialog(Context context, Button upButton, String description) {
        super(context);
    }
}

 
CustomDialog的使用者除了從建構式的參數以及註解外, 無法了解建構式的使用方式(意圖), 為了解決這個情況我試著引入Simple Factory來修改

package com.foxx.simplefactory;
import android.content.Context;
import android.widget.Button;
public class CustomDialogFactory
{
    public static CustomDialog createOneButtonDialog(Context context, Button upButton)
    {
        return new CustomDialog(context, upButton);
    }
    public static CustomDialog createTwoButtonDialog(Context context, Button upButton,
            Button downButton)
    {
        return new CustomDialog(context, upButton, downButton);
    }
    public static CustomDialog createOneButtonOneDescriptionDialog(Context context,
            Button upButton, String description)
    {
        return new CustomDialog(context, upButton, description);
    }
}

 
使用者可根據Simple Factory的方法名稱選擇建立CustomDialog物件

package com.foxx.simplefactory;
import com.example.simplefactory.R;
import com.foxx.factorymethod.*;
public class MainActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CustomDialog oneButtonDialog = CustomDialogFactory.createOneButtonDialog(context, upButton);
        CustomDialog twoButtonDialog = CustomDialogFactory.createTwoButtonDialog(context, upButton, downButton);
        CustomDialog oneButtonOneDescriptionDialog =
        CustomDialogFactory.createOneButtonOneDescriptionDialog(context, upButton, description);
    }
}

 

Class Diagram

simplefactory_classdiagram
 

分類
Design Pattern Refactoring

使用策略模式(Strategy Pattern)取代 switch

一般來說使用switch通常會產生一些缺點,e.g.
1. 同樣的 switch 散佈在不同的位置時, 當新的需求出現而需要修改 switch 時,必須找出所有的 switch 一併修改。
2. 因為 switch 代表做了複數的工作,導致 switch 很難符合開閉原則 (OCP)。
3. switch 中條件區塊擴大時會讓 switch 越來越難以理解。
以上的因素也可套用到 if-else。
因此可考慮使用 Strategy Pattern 取代 switch 或是 if-else
簡單的switch如下

    private static void useSwitchExample(){
        switch(condition){
            case 0:
                System.out.println("audio error");
                break;
            case 1:
                System.out.println("client error");
                break;
            case 2:
                System.out.println("not match");
                break;
        }
    }

 
根據 condition 執行不同的 case 條件,會印出不同的錯誤訊息,為了取代 switch ,先建立 Strategy 如下

public interface IErrorStrategy
{
    public void showErrorMessage();
   
    class AudioError implements IErrorStrategy{
        @Override
        public void showErrorMessage()
        {
            System.out.println("audio error");
        }
    }
   
    class ClientError implements IErrorStrategy{
        @Override
        public void showErrorMessage()
        {
            System.out.println("client error");
        }
    }
   
    class NoMatchError implements IErrorStrategy{
        @Override
        public void showErrorMessage()
        {
            System.out.println("no match");
        }
    }
}

 
在 showErrorMessage 方法即是印出各個錯誤代碼,接著建立 Manager 來控制 Strategy e.g.,

public class ErrorStrategyManager
{
    public static final int AUDIO_ERROR = 0;
    public static final int CLIENT_ERROR = 1;
    public static final int NOMATCH_ERROR = 2;
    private Map<Integer, IErrorStrategy> mErrors = new HashMap<Integer, IErrorStrategy>();
    public ErrorStrategyManager() {
        initErrors();
    }
    private void initErrors()
    {
        mErrors.put(CLIENT_ERROR, new IErrorStrategy.ClientError());
        mErrors.put(AUDIO_ERROR, new IErrorStrategy.AudioError());
        mErrors.put(NOMATCH_ERROR, new IErrorStrategy.NoMatchError());
    }
    public void showErrorMessage(int errorMessage)
    {
        IErrorStrategy error = mErrors.get(errorMessage);
        error.showErrorMessage();
    }
}

 
其中使用 map 來對應不同的情況對應不同的 Strategy ,便可取代原本的 switch

    private static void useStrategyInsteadSwitchExample(){
        new ErrorStrategyManager().showErrorMessage(condition);
    }

 
以上的處理方式有個缺點,如果傳入的數值沒有在對應的項目之中就會出現 NullPointerException. e.g.,

new ErrorStrategyManager().showErrorMessage(100);

 
因此我們必須考慮 “例外” 的情況發生,另外判斷 null 的狀態, e.g.,

    public void showErrorMessage(int errorMessage) {
        IErrorStrategy error = mErrors.get(errorMessage);
        if (error != null) {
            error.showErrorMessage();
        } else {
            System.out.println("no this error message");
        }
    }

 
或是確實使用 “例外” 來處理,端看是否把該情況視為例外。

    public void showErrorMessage(int errorMessage) {
        try {
            IErrorStrategy error = mErrors.get(errorMessage);
            error.showErrorMessage();
        } catch (NullPointerException e) {
            System.out.println("no this error message");
        }
    }

 
以下是另外一個範例。
對字串做檢查,判斷字串是否符合某些規則。
一般來說最簡單直覺的方式就是以 if-else 來判斷 e.g.,

        public boolean checkData(String data)
        {
            if(data == null){
                return false;
            }else if(data.equals("")){
                return false;
            }
            return true;
        }

 
當然也具有在開頭提到的種種缺點,因此我們一樣使用 Strategy 來取代 if-else

public interface DataFormatChecker
{
    public static final String TAG = DataFormatChecker.class.getSimpleName();
    public boolean checkData(String data);
    class CheckAllRules implements DataFormatChecker
    {
        private Collection<DataFormatChecker> mRules = new ArrayList<DataFormatChecker>();
        public CheckAllRules() {
            mRules.add(new CheckNull());
            mRules.add(new CheckEmpty());
        }
        @Override
        public boolean checkData(String data)
        {
            for (DataFormatChecker checker : mRules) {
                if (checker.checkData(data)) {
                    Log.d(TAG, "which checker:" + checker.getClass().getSimpleName());
                    return false;
                }
            }
            return true;
        }
    }
    class CheckNull implements DataFormatChecker
    {
        @Override
        public boolean checkData(String data)
        {
            return null == data;
        }
    }
    class CheckEmpty implements DataFormatChecker
    {
        @Override
        public boolean checkData(String data)
        {
            return data.equals("");
        }
    }
}

第7行的 CheckAllRules 會將所有的規則物件放到 list 中,在其 checkData 方法(第18行)將這些規則物件取出並比對字串與規則。
之後若有新的規則只要新增 CheckXXX 類別 implements DataFormatChecker,再加入到 CheckAllRules 的 list 中。
外部呼叫 e.g.,

String data = "1234";
new DataFormatChecker.CheckAllRules().checkData(data);

 
另外也可使用 enum 取代 CheckAllRules e.g,

public interface DataFormatChecker
{
    ...
    public enum CheckAllRules {
        CHECK_NULL(new DataFormatChecker.CheckNull()),
        CHECK_EMPTY(new DataFormatChecker.CheckEmpty());
        private DataFormatChecker mChecker;
        private CheckAllRules(DataFormatChecker checker) {
            mChecker = checker;
        }
        public static boolean checkData(String data)
        {
            for (CheckAllRules type : CheckAllRules.values()) {
                if (type.mChecker.checkData(data)) {
                    Log.d(TAG, "check data error:" + type.mChecker.getClass().getSimpleName());
                    return false;
                }
            }
            Log.d(TAG, "check data valid");
            return true;
        }
    }
    ...
}

 
若有新的規則需求只要新增 CHECK_XXX(new DataFormatChecker.CheckXXX()) 在 enum 中,
並實作 DataFormatChecker.CheckXXX 內容 e.g.。

    public enum CheckAllRules {
        CHECK_NULL(new DataFormatChecker.CheckNull()),
        CHECK_EMPTY(new DataFormatChecker.CheckEmpty()),
        CHECK_LENGTH_MAX(new DataFormatChecker.CheckLengthMax());
        ...
    }
    ...
    class CheckLengthMax implements DataFormatChecker
    {
        @Override
        public boolean checkData(String data)
        {
            return data.length() > 100;
        }
    }

 
外部呼叫不需要修改(符合 OCP),但我們已經新增了另一項檢查規則(Check Length Max)了。

String data = "1234";
new DataFormatChecker.CheckAllRules().checkData(data);

 

分類
Design Pattern

Observer Pattern(觀察者模式)

一對多關係(Subject & Observer),實際資料位於主題(Subject)中,觀察者依附於主題,只要主題資料有更動,觀察者也會一併被通知
主題類別(要發送訊息的類別實作它)

  public interface Subject {//主題
      public void registerObserver(Observer ob);// 註冊觀察者
      public void removeObserver(Observer ob);// 移除觀察者
      public void notifyObservers();// 通知所有觀察者資料變動
  }

 
觀察者類別(要接收訊息的類別實作它)

  import java.util.ArrayList;
  interface Observer {// 觀察者
      public void updateNum(ArrayList<Integer> al);// 所有觀察者更新資料
  }

 

樂透開獎機(這是我的主題)

  import java.util.ArrayList;
  import java.util.Random;
  public class LottoSystem implements Subject, Runnable {// Runnable只用於方便開獎,沒有其他用途
      ArrayList<Observer> obsal;// 存放所有觀察者
      ArrayList<Integer> numberal;// 存放樂透數字
      public LottoSystem() {
          obsal = new ArrayList<Observer>();
          numberal = new ArrayList<Integer>();
          for (int i = 0; i < 6; i++) {// 初始 6 個號碼為 0
              numberal.add(0);
          }
      }
      public void registerObserver(Observer ob) {
          obsal.add(ob);
      }
      public void removeObserver(Observer ob) {
          int i = obsal.indexOf(ob);
          if (i >= 0) {
              obsal.remove(i);
          }
      }
      // 主題更新資料之後,必須呼叫此方法以通知所有觀察者
      public void notifyObservers() {
          for (int i = 0; i < obsal.size(); i++) {
              Observer ob = (Observer) obsal.get(i);
              ob.updateNum(numberal);
          }
      }
      public void lottery() {
          Random r = new Random();
          for (int i = 0; i < numberal.size(); i++) {
              int tempnum = r.nextInt(48);
              for (int j = 0; j < numberal.size(); j++) {
                  if (tempnum == numberal.get(j) && i != j) {
                      tempnum = r.nextInt(48);
                      j = 0;
                      i = 0;
                  }
              }
              numberal.set(i, tempnum);
          }
          notifyObservers();
      }
      // 模擬開獎
      public void run() {
          while (true) {
              lottery();
              try {
                  Thread.currentThread().sleep(1500);
              } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
          }
      }
  }

 

這是購買樂透的人(也是觀察者)

  import java.util.ArrayList;
  public class ShowLottoNum implements Observer {
      String name;
      ArrayList<Integer> numlist;// 存放樂透數字
      Subject s;// 主題,用來註冊該觀察者
      // 當觀察者建構自己必須傳入已存在的主題物件以註冊自己本身
      public ShowLottoNum(Subject s, String name) {
          this.s = s;
          s.registerObserver(this);
          this.name = name;
      }
      @Override
      public void updateNum(ArrayList<Integer> al) {
          // TODO Auto-generated method stub
          numlist = al;
          showNum();
      }
      public void showNum() {
          System.out.print(name + " get number :");
          for (int i = 0; i < numlist.size(); i++) {
              System.out.print(numlist.get(i) + ",");
          }
          System.out.println();
      }
  }

測試程式

  import java.util.ArrayList;
  import java.util.List;
  public class Simulator {
      public static void main(String[] args) {
          LottoSystem ls = new LottoSystem();// 主題
          ShowLottoNum sln = new ShowLottoNum(ls, "John");// 觀察者1(若需要更多觀察者,只要照著
                                                          // ShowLottoNum 建構即可 )
          ShowLottoNum sln2 = new ShowLottoNum(ls, "Peter");// 觀察者2
          ShowLottoNum sln3 = new ShowLottoNum(ls, "Little flower");// 觀察3
          // 開始開獎
          new Thread(ls).start();
      }
  }
分類
Design Pattern

Strategy Pattern(策略模式)

1.把常變動的部分封裝起來
2.使用實作介面來建立物件
3.多用合成少用繼承
範例

  public class Simulator {
      /**
       * @param args
       */
      public static void main(String[] args) {
          // TODO Auto-generated method stub
          Character sprite = new Sprite();//使用多型建立
          Character normalman = new NormalMan();//同上
          Character samuri = new Samuri();//同上
          Character monsterboss = new MonsterBoss();//同上
          //executeAttack 是 Character 類別的方法,方法內容為 ab 呼叫 attack 方法
               //而各 ab 是在每個建構子中建立不同的 ab
          sprite.executeAttack();
          normalman.executeAttack();
          samuri.executeAttack();
          monsterboss.executeAttack();
               //動態改變 ab
          monsterboss.setAttackBehavior(new AttackBySword());
          monsterboss.executeAttack();
          monsterboss.setAttackBehavior(new AttackByMagic());
          monsterboss.executeAttack();
      }
  }

 

Character.java

    
  public abstract class Character {
   
      AttackBehavior ab;//(攻擊行為)常變動的部分封裝成類別,宣告為介面型態可以動態改變
      public void executeAttack(){
          ab.attack();
      }
      public void setAttackBehavior(AttackBehavior ab){
          this.ab = ab;
      }
  }
   
  class NormalMan extends Character{
      public NormalMan(){
          ab = new AttackByHand();
      }
  }
   
  class Sprite extends Character{
      public Sprite(){
          ab = new AttackByMagic();
      }
  }
   
  class Samuri extends Character{
      public Samuri(){
          ab = new AttackBySword();
      }
  }
   
  class MonsterBoss extends Character{
      public MonsterBoss(){
          ab = new AttackByHand();
      }
  }

如果之後還有新的角色(Character)種類,可以繼續擴充,不影響原角色類別

   
  public interface AttackBehavior {
      public void attack();
  }
   
  class AttackByHand implements AttackBehavior{
      public void attack(){
          System.out.println("AttackByHand");
      }
  }
   
  class AttackBySword implements AttackBehavior{
      public void attack(){
          System.out.println("AttackBySword");
      }
  }
   
  class AttackByMagic implements AttackBehavior{
      public void attack(){
          System.out.println("AttackByMagic");
      }
  }

如果之後有新的攻擊行為種類,可以繼續擴充,不影響原攻擊行為類別