拒絕裝飾模式
裝飾模式是委托的一種,它更注重的是對原有對象功能的擴充。是以是一個使用頻率較高的模式。但是!今天我要告訴你,在一些時候,你應該拒絕它的“誘惑”
==============
背景
通常,在我們的程式中都會存在兩類javaBean:Entity(實體類)與Dto(資料傳輸類)。
其中Entity是最普通的javaBean,隻有屬性與setter/getter方法,通常作為資料持久化操作。
而Dto負責應用的業務邏輯中的資料傳遞,除屬性以外,還會有一些業務邏輯方法。通常,Dto是Entity的擴充。
實際的使用情況是什麼呢?我們在開發中并沒有将二者區分開,而是合二為一,以Entity為基礎,在上面添加額外的屬性和方法。這樣做有什麼好處呢?
好處:
- 不需要維護兩套類
- Entity與Dto有着高度相似性,合二為一後不需要寫重複的代碼。
缺點:
- 一個本應該“幹淨”的javaBean混入了過多的業務邏輯,從Entity的角度,這已經不單一了。
- Entity、Dto合二為一的後果就是你得到了一個臃腫的Dto
- 在使用了ORM架構的情況下,為了區分出需要持久化和不需要持久化的屬性,不得不又引入了注解。
- 一個永遠沒有答案的問題:javaBean裡究竟應不應該有計算方法?
那麼分析完優缺點後,接下來,我們嘗試将二者拆開。
拆開絕對不是簡單拆除兩套類,這是非常愚蠢的。
既然Dto是Entity的擴充,那麼很快我就想到了裝飾模式。功能擴充呀,沒錯的。
嘗試
這裡假設你已經對裝飾模式比較了解了。
第一步:建立,如果使用裝飾模式,Entity和Dto就應該有一套相同的接口。
額……javaBean的接口?難道你是說getter和setter方法? 哦~哪個傻蛋會這麼做……
姑且我們傻了一回,這樣做了。第二步:使用,在使用Dto的過程中,你需要再new一個Dto裝飾者。然後将Entity的對象設定進去,就像這樣:
//通過manager取得了user對象
UserEntity userEntity = userManager.getUser();
//要使用Dto了
IUser userDto = new UserDto(userEntity);
....
我不知道你怎麼看這個寫法,我覺得有點蠢。
我為自己規定的代碼優化與重構守則:
- 在不增加原有代碼編寫複雜度的情況進行優化。
以前我就是看着難受了一點,現在你這麼一搞,直接給我增加勞動量了,我肯定不願意!!!
就這麼走了兩步就走不下去了~怎麼辦吧你說……
拒絕裝飾模式
說到功能擴充,你除了裝飾模式,還能想到什麼?繼承!!
比較喜劇性的是,在設計模式之禅這本書中,襯托裝飾模式的就是繼承。
繼承的缺點有哪些呢?當需要擴充的情況變多以後,容易造成類爆炸。多層繼承結構維護困難等等~然後呢?其實有時候你會發現,這些對你來說并不是問題。
比學會設計模式更重要的事情是知道什麼時候該使用設計模式!
你真的有很多擴充的可能嗎?NO~至少我開發Androd的兩年以來并沒有遇到這樣的需求。你真的有很多繼承結構嗎?NO~除了android native元件,真的很少會遇到超兩層繼承結構的東西。
既然繼承的缺點不是缺點了,那麼你可以理直氣壯的拒絕裝飾模式了嗎?
來,我們直接上代碼。
案例:紀念日類
MemorialDayEntity.java 實體類。用于資料持久化的一些基本屬性。
public class MemorialDayEntity {
protected int id;
protected String title;
protected String datetime;
protected String buildName;
protected String buildAccount;
protected boolean loop;
public MemorialDayEntity(){}
public MemorialDayEntity(String buildAccount, String buildName, String datetime, int id, boolean loop, String title) {
this.buildAccount = buildAccount;
this.buildName = buildName;
this.datetime = datetime;
this.id = id;
this.loop = loop;
this.title = title;
}
/**
* getter and setter
*/
}
MemorialDayDto.java Dto類。在Entity的基礎上增加了許多需要臨時計算的屬性:在重複類型與不重複類型下與目前日期的內插補點,用于顯示的組合名稱等等。
按時
public class MemorialDayDto extends MemorialDayEntity {
private static final DateFormat DATE_FORMAT = SimpleDateFormat.getDateInstance();
private Calendar calendar = GregorianCalendar.getInstance();
private Date nowDate;
private Date targetDate;
private int dateYear;
private int dateMonth;
private int dateDay;
private String nameStr;
private long day;
public MemorialDayDto(String buildAccount, String buildName, String datetime, int id, boolean loop, String title) {
super(buildAccount, buildName, datetime, id, loop, title);
try {
nowDate = new Date();
targetDate = DATE_FORMAT.parse(datetime);
calendar.setTime(targetDate);
dateYear = calendar.get(Calendar.YEAR);
dateMonth = calendar.get(Calendar.MONTH);
dateDay = calendar.get(Calendar.DATE);
calculateDate();
} catch (ParseException e) {
e.printStackTrace();
}
}
public String getDateStr() {
return dateYear + "年" + dateMonth + "月" + dateDay + "日";
}
private void calculateDate() {
//日期計算
}
}
兩個類我們設計完了。要怎麼用呢?
在之前二者合二為一的項目代碼基礎上,不需要做任何修改,還是直接使用Dto類。
不同的是在做資料儲存時。這個時候也是非常簡單的。看代碼:
Dao層資料儲存方法:
public interface MemorialDayDao{
public void save(MemorialDayEntity entity);
....
}
class Client{
MemorialDayDto memorialDayDto = //一些列的操作後我們得到了紀念日Dto類
MemorialDayDao mDao = Dao.getMemorialDayDao();
//就這樣,這裡也幾乎沒有任何改變
mDao.save(memorialDayDto);
}
發現我們的改變了嗎?就是将需要持久化的屬性抽出放到父類中當做Entity。
為什麼在資料儲存時,Dao層需要的是Entity,我們傳Dto确沒有問題呢?你難道忘啦?這可是正經的裡氏替換啊:父類出現的地方,子類都可以無縫替換。
沒錯,就是無縫,相對于裝飾模式的改變,使用了繼承,就實作了無縫的代碼優化。爽不爽???
裝飾模式的優點
雖然我們拒絕了裝飾模式,但還是要知道它的優點的
- 裝飾模式的特點是動态。這是對比繼承非常大的一個特點
- 你可以定義非常多的裝飾類,這些裝飾類的維護成本較繼承的成本是較低裡的。
- 裝飾模式的擴充和原本的基礎體是分開解耦的
裝飾模式的缺點
- 多層裝飾和多層繼承一樣,非常容易帶來維護困難的問題。
- 裝飾模式并不是絕對的優于繼承。這需要充分考慮使用場景,就像我們這篇文章所講的。