寫在前面:
- 你好,歡迎你的閱讀!
- 我熱愛技術,熱愛分享,熱愛生活, 我始終相信:技術是開源的,知識是共享的!
- 博客裏面的內容大部分均爲原創,是自己日常的學習記錄和總結,便於自己在後面的時間裏回顧,當然也是希望可以分享自己的知識。目前的內容幾乎是基礎知識和技術入門,如果你覺得還可以的話不妨關注一下,我們共同進步!
- 除了分享博客之外,也喜歡看書,寫一點日常雜文和心情分享,如果你感興趣,也可以關注關注!
- 微信公衆號:傲驕鹿先生
六、合成模式(Composite Pattern)
合成模式屬於對象的結構模式,又叫做“部分——整體”模式。合成模式將對象組織到樹結構中,可以用來描述整體與部分的關係。合成模式可以使客戶端將單純元素與複合元素同等看待。
比如,一個文件系統就是一個典型的合成模式系統。下圖是常見的計算機XP文件系統的一部分。
從上圖可以看出,文件系統是一個樹結構,樹上長有節點。樹的節點有兩種,一種是樹枝節點,即目錄,有內部樹結構,在圖中塗有顏色;另一種是文件,即樹葉節點,沒有內部樹結構。顯然,可以把目錄和文件當做同一種對象同等對待和處理,這也就是合成模式的應用。
合成模式可以不提供父對象的管理方法,但是合成模式必須在合適的地方提供子對象的管理方法,諸如:add()、remove()、以及getChild()等。
合成模式的實現根據所實現接口的區別分爲兩種形式,分別稱爲安全式和透明式。
6.1、安全式合成模式
安全模式的合成模式要求管理聚集的方法只出現在樹枝構件類中,而不出現在樹葉構件類中。
這種形式涉及到三個角色:
●抽象構件(Component)角色:這是一個抽象角色,它給參加組合的對象定義出公共的接口及其默認行爲,可以用來管理所有的子對象。合成對象通常把它所包含的子對象當做類型爲Component的對象。在安全式的合成模式裏,構件角色並不定義出管理子對象的方法,這一定義由樹枝構件對象給出。
●樹葉構件(Leaf)角色:樹葉對象是沒有下級子對象的對象,定義出參加組合的原始對象的行爲。
●樹枝構件(Composite)角色:代表參加組合的有下級子對象的對象。樹枝構件類給出所有的管理子對象的方法,如add()、remove()以及getChild()。
抽象構件角色類
public interface Component {
/**
* 輸出組建自身的名稱
*/
public void printStruct(String preStr);
}
樹枝構件角色類
public class Composite implements Component {
//用來存儲組合對象中包含的子組件對象
private List<Component> childComponents = new ArrayList<Component>();
//組合對象的名字
private String name;
/**
* 構造方法,傳入組合對象的名字
* @param name 組合對象的名字
*/
public Composite(String name){
this.name = name;
}
/**
* 聚集管理方法,增加一個子構件對象
* @param child 子構件對象
*/
public void addChild(Component child){
childComponents.add(child);
}
/**
* 聚集管理方法,刪除一個子構件對象
* @param index 子構件對象的下標
*/
public void removeChild(int index){
childComponents.remove(index);
}
/**
* 聚集管理方法,返回所有子構件對象
*/
public List<Component> getChild(){
return childComponents;
}
/**
* 輸出對象的自身結構
* @param preStr 前綴,主要是按照層級拼接空格,實現向後縮進
*/
@Override
public void printStruct(String preStr) {
// 先把自己輸出
System.out.println(preStr + "+" + this.name);
//如果還包含有子組件,那麼就輸出這些子組件對象
if(this.childComponents != null){
//添加兩個空格,表示向後縮進兩個空格
preStr += " ";
//輸出當前對象的子對象
for(Component c : childComponents){
//遞歸輸出每個子對象
c.printStruct(preStr);
}
}
}
}
樹葉構件角色類
public class Composite implements Component {
//用來存儲組合對象中包含的子組件對象
private List<Component> childComponents = new ArrayList<Component>();
//組合對象的名字
private String name;
/**
* 構造方法,傳入組合對象的名字
* @param name 組合對象的名字
*/
public Composite(String name){
this.name = name;
}
/**
* 聚集管理方法,增加一個子構件對象
* @param child 子構件對象
*/
public void addChild(Component child){
childComponents.add(child);
}
/**
* 聚集管理方法,刪除一個子構件對象
* @param index 子構件對象的下標
*/
public void removeChild(int index){
childComponents.remove(index);
}
/**
* 聚集管理方法,返回所有子構件對象
*/
public List<Component> getChild(){
return childComponents;
}
/**
* 輸出對象的自身結構
* @param preStr 前綴,主要是按照層級拼接空格,實現向後縮進
*/
@Override
public void printStruct(String preStr) {
// 先把自己輸出
System.out.println(preStr + "+" + this.name);
//如果還包含有子組件,那麼就輸出這些子組件對象
if(this.childComponents != null){
//添加兩個空格,表示向後縮進兩個空格
preStr += " ";
//輸出當前對象的子對象
for(Component c : childComponents){
//遞歸輸出每個子對象
c.printStruct(preStr);
}
}
}
}
樹葉構件角色類
public class Leaf implements Component {
/**
* 葉子對象的名字
*/
private String name;
/**
* 構造方法,傳入葉子對象的名稱
* @param name 葉子對象的名字
*/
public Leaf(String name){
this.name = name;
}
/**
* 輸出葉子對象的結構,葉子對象沒有子對象,也就是輸出葉子對象的名字
* @param preStr 前綴,主要是按照層級拼接的空格,實現向後縮進
*/
@Override
public void printStruct(String preStr) {
System.out.println(preStr + "-" + name);
}
}
客戶端類
public class Client {
public static void main(String[]args){
Composite root = new Composite("服裝");
Composite c1 = new Composite("男裝");
Composite c2 = new Composite("女裝");
Leaf leaf1 = new Leaf("襯衫");
Leaf leaf2 = new Leaf("夾克");
Leaf leaf3 = new Leaf("裙子");
Leaf leaf4 = new Leaf("套裝");
root.addChild(c1);
root.addChild(c2);
c1.addChild(leaf1);
c1.addChild(leaf2);
c2.addChild(leaf3);
c2.addChild(leaf4);
root.printStruct("");
}
}
可以看出,樹枝構件類(Composite)給出了addChild()、removeChild()以及getChild()等方法的聲明和實現,而樹葉構件類則沒有給出這些方法的聲明或實現。這樣的做法是安全的做法,由於這個特點,客戶端應用程序不可能錯誤地調用樹葉構件的聚集方法,因爲樹葉構件沒有這些方法,調用會導致編譯錯誤。安全式合成模式的缺點是不夠透明,因爲樹葉類和樹枝類將具有不同的接口。
6.2、透明式合成模式結構
與安全式的合成模式不同的是,透明式的合成模式要求所有的具體構件類,不論樹枝構件還是樹葉構件,均符合一個固定接口。
抽象構件角色類
public abstract class Component {
/**
* 輸出組建自身的名稱
*/
public abstract void printStruct(String preStr);
/**
* 聚集管理方法,增加一個子構件對象
* @param child 子構件對象
*/
public void addChild(Component child){
/**
* 缺省實現,拋出異常,因爲葉子對象沒有此功能
* 或者子組件沒有實現這個功能
*/
throw new UnsupportedOperationException("對象不支持此功能");
}
/**
* 聚集管理方法,刪除一個子構件對象
* @param index 子構件對象的下標
*/
public void removeChild(int index){
/**
* 缺省實現,拋出異常,因爲葉子對象沒有此功能
* 或者子組件沒有實現這個功能
*/
throw new UnsupportedOperationException("對象不支持此功能");
}
/**
* 聚集管理方法,返回所有子構件對象
*/
public List<Component> getChild(){
/**
* 缺省實現,拋出異常,因爲葉子對象沒有此功能
* 或者子組件沒有實現這個功能
*/
throw new UnsupportedOperationException("對象不支持此功能");
}
}
樹枝構件角色類,此類將implements Conponent改爲extends Conponent,其他地方無變化。
public class Composite extends Component {
/**
* 用來存儲組合對象中包含的子組件對象
*/
private List<Component> childComponents = new ArrayList<Component>();
/**
* 組合對象的名字
*/
private String name;
/**
* 構造方法,傳入組合對象的名字
* @param name 組合對象的名字
*/
public Composite(String name){
this.name = name;
}
/**
* 聚集管理方法,增加一個子構件對象
* @param child 子構件對象
*/
public void addChild(Component child){
childComponents.add(child);
}
/**
* 聚集管理方法,刪除一個子構件對象
* @param index 子構件對象的下標
*/
public void removeChild(int index){
childComponents.remove(index);
}
/**
* 聚集管理方法,返回所有子構件對象
*/
public List<Component> getChild(){
return childComponents;
}
/**
* 輸出對象的自身結構
* @param preStr 前綴,主要是按照層級拼接空格,實現向後縮進
*/
@Override
public void printStruct(String preStr) {
// 先把自己輸出
System.out.println(preStr + "+" + this.name);
//如果還包含有子組件,那麼就輸出這些子組件對象
if(this.childComponents != null){
//添加兩個空格,表示向後縮進兩個空格
preStr += " ";
//輸出當前對象的子對象
for(Component c : childComponents){
//遞歸輸出每個子對象
c.printStruct(preStr);
}
}
}
}
樹葉構件角色類,此類將implements Conponent改爲extends Conponent,其他地方無變化。
public class Leaf extends Component {
/**
* 葉子對象的名字
*/
private String name;
/**
* 構造方法,傳入葉子對象的名稱
* @param name 葉子對象的名字
*/
public Leaf(String name){
this.name = name;
}
/**
* 輸出葉子對象的結構,葉子對象沒有子對象,也就是輸出葉子對象的名字
* @param preStr 前綴,主要是按照層級拼接的空格,實現向後縮進
*/
@Override
public void printStruct(String preStr) {
// TODO Auto-generated method stub
System.out.println(preStr + "-" + name);
}
}
客戶端類的主要變化是不再區分Composite對象和Leaf對象。
public class Client {
public static void main(String[]args){
Component root = new Composite("服裝");
Component c1 = new Composite("男裝");
Component c2 = new Composite("女裝");
Component leaf1 = new Leaf("襯衫");
Component leaf2 = new Leaf("夾克");
Component leaf3 = new Leaf("裙子");
Component leaf4 = new Leaf("套裝");
root.addChild(c1);
root.addChild(c2);
c1.addChild(leaf1);
c1.addChild(leaf2);
c2.addChild(leaf3);
c2.addChild(leaf4);
root.printStruct("");
}
}
可以看出,客戶端無需再區分操作的是樹枝對象(Composite)還是樹葉對象(Leaf)了;對於客戶端而言,操作的都是Component對象。
6.3、兩種模式的選擇
-
這裏所說的安全性合成模式是指:從客戶端使用合成模式上看是否更安全,如果是安全的,那麼就不會有發生誤操作的可能,能訪問的方法都是被支持的。
-
這裏所說的透明性合成模式是指:從客戶端使用合成模式上,是否需要區分到底是“樹枝對象”還是“樹葉對象”。如果是透明的,那就不用區分,對於客戶而言,都是Compoent對象,具體的類型對於客戶端而言是透明的,是無須關心的。
對於合成模式而言,在安全性和透明性上,會更看重透明性,畢竟合成模式的目的是:讓客戶端不再區分操作的是樹枝對象還是樹葉對象,而是以一個統一的方式來操作。
而且對於安全性的實現,需要區分是樹枝對象還是樹葉對象。有時候,需要將對象進行類型轉換,卻發現類型信息丟失了,只好強行轉換,這種類型轉換必然是不夠安全的。
因此在使用合成模式的時候,建議多采用透明性的實現方式。
6.4、優點和缺點
優點:
-
合成模式可以清楚地定義分層次的複雜對象,表示對象的全部或部分層次,它讓客戶端忽略了層次的差異,方便對整個層次結構進行控制。
-
客戶端可以一致地使用一個組合結構或其中單個對象,不必關心處理的是單個對象還是整個組合結構,簡化了客戶端代碼。
-
在合成模式中增加新的容器構件和葉子構件都很方便,無須對現有類庫進行任何修改,符合“開閉原則”。
-
合成模式爲樹形結構的面向對象實現提供了一種靈活的解決方案,通過葉子對象和容器對象的遞歸組合,可以形成複雜的樹形結構,但對樹形結構的控制卻非常簡單。
缺點:
-
破壞了“單一職責原則”。
-
在增加新構件時很難對容器中的構件類型進行限制。有時候我們希望一個容器中只能有某些特定類型的對象,例如在某個文件夾中只能包含文本文件,使用組合模式時,不能依賴類型系統來施加這些約束,因爲它們都來自於相同的抽象層,在這種情況下,必須通過在運行時進行類型檢查來實現,這個實現過程較爲複雜。
6.5、模式應用
組合模式使用面向對象的思想來實現樹形結構的構建與處理,描述瞭如何將容器對象和葉子對象進行遞歸組合,實現簡單,靈活性好。由於在軟件開發中存在大量的樹形結構,因此組合模式是一種使用頻率較高的結構型設計模式,Java SE中的AWT和Swing包的設計就基於組合模式,在這些界面包中爲用戶提供了大量的容器構件(如Container)和成員構件(如Checkbox、Button和TextArea等)。
七、享元模式( Flyweight Pattern)
Flyweight在拳擊比賽中指最輕量級,即“蠅量級”或“雨量級”,這裏選擇使用“享元模式”的意譯,是因爲這樣更能反映模式的用意。享元模式是對象的結構模式。享元模式以共享的方式高效地支持大量的細粒度對象。
在JAVA語言中,String類型就是使用了享元模式。String對象是final類型,對象一旦創建就不可改變。在JAVA中字符串常量都是存在常量池中的,JAVA會確保一個字符串常量在常量池中只有一個拷貝。String a="abc",其中"abc"就是一個字符串常量。
public class Test {
public static void main(String[] args) {
String a = "abc";
String b = "abc";
System.out.println(a==b);
}
}
上面的例子中結果爲:true ,這就說明a和b兩個引用都指向了常量池中的同一個字符串常量"abc"。這樣的設計避免了在創建N多相同對象時所產生的不必要的大量的資源消耗。
享元模式採用一個共享來避免大量擁有相同內容對象的開銷。這種開銷最常見、最直觀的就是內存的損耗。享元對象能做到共享的關鍵是區分內蘊狀態(Internal State)和外蘊狀態(External State)。
-
一個內蘊狀態是存儲在享元對象內部的,並且不會隨環境改變而有所不同。因此,一個享元可以具有內蘊狀態並可以共享。
-
一個外蘊狀態是隨環境的改變而改變的、不可以共享的。享元對象的外蘊狀態必須由客戶端保存,並在享元對象被創建之後,在需要使用的時候再傳入到享元對象內部。外蘊狀態不可以影響享元對象的內蘊狀態,它們是相互獨立的。
享元模式可以分成單純享元模式和複合享元模式兩種形式。
7.1、單純享元模式結構
單純享元模式所涉及到的角色如下:
●抽象享元(Flyweight)角色 :給出一個抽象接口,以規定出所有具體享元角色需要實現的方法。
●具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定出的接口。如果有內蘊狀態的話,必須負責爲內蘊狀態提供存儲空間。
●享元工廠(FlyweightFactory)角色 :本角色負責創建和管理享元角色。本角色必須保證享元對象可以被系統適當地共享。當一個客戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當創建一個合適的享元對象。
抽象享元角色類
public interface Flyweight {
//一個示意性方法,參數state是外蘊狀態
public void operation(String state);
}
具體享元角色類ConcreteFlyweight有一個內蘊狀態,在本例中一個Character類型的intrinsicState屬性代表,它的值應當在享元對象被創建時賦予。所有的內蘊狀態在對象創建之後,就不會再改變了。
如果一個享元對象有外蘊狀態的話,所有的外部狀態都必須存儲在客戶端,在使用享元對象時,再由客戶端傳入享元對象。這裏只有一個外蘊狀態,operation()方法的參數state就是由外部傳入的外蘊狀態。
public class ConcreteFlyweight implements Flyweight {
private Character intrinsicState = null;
/**
* 構造函數,內蘊狀態作爲參數傳入
* @param state
*/
public ConcreteFlyweight(Character state){
this.intrinsicState = state;
}
/**
* 外蘊狀態作爲參數傳入方法中,改變方法的行爲,但是並不改變對象的內蘊狀態。
*/
@Override
public void operation(String state) {
// TODO Auto-generated method stub
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}
享元工廠角色類,必須指出的是,客戶端不可以直接將具體享元類實例化,而必須通過一個工廠對象,利用一個factory()方法得到享元對象。一般而言,享元工廠對象在整個系統中只有一個,因此也可以使用單例模式。
當客戶端需要單純享元對象的時候,需要調用享元工廠的factory()方法,並傳入所需的單純享元對象的內蘊狀態,由工廠方法產生所需要的享元對象。
public class FlyweightFactory {
private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
public Flyweight factory(Character state){
//先從緩存中查找對象
Flyweight fly = files.get(state);
if(fly == null){
//如果對象不存在則創建一個新的Flyweight對象
fly = new ConcreteFlyweight(state);
//把這個新的Flyweight對象添加到緩存中
files.put(state, fly);
}
return fly;
}
}
客戶端類
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
FlyweightFactory factory = new FlyweightFactory();
Flyweight fly = factory.factory(new Character('a'));
fly.operation("First Call");
fly = factory.factory(new Character('b'));
fly.operation("Second Call");
fly = factory.factory(new Character('a'));
fly.operation("Third Call");
}
}
雖然客戶端申請了三個享元對象,但是實際創建的享元對象只有兩個,這就是共享的含義。運行結果如下:
7.2、複合享元模式
在單純享元模式中,所有的享元對象都是單純享元對象,也就是說都是可以直接共享的。還有一種較爲複雜的情況,將一些單純享元使用合成模式加以複合,形成複合享元對象。這樣的複合享元對象本身不能共享,但是它們可以分解成單純享元對象,而後者則可以共享。
複合享元角色所涉及到的角色如下:
●抽象享元(Flyweight)角色 :給出一個抽象接口,以規定出所有具體享元角色需要實現的方法。
●具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定出的接口。如果有內蘊狀態的話,必須負責爲內蘊狀態提供存儲空間。
●複合享元(ConcreteCompositeFlyweight)角色 :複合享元角色所代表的對象是不可以共享的,但是一個複合享元對象可以分解成爲多個本身是單純享元對象的組合。複合享元角色又稱作不可共享的享元對象。
●享元工廠(FlyweightFactory)角色 :本角 色負責創建和管理享元角色。本角色必須保證享元對象可以被系統適當地共享。當一個客戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有 一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當創建一個 合適的享元對象。
抽象享元角色類
public interface Flyweight {
//一個示意性方法,參數state是外蘊狀態
public void operation(String state);
}
具體享元角色類
public class ConcreteFlyweight implements Flyweight {
private Character intrinsicState = null;
/**
* 構造函數,內蘊狀態作爲參數傳入
* @param state
*/
public ConcreteFlyweight(Character state){
this.intrinsicState = state;
}
/**
* 外蘊狀態作爲參數傳入方法中,改變方法的行爲,但是並不改變對象的內蘊狀態。
*/
@Override
public void operation(String state) {
// TODO Auto-generated method stub
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}
複合享元對象是由單純享元對象通過複合而成的,因此它提供了add()這樣的聚集管理方法。由於一個複合享元對象具有不同的聚集元素,這些聚集元素在複合享元對象被創建之後加入,這本身就意味着複合享元對象的狀態是會改變的,因此複合享元對象是不能共享的。
複合享元角色實現了抽象享元角色所規定的接口,也就是operation()方法,這個方法有一個參數,代表複合享元對象的外蘊狀態。一個複合享元對象的所有單純享元對象元素的外蘊狀態都是與複合享元對象的外蘊狀態相等的;而一個複合享元對象所含有的單純享元對象的內蘊狀態一般是不相等的,不然就沒有使用價值了。
public class ConcreteCompositeFlyweight implements Flyweight {
private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
/**
* 增加一個新的單純享元對象到聚集中
*/
public void add(Character key , Flyweight fly){
files.put(key,fly);
}
/**
* 外蘊狀態作爲參數傳入到方法中
*/
@Override
public void operation(String state) {
Flyweight fly = null;
for(Object o : files.keySet()){
fly = files.get(o);
fly.operation(state);
}
}
}
享元工廠角色提供兩種不同的方法,一種用於提供單純享元對象,另一種用於提供複合享元對象。
public class FlyweightFactory {
private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
/**
* 複合享元工廠方法
*/
public Flyweight factory(List<Character> compositeState){
ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight();
for(Character state : compositeState){
compositeFly.add(state,this.factory(state));
}
return compositeFly;
}
/**
* 單純享元工廠方法
*/
public Flyweight factory(Character state){
//先從緩存中查找對象
Flyweight fly = files.get(state);
if(fly == null){
//如果對象不存在則創建一個新的Flyweight對象
fly = new ConcreteFlyweight(state);
//把這個新的Flyweight對象添加到緩存中
files.put(state, fly);
}
return fly;
}
}
客戶端角色
public class Client {
public static void main(String[] args) {
List<Character> compositeState = new ArrayList<Character>();
compositeState.add('a');
compositeState.add('b');
compositeState.add('c');
compositeState.add('a');
compositeState.add('b');
FlyweightFactory flyFactory = new FlyweightFactory();
Flyweight compositeFly1 = flyFactory.factory(compositeState);
Flyweight compositeFly2 = flyFactory.factory(compositeState);
compositeFly1.operation("Composite Call");
System.out.println("---------------------------------");
System.out.println("複合享元模式是否可以共享對象:" + (compositeFly1 == compositeFly2));
Character state = 'a';
Flyweight fly1 = flyFactory.factory(state);
Flyweight fly2 = flyFactory.factory(state);
System.out.println("單純享元模式是否可以共享對象:" + (fly1 == fly2));
}
}
從運行結果可以看出,一個複合享元對象的所有單純享元對象元素的外蘊狀態都是與複合享元對象的外蘊狀態相等的。即外運狀態都等於Composite Call。
從運行結果可以看出,一個複合享元對象所含有的單純享元對象的內蘊狀態一般是不相等的。即內蘊狀態分別爲b、c、a。
從運行結果可以看出,複合享元對象是不能共享的。即使用相同的對象compositeState通過工廠分別兩次創建出的對象不是同一個對象。
從運行結果可以看出,單純享元對象是可以共享的。即使用相同的對象state通過工廠分別兩次創建出的對象是同一個對象。
7.3、優缺點
享元模式的優點在於它大幅度地降低內存中對象的數量。但是,它做到這一點所付出的代價也是很高的:
●享元模式使得系統更加複雜。爲了使對象可以共享,需要將一些狀態外部化,這使得程序的邏輯複雜化。
●享元模式將享元對象的狀態外部化,而讀取外部狀態使得運行時間稍微變長。