這次總結乙個個人認為的反模式:「繫結子類的泛型層基類」,這個模式在一些著名的框架中也見到過,如果csla、blogengine。我自己在原來的寫的框架中,也用到過。
當然了,個人認為是反模式,各們同仁並不一定這樣認為,仁者見仁,智者見智了。不過我好幾次都是受盡折磨,所以決定寫出來給大家分享下心得。
模式介紹
「層基類」是mf提出的乙個基本模式,詳見:《layer supertype》。這種模式在經典的層次型架構設計的實現中,是極其重要的。我相信,大家一般在做三層架構時,不可能不給出基類的。至少我沒見過。:)
.net2.0推出後,帶來了新的語言特性:《泛型》。它實現了型別的執行時多型,是一種強大的語言特性。
今天要說的主題正是基於layersupertype,並結合了泛型技術而實現的,同樣,它還有乙個重要的約定:泛型的型別引數必須是最終的子類。看如下乙個例子:
1
public
abstract
class
entitybase
1
2
3
4
5
6
7
8
9
10
11
12
13
where
t : entitybase
//sth else important......
}
public
class
user : entitybase
}
public
class
article : entitybase
}
entitybase作為所有實體類的基類,提供了統一的實體模板、約定和一些通用的基礎實現。基於這個基類的**重用,使得子類的**非常簡單。這裡和普通繼承、普通泛型的不同點在於父類在執行時繫結了具體子類的型別。
設計原理
為什麼要這樣設計?基類為什麼不直接使用非泛型的基類呢?這是為了在基類實現的通用方法中,能夠以強型別的方式直接訪問最終的子類。用上面的類舉個例子,如果你使用「activerecord模式」,那麼要是使用非泛型的基類,你可能會在entitybase中加入方法:
1
2
3
4
5
6
7
public
abstract
class
entitybase
}
1
使用時:
1
entitybase user = user.getbyid(id);
但是,使用泛型基類繫結具體的子類後,我們會這樣寫**:
1
2
3
4
5
6
7
public
abstract
class
entitybase
}
1
user user = user.getbyid(id);
也就是說,這是一種更加型別安全的api,用起來會很方便。
再舉乙個例子:由於泛型基類執行時繫結了不同的子類,使得它本身的靜態字段繫結到最終的子類中的。例如上文中的例子,entitybase和 entitybase其實是不同的兩個執行時型別。這樣,當我在entitybase內宣告的靜態欄位是繫結到各子類中的。如:我在entitybase中宣告了靜態字段:
public abstract class entitybase
那麼這個欄位並不是為所有子類共享,而是user.typename和article.typename的值不同,分別是"user」和「article」。同樣的功能,如果你要使用非泛型的基類,由於所有型別共享乙個執行時基類,你需要考慮為在基類中為每個具體的型別儲存對應的值,例如,使用乙個字典儲存:
1
2
3
4
5
6
7
8
public
abstract
class
entitybase
}
這樣的api用起來,是不是很不易用呢?
上面只是舉了些最簡單的例子,實際上,由於使用了繫結具體子類的泛型基類,還會有很多地方的設計變得更簡單了,在此不再一一枚舉。
帶來的問題
使用這種模式,缺點是顯而易見的:
1. 不能直接使用基類進行統一的處理
繼續上面的例子,這樣的設計,使得我們不能對所有的實體進行統一的處理。由於user和article的基類其實是兩個不同的執行時型別,所以我不能把它們轉換為同乙個「實體」型別。如:
entitybase a = new article();
a = new user();
我甚至都不可能用到抽象的entitybase類,因為我要使用此類,必須指定具體的子類,但是我如果知道要使用哪個具體的子類,也就沒有必要使用它們的基類了。也就是說,根本就不存在實體的抽象類,而entitybase存在的意義只是為了**重用。我不知道這是否能看為違反了oo的liskov替換原則,不過真是難以忍受。
2. 無法直接實現實體的再繼承
第二個問題,同樣是繼承機制的問題。我無法從現在的具體實體類直接進行派生!!!我無法使用這樣的語法:goodarticle : article。這是因為article已經「告訴」基類entitybase繫結子類的型別是article,而不是goodarticle,這按照entitybase設計時的約定「t必須是最終的子類」相矛盾!
無法繼承……繼承作為oo三大特性中的乙個,這個問題簡直無法忍受。
想辦法繞開這兩個問題
其實,上面提到的兩個問題,在技術上都是能夠找到一些方法來解決的:
1. 無法向基類轉換。
這個問題產生的原因,主要是因為沒有乙個「與子類無關的抽象」存在。我們可以為entitybase新增ientity介面,這樣,所有的子類都能轉換為ientity,也就能進行統一的處理。
2. 無法再繼承。
要解決這個問題,我們需要把需要進行再繼承的類也提取為乙個泛型基類和乙個繼承此基類的空的子類。如:
1
2
3
4
5
6
7
8
9
10
11
public
class
article: entitybase
where
t : article
//...
}
public
class
article : article
public
class
goodarticle : article
這樣的方案好像可以解決,但是這樣的設計實在讓人難以接受:
* 作為設計類庫來說,我只是新增了乙個單向依賴父類的子類,卻不得不修改父類的**,分離為兩個類。
* 要不就是所有的類都直接寫成乙個泛型類+乙個空子類的方法。(這個設計醜陋嗎?)
* 沒有解決根本的問題:toparticle 並不是乙個 article,它只是乙個和article有重用**的類而已。
小結
在被這樣的設計折磨多次後,我反思了這篇文章,並決定以後再不使用這樣的設計。希望別人不再犯同樣的錯誤…… :)
不知道對於這個問題,大家有什麼看法?歡迎拍磚。
繫結子類的泛型基類,反模式?
這次總結乙個個人認為的反模式 繫結子類的泛型層基類 這個模式在一些著名的框架中也見到過,如果csla blogengine。我自己在原來的寫的框架中,也用到過。當然了,個人認為是反模式,各們同仁並不一定這樣認為,仁者見仁,智者見智了。不過我好幾次都是受盡折磨,所以決定寫出來給大家分享下心得。模式介紹...
泛型的基類和介面
象其他的型別一樣,泛型也可以定義基類和介面。注意 當子類實現泛型基類時候,需要指定到底是什麼型別。當子類實現泛型基類的abstract或者virtual的方法時候,也要指定型別。例如 a generic class with a virtual method.public class mylist ...
子類父類,介面實現的泛型
package zmx.stringbuilder import org.omg.corba.public member 父類為泛型類 1 屬性 2 方法 要麼同時擦除,要麼子類大於等於父類的型別,便於新增種類,class son extends father class sonextends fa...