組合模式定義:

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

需求:

必須提供一個機制讓資料可以轉換成 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()));
    }