This page looks plain and unstyled because you're using a non-standard compliant browser. To see it in its best form, please visit upgrade to a browser that supports web standards. It's free and painless.

Fillano's Learning Notes 會員登入 會員註冊
在之前的文章「Java新手的跌倒日記 - Tell Don't Ask?」裡面,搞不懂Nat Pryce所說的Tell don't ask的意思。這幾天看了一些委派方法以及composite pattern的文章,重新設計了一個產品/零件的例子,似乎問題就比較清楚了。

之前嘗試實作出Nat Pryce所說的不良示範,但是對於如何改良比較摸不著頭緒。後來想了一下,其實零件可以組合成產品,產品又可能是其他產品的零件,這是一種composite的實例,所以再寫幾個例子來測試一下:

package idv.fillano.part;

public interface Part {
	public void setName(String name);
	public String getName();
	public void setCost(double cost);
	public Double getCost();
}
Part.java

package idv.fillano.part;

public class PartImpl implements Part {
	private Double cost=new Double(0);
	private String name="";
	public Double getCost() {
		return cost;
	}

	public String getName() {
		return name;
	}

	public void setCost(double cost) {
		this.cost = cost;
	}

	public void setName(String name) {
		this.name = name;
	}

}
PartImpl.java

package idv.fillano.part;

import java.util.ArrayList;

public interface Product {
	public int addPart(Part part);
	public Part getPart(int index);
	public void rmPart(int index);
	public Double getTotalCost();
	public Double getPartCost(String name);
	public ArrayList getPartNames();
}
Product.java

package idv.fillano.part;

import java.util.ArrayList;
import java.util.Iterator;

public class ProductImpl extends PartImpl implements Product {
	private ArrayList parts=null;

	public ProductImpl() {
		super();
		parts = new ArrayList();
	}

	public int addPart(Part part) {
		parts.add(part);
		return parts.size();
	}

	public Part getPart(int index) {
		return parts.get(index);
	}

	public Double getTotalCost() {
		Double tmp = new Double(0);
		Iterator it = parts.iterator();
		while(it.hasNext()) {
			Part pt=it.next();
			tmp += pt.getCost();
		}
		tmp += getCost();
		return tmp;
	}

	public void rmPart(int index) {
		parts.remove(index);
	}

	public Double getPartCost(String name) {
		Part tmp = getPartByName(name);
		if (tmp==null) {
			return null;
		} else {
			return tmp.getCost();
		}
	}

	public Part getPartByName(String name) {
		if (parts.size()==0) return null;
		Iterator it = parts.iterator();
		while(it.hasNext()) {
			Part tmp = it.next();
			if (tmp.getName()==name) return tmp;
			if (tmp.getClass().getName()=="idv.fillano.part.ProductImpl") {
				Part tmp1 = ((ProductImpl)tmp).getPartByName(name);
				if (tmp1!=null) return tmp1;
			}
		}
		return null;
	}
	
	public ArrayList getPartNames() {
		ArrayList  ret = new ArrayList();
		if (parts.size()==0) return ret;
		Iterator it = parts.iterator();
		while(it.hasNext()) {
			Part tmp = it.next();
			ret.add(tmp.getName());
			if (tmp.getClass().getName()=="idv.fillano.part.ProductImpl") {
				ArrayList  tmp1 = ((ProductImpl)tmp).getPartNames();
				if (tmp1.size()>0) {
					ret.addAll(tmp1);
				}
			}
		}
		return ret;
	}
}
ProductImpl.java

然後試用一下:

package idv.fillano.part;

import java.util.ArrayList;
import java.util.Iterator;
public class Main {

	public static void main(String[] args) {
		PartImpl part1 = new PartImpl();
		PartImpl part2 = new PartImpl();
		PartImpl part3 = new PartImpl();
		PartImpl part4 = new PartImpl();
		ProductImpl product1 = new ProductImpl();
		ProductImpl product2 = new ProductImpl();
		ProductImpl product3 = new ProductImpl();
		part1.setName("part1");
		part1.setCost(1.5);
		part2.setName("part2");
		part2.setCost(2.3);
		part3.setName("part3");
		part3.setCost(3.8);
		part4.setName("part4");
		part4.setCost(4.2);
		product1.setName("product1");
		product1.setCost(0.3);
		product2.setName("product2");
		product2.setCost(0.5);
		product3.setName("product3");
		product3.setCost(0.5);
		product1.addPart(part1);
		product1.addPart(part2);
		product2.addPart(product1);
		product2.addPart(part3);
		product3.addPart(part4);
		product3.addPart(product2);
		ArrayListnames = product3.getPartNames();
		System.out.println("Total cost of product3: "+product3.getTotalCost());
		System.out.println("Total cost of product2: "+product2.getTotalCost());
		System.out.println("Total cost of product1: "+product1.getTotalCost());
		System.out.println("Get part cost from product3: "+product3.getPartCost("product1"));
		System.out.println("Parts names of product3:");
		Iterator it = names.iterator();
		while(it.hasNext()) {
			System.out.println("-> "+it.next());
		}
	}

}

跑出來的結果是:

Total cost of product3: 5.2
Total cost of product2: 4.6
Total cost of product1: 4.1
Get part cost from product3: 0.3
Parts names of product3:
-> part4
-> product2
-> product1
-> part1
-> part2
-> part3

在我之前的例子裡,SubPart是Part的一個field,這樣兩個的關係就耦合了。現在用ArrayList容器,讓parts field可以放Part的子類別,多少可以解耦一點。放到parts的Part或Product是透過addPart方法放進來的,針對parts容器內物件的操作,會更像委派。

另外,使用Product的getTotalCost()可以計算所有sub parts加總的成本;使用getPartCost(part's name)方法,可以取得sub part的成本;利用getPartNames()可以取得所有sub parts的名字。

不知道我這樣做,是否就是Nat Pryce說的意思呢...

(倉促寫的例子,不是很嚴謹就是了。)

龐大的switch case常常讓程式不好維護,需要增加新功能時就得去改switch case所在的程式,讓維護比較麻煩。之前看到可以用command pattern來改進這個狀況,所以就用來試一試。

大致的構想如下:

class CommandExecuter {
  var $command=array();
  function addCommand($command,&$obj) {
    $this->command[$obj->cmdName] =& $obj;
  }
  function getAllCommand() {
    return array_keys($this->command);
  }
  function render($command) {
    $this->command[$command]->render();
  }
  function deal($command) {
    $this->command[$command]->deal();
  }
}

上面是主要的程式,用來執行所有表單產生以及回傳搜尋結果。

class Command {
  var $cmdName="";
  function Command ($str) {
    $this->cmdName = $str;
  }
  function render() {
    產生表單
    echo "<input type='hidden' name='COMMAND' value='".$this->cmdName."'>";
    ......
    echo "</form>";
  }
  function deal() {
    處理表單資料,產生搜尋結果並回傳
  }
}

以上是實際產生表單以及處理搜尋結果的命令類別。

class CommandAssembler {
  function getCommandExecuter(){
    $tmp = new CommandExecuter();
    $tmp->addCommand("search1",new Command("search1"));
    return $tmp;
  }
}

用這個類別組出完整的設計。

使用方法(產生搜尋表單):

$cmd = CommandAssembler::getCommandExecuter();
$keys = $cmd->getAllCommand();
foreach ($keys as $k) {
  $cmd->render($k);
}

使用方法(產生搜尋結果):

$cmd = CommandAssembler::getCommandExecuter();
$cmd->deal($_POST['COMMAND']);

這是大略的想法。

公司在apply一些制度,嚴格要求文件的製作。一些比較早開始的專案就麻煩了,缺了一些文件,結不了案。

原始碼裡面是有一些註解(但是不完全,趕時間就會缺),但其實Java API Doc的格式也不符合我們對於文件格式的要求。這時突然想到,一些UML工具裡面都有Reverse Engineering的工具,不知道可以拿來做什麼。

試過了Jude, StarUML, ArgoUML, BOUML等工具,我們需要的是最好可以直接轉成我們需要的格式的功能。StarUML裡面有產生Word Doc的工具,但是沒有裝word好像無法正常執行。幾經搜尋,在網路上找到用xslt的技術,把xmi格式的文件轉換成html的文件的方法。雖然還是沒有完全符合需求,但是已經可以訂製我們要的格式了。

其實Microsoft Word也有xml的檔案格式,可以考慮利用同樣的方法,把從UML工具轉出的xmi檔案轉成word xml。Openoffce的odt以及office 2007的docx格式,雖然也是xml,但是實際檔案是一個archive,裡面包含了好幾個xml檔案,為了簡化,最後自己決定用word 2003 xml格式來嘗試一下。

另外在網路上找到有人寫好的xmi-to-html的xslt解決方案:http://www.objectsbydesign.com/projects/xmi_to_html_2.html,它支援ArgoUML0.80以上版本所產生的xmi檔案,所以就以這個為基礎來改了。(另外,我是先用OpenOffice做好格式範本,存成Word 2003 xml格式,再用這個為做xslt的基礎)

跟幾個UML工具搭配來測了一下,發現ArgoUML以及BOUML所產生的xmi檔案都可以適用(BOUML必須選擇產生XMI 1.2的格式)。但是BOUML速度比較快,對於各種編碼的容錯比較好,所以後來就用BOUML來做了。(不過如果是要製作UML,我會推薦Jude,介面設計比較好。)

(BOUML可以在http://bouml.free.fr/找到)

我的步驟大致如下:

  1. 先安裝好BOUML
  2. 開新專案
  3. 選擇Languages->Java
  4. 選擇Tools->Reverse Java->跳過選擇.cat檔->選擇原始碼目錄->開始
  5. 選擇Tools->Generate XMI 1.2->指定檔名與編碼->按下Java按鈕

接下來是準備java的lib,需要到Xalan Java下載需要的工具,解開binary distribution的zip檔後,使用其中的xalan.jar以及serializer.jar兩個jar檔。

xmi-to-html裡面的run.cmd,命令我用起來怪怪的,既然已經把xalan.jar都拿來了,乾脆直接呼叫:java -jar xalan.jar -in input.xmi -xsl xmi-to-xml.xsl -out output.xml,這樣就可以把input.xmi檔案轉成word 2003 xml格式的output.xml檔案了,xmi-to-xml.xsl則是我修改過的xsl檔案,用來把xmi轉成xml。

xsl可以到下面的網址下載:http://www.fillano.idv.tw/xmi-to-xml.xsl(由於xmi-to-html是以gpl的形式發表,所以我改寫的也應該用gpl發表。詳見xmi-to-html裡面的說明。)

需要修改的話,microsoft網站上可以下載Microsoft Office 2003 XML Reference Schemas來參考,chm裡面有詳細的說明:下載