2013-03-27 54 views
3

我正在尋找裝飾模式的替代方案,以使其更具動態性。作爲一個簡單的例子,假設我們有這樣的代碼:面向對象編程:實時添加功能

interface Resource { 
    public String getName(); 
} 

interface Wrapper extends Resource { 
    public Resource getSource(); 
} 

interface Readable extends Resource { 
    public InputStream getInputStream(); 
} 

interface Listable extends Resource { 
    public List<Resource> getChildren(); 
} 



class File implements Readable { 
    ... 
} 

class Zip implements Listable, Wrapper { 
    public Zip(Readable source) { ... } 
} 

正如你可以看到,郵編不直接實現它是從做讀書讀,但是資源。假設我們構造一個ZIP:

Zip zip = new Zip(new File()); 

我不想(也不能)堆棧的所有接口相互延伸(如可列延伸可讀的),我也可以構建所有的對象全部實行功能,因爲不是所有的功能都相互關聯,你希望能夠通過包裝它們來「裝飾」對象。

我確定這是一個常見問題,但有沒有解決它的模式?使用「包裝器」界面,您當然可以探測資源鏈來檢查功能,但我不確定這是否是一種理智的方法。

UPDATE

的問題是上面不那麼你不能建立的接口一個不錯的層次中的所有功能都與作爲說明。例如,假設你有這樣的任意新功能:如果您運行

interface Rateable extends Resource { 
    public int getRating(); 
} 

class DatabaseRateable implements Rateable, Wrapper { 
    public DatabaseRateable(Resource resource) { ... } 
} 

Resource resource = new DatabaseRateable(new Zip(new File)); 

產生的資源已經「丟失」的所有功能(可讀,可列,...)加入該。 讓Rateable擴展爲Listable將是荒謬的。

再次我可以遞歸地檢查resource.getSource()並找出所有的功能。在即時回覆中沒有明確的解決方案,所以遞歸檢查畢竟是一個很好的選擇?

+0

爲了什麼目的,你裝飾的對象? – flup 2013-03-27 08:42:44

+0

爲什麼'Zip'也不能執行'Readable'? – 2013-03-27 08:42:54

+1

你可以提供一個具體的例子,這是一個問題嗎? – Dai 2013-03-27 08:44:20

回答

0

我會提出我自己的建議(如在原來的問題提出的)作爲一個答案。如果有足夠多的人認爲這是一個有價值的解決方案,或者沒有更好的解決方案,我會接受它。

總之,我自己的解決方案包括使用Wrapper接口向後瀏覽資源以確定哪些功能存在。給出的例子:

Resource resource = new DatabaseRateable(new Zip(new File)); 

你能想象這樣做:

public Readable asReadable(Resource resource) { 
    if (resource instanceof Readable) 
     return (Readable) resource; 
    else if (resource instanceof Wrapper) 
     return (asReadable(((Wrapper) resource).getSource()); 
    else 
     return null; 
} 
2

這聽起來像你在這裏爭取的概念是duck typing,而java本身並不是在本地執行的(請參閱我的關於基於反射的java庫的評論)。但是,JVM上運行的其他語言當然可以。例如 - groovy

class Duck { 
    quack() { println "I am a Duck" } 
} 

class Frog { 
    quack() { println "I am a Frog" } 
} 

quackers = [ new Duck(), new Frog() ] 
for (q in quackers) { 
    q.quack() 
} 

你可以寫在Groovy代碼,並把它旁邊的Java代碼的無縫剩下的工作,並在Groovy解決這個問題。

+0

雖然有趣,但groovy不是一種選擇,嚴格的打字是必須的。 – nablex 2013-03-27 09:41:11

+0

@ user1109519 - 在這種情況下,您可以考慮使用庫來在java中輸入鴨子 - http://code.google.com/p/duckapter/。 請注意,它使用了反射,但除非您在一個不會對您造成太大影響的緊密循環中調用這些東西。 – radai 2013-03-27 11:21:16

0

裝飾對象時,通常只修飾對象的一個​​接口,以修改或只添加其行爲的一個方面。 您可以用不同的裝飾器修飾對象的另一個接口。這些裝飾器可以同時存在。

您可以將一個裝飾器傳遞給一個方法,將另一個裝飾器傳遞給另一個方法。

當你希望首先用幾個裝飾器來裝飾對象,然後在你的代碼中傳遞對象時,這會變得怪異。

因此,對於您的情況,我建議您將裝飾器再次封裝到一個單獨的對象中,該對象知道資源中存在哪種裝飾器。

class Resource { 
    private Readable readable; 
    private Listable listable; 
    private Rateable rateable; 

    setReadable(Readable readable) { 
     this.readable = readable; 
    } 

    setListable(Listable listable) { 
     this.listable = listable; 
    } 

    setRateable(Rateable rateable) { 
     this.rateable = rateable; 
    } 

    public boolean isRateable(){ 
     return rateable != null; 
    } 

    public Rateable getRateable(){ 
     return rateable; 
    } 
    // etc 
} 

File file1 = new File(); 
Resource resource = new Resource(file1); 
resource.setReadable(new ReadableFile(file1)); 
resource.setListable(new ListableFile(file1)); 
resource.setRateable(new DatabaseRateableFile(file1)); 

然後,您可以傳遞資源並且其用戶可以發現此特定資源具有哪些功能。

Qi4j framework允許您以更清晰的方式使用註釋執行此操作(以及更多操作)。你將碎片組合成複合材料。不過,這確實需要一些習慣。將自己的特定實現部署到資源中的好處是,向其他人解釋會更容易。

+0

問題是初始對象沒有實現這些功能。功能被添加(並且他們自己可以被裝飾)。裝飾者模式假定你簡單地覆蓋現有的方法,而不是基本上實時添加它們。 – nablex 2013-03-27 08:59:03

+0

*重讀*不完全正確,您可以使用裝飾器添加行爲。 – flup 2013-03-27 09:08:33

+0

是的,但如果你添加行爲,生成的裝飾器仍然意味着實現已經存在的行爲的所有方法,此時它變得站不住腳。問題的確是我想傳遞一個裝飾物體。 – nablex 2013-03-27 09:17:47

2

我想你在找什麼是mixins

鏈接的維基百科頁面有支持它們的OOP語言的一個很好的列表。或者你是否專門與Java綁定?

+0

運行時mixin(與編譯時相對)可以提供解決方案,但我非常依賴於java – nablex 2013-03-27 08:56:34

+1

有一個用於java的mixin實現,請參閱[Qi4j](http://qi4j.org /latest/ten-minutes-intro.html)。它從片段構建複合材料。不過,這可能是一個有點重量級的解決方案。 – flup 2013-03-27 09:46:56

+0

@flup如果我正確地理解了框架,Qi4j不是「動態」的,因爲複合材料等都必須編譯。但是這些功能是完全動態的,不應該要求重建才能正常工作。 – nablex 2013-03-28 14:06:57

0

也許adapter pattern可以幫助:

Readable r = zip.adapt(Readable.class); 

這問法adapt()返回的zip一個實例,實現Readable接口。

實現通常使用一個「適配器管理器」,它知道如何爲所有註冊類型構建包裝器。

+0

儘管適配器模式可能會按照代碼方式實現,但模式本身(在我看來)並不是爲它設計的。它意味着將相似但具有不同接口的功能轉換爲彼此。手頭的問題是全新的,不相關的功能。你可以將Zip修改爲Readable,但是我認爲它偏離了模式的意圖。 – nablex 2013-03-27 09:14:55

+0

代碼設計始終是相互矛盾的目標之間的選擇。當你堅持「純潔」的解決方案時,答案是:這是不可能的。 Java根本沒有這些工具。所以你必須接受一些解決方法。適配器模式足夠靈活,可以在您明確傳達意圖的情況下完成所需的任務。模式文檔沒有提到這個用例並不是反對使用的論據,因爲文檔可能是錯誤的。 – 2013-03-27 09:59:07

+0

完全屬實,我自己的解決方案(請參閱原始問題或我自己的答案)遵循相同的stategem(鑄造到您需要的),但沒有實際的適配器。因此它與上述模式相差太遠。 – nablex 2013-03-27 10:12:56

0

這裏也許不那麼恰當,而是一個動態特徵發現:

public class Features { 

    public <T> lookup(Class<T> intface) 
      throws UnsupportedOperationException { 
     return lookup(intface, intface.getSimpleName()); 
    } 

    public <T> lookup(Class<T> intface, String name) 
      throws UnsupportedOperationException { 
     return map.get(...); 
    } 
} 

public class X { 
    public final Features FEATURES = new Features(); 
    ... 
} 

X x; 
Readable r = x.FEATURES.lookup(Readable.class);