Java新手的跌倒日記 - Tell Don't Ask?

 Wed, 26 Dec 2007 13:35:42 +0800

最近在MockObjects網站上看到的文章:
Mock Objects: Tell Don't Ask and Mock Objects

簡單地說,就是讓物件在做判斷決策時,只倚賴內部的資訊或是透過參數傳進來的資訊,而不倚賴其他物件所有的資訊。透過這樣的設計,可以讓物件之間的耦合降低,同時也更容易做單元測試。

作者舉了一個極端的反例:像 "object.getPart().getSubpart().getAttribute()" 這樣的敘述如果在程式中出現的話,因為耦合了隔鄰的物件,同時又耦合了隔鄰的隔鄰物件,結果就很難用Mock Object來進行單元測試了。

解決的辦法,是透過refactoring重新設計....不知道怎麼做耶???作者說在隔鄰的物件新增方法???

我嘗試了一下,應該可以透過Eclipse的「產生委派方法」來實現???不是很確定,不過動手試了一下:

package com.fillano;
public class SubPart {
private String attrib;
public String getAttrib() {
return attrib;
}
public void setAttrib(String attrib) {
this.attrib = attrib;
}
}
SubPart.java

package com.fillano;
public class Part {
private SubPart subPart;
public Part () {
subPart = new SubPart();
}
public SubPart getSubPart() {
return subPart;
}
public void setSubPart(SubPart subPart) {
this.subPart = subPart;
}
}
Part.java

package com.fillano;
public class Product {
private Part part;
public Product () {
part = new Part();
}
public Part getPart() {
return part;
}
public void setPart(Part part) {
this.part = part;
}
}
Product.java

這樣,就可以重現作者描述的train-wreck:

Product pd = new Product();
pd.getPart().getSubPart().setAttrib("an attrib");
System.out.println(pd.getPart().getSubPart.getAttrib());

透過在Product與Part類別新增委派方法,可以讓Product直接get/set SubPart的attrib:

package com.fillano;
public class Part {
private SubPart subPart;
public Part() {
subPart = new SubPart();
}
public String getAttrib() {
return subPart.getAttrib();
}
public void setAttrib(String attrib) {
subPart.setAttrib(attrib);
}
public SubPart getSubPart() {
return subPart;
}
public void setSubPart(SubPart subPart) {
this.subPart = subPart;
}
}
新的Part類別

接著可以在Product類別中加入更多委派方法:

package com.fillano;
public class Product {
private Part part;
public Product() {
part = new Part();
}
public String getAttrib() {
return part.getAttrib();
}
public SubPart getSubPart() {
return part.getSubPart();
}
public void setAttrib(String attrib) {
part.setAttrib(attrib);
}
public void setSubPart(SubPart subPart) {
part.setSubPart(subPart);
}
public Part getPart() {
return part;
}
public void setPart(Part part) {
this.part = part;
}
}
新的Product類別

這樣似乎透過委派方法就可以去掉train-wreck的程式碼:

Product pd = new Product();
pd.setAttrib("an attrib");
System.out.println(pd.getAttrib());

說實話,這並不是refactoring,我也沒有改進物件之間的耦合。想了一下,有一個原因是設計太簡單,其實無法反映出tell don't ask style的精神。


2008-1-25 更新

根據在java world看到的文章,我這樣做並不是委派方法,而是request forwarding與composition。他認為真正的委派方法中,物件會把自己當作委派方法的參數傳遞給執行委派的物件。


2008-1-30 更新

請看續集:Java新手的跌倒日記 - 再看 Tell don't ask 及委派方法,我把問題重新想了一下....