2010-04-30 87 views
7

如果我有幾個工廠方法返回非公共類型並配對給出了此非公共類型的變量的方法集,該怎麼辦?此結果帶有NetBeans中的標題警告消息。通過公共API導出非公共類型

結果公共API將只包含兩組配對方法。原因是使我的類型層次結構密封(如Scala中的密封類),並允許用戶只通過工廠方法實例化這些類型。所以我們從某種意義上來說DSL。

例如,由日曆字段的約束表示的計劃類。有一些類型的限制 - Range,Singleton,List,FullSet - 以NumberSet接口爲根。我們不想公開這些類型以及Schedule如何與它們交互。我們只需要用戶的規格。所以我們讓NumberSet package-private。在上課時間,我們創建了約束少數工廠方法:

NumberSet singleton(int value); 
NumberSet range(int form, int to); 
NumberSet list(NumberSet ... components); 

和創建計劃對象的一些方法:

Schedule everyHour(NumberSet minutes); 
Schedule everyDay(NumberSet minutes, NumberSet hours); 

用戶只能以這樣的方式使用它們:

Schedule s = Schedule.everyDay(singleton(0), list(range(10-15), singleton(8))); 

這是個壞主意嗎?

回答

9

這個想法本身很健全。但是,如果你使根類型(這裏:NumberSet)包私有,它將不起作用。要麼公開該類型,要麼使用公共接口。實際的實現類型應該(並且可以)被隱藏。

public abstract class NumberSet { 

    // Constructor is package private, so no new classes can be derived from 
    // this guy outside of its package. 
    NumberSet() { 
    } 
} 

public class Factories { 

    public NumberSet range(int start, int length) { 
     return new RangeNumberSet(start, length); 
    } 

    // ... 
} 

class RangeNumberSet extends NumberSet { 
    // ... must be defined in the same package as NumberSet 
    // Is "invisible" to client code 
} 

編輯爲了揭露從公共API隱藏/私有根型是一個錯誤。考慮以下情況:

package example; 

class Bar { 
    public void doSomething() { 
      // ... 
    } 
} 

public class Foo { 

    public Bar newBar() { 
     return new Bar(); 
    } 
} 

並考慮使用此API的客戶端應用程序。客戶可以做什麼?它不能正確聲明變量的類型爲Bar,因爲該類型對包example以外的任何類都是不可見的。它甚至不會調用Bar實例上的方法,因爲它不知道存在這樣的公共方法(它看不到該類,更不用說它暴露的任何成員)。所以,最好的客戶可以在這裏做的是這樣的:

Object bar = foo.newBar(); 

這實質上是無用的。不同的是擁有一個公共接口(或抽象類)而不是私有包,就像上面定義的代碼一樣。在這種情況下,客戶端可以通過聲明NumberSet類型的變量。它不能創建自己的實例或派生子類,因爲構造函數是隱藏的,但它可以訪問定義的公共API。

再次編輯即使您想要一個「無特徵」的值(從客戶端的角度來看),即一個沒有定義客戶端可能想要調用的有趣API的值,仍然是一個好主意按照上述方式公開基礎類型。只是爲了編譯器能夠執行類型檢查並允許客戶機代碼將這樣的值臨時存儲到(正確聲明的)變量中。

如果你不想讓你的客戶端調用任何類型的API方法:沒關係。沒有什麼能夠阻止你在你的(否則)公共基礎類型上提供一個公共API。只是不要聲明任何。使用「空的」抽象基類(從客戶端角度來看是空的,因爲所有感興趣的方法都是封裝私有的,因此是隱藏的)。但是你必須提供一個公共基類型,否則你應該使用普通的Object作爲返回值,但是在編譯時你會放棄錯誤檢查。經驗法則:如果客戶端必須調用某種方法才能獲取值並將其傳遞給其他API,那麼客戶端實際上知道知道,這裏有一些神奇的特殊值。它必須能夠以某種方式處理它(至少「傳遞它」)。除了(完全合適的)編譯器警告之外,沒有爲客戶端提供正確的類型來處理這些值不會爲您購買任何東西。

public abstract class Specification { 

    Specification() { 
     // Package private, thus not accessible to the client 
     // No subclassing possible 
    } 

    Stuff getInternalValue1() { 
     // Package private, thus not accessible to the client 
     // Client cannot call this 
    } 
} 

就客戶端代碼而言,上面的類是「空的」它除了提供Object已提供的東西外,不提供可用的API。擁有它的主要好處是:客戶端可以聲明這種類型的變量,並且編譯器能夠輸入檢查。不過,您的框架仍然是唯一可以創建此類型的具體實例的地方,因此您的框架可以完全控制所有值。

+0

爲什麼它不起作用,如果我們讓root類型的package-private? – 2010-05-01 08:42:42

+0

是的,但它是不需要的客戶端看到這種類型。我們只需要通過工廠方法進行規範,並通過工廠方法傳遞Schedule對象。 – 2010-05-03 11:02:07

0

我能想到的兩種方法可以做到這一點,但沒有真正的工作:

  1. 聲明NumberSet作爲包私有,而是改變當前使用NumberSet類型使用Object,而不是公共API方法,並根據需要,實現將參數從Object轉換爲NumberSet。這依賴於呼叫者總是傳遞正確的對象類型,並且容易出現ClassCastException

  2. 實現公共NumberSet接口沒有方法和包私人InternalNumberSet實現NumberSet並定義內部所需的方法。類型轉換再次用於將參數NumberSet轉換爲InternalNumberSet。這比以前的方法更好,但是如果有人創建了一個實現NumberSet的類而沒有執行InternalNumberSet,則結果將再次爲ClassCastException

我個人認爲你應該聲明NumberSet接口爲public。在接口中公開getter並沒有真正的危害,如果你希望你的實現類是不可變的,那麼將它們聲明爲final,並且不要公開setter。