通过公共API导出非公共类型

时间:2010-04-30 13:37:03

标签: java api inheritance

如果我有很少的工厂方法返回非公共类型和配对方法集合给出这种非公开类型的变量怎么办?这会在NetBeans中标题为警告消息。

结果公共API将只包含两组配对方法。原因是密封我的类型层次结构(如Scala中的封装类),并允许用户仅通过工厂方法实例化这些类型。所以我们在某种意义上得到了DSL。

例如,Schedule类由日历字段的约束表示。有一些类型的约束 - Range,Singleton,List,FullSet - 以NumberSet接口作为根。我们不希望公开这些类型以及Schedule如何与它们进行交互。我们只想要用户的规范。所以我们将NumberSet包私有化。在课程表中,我们为约束创建了一些工厂方法:

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

以及创建Schedule对象的一些方法:

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

用户只能以下列方式使用它们:

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

这是个坏主意吗?

2 个答案:

答案 0 :(得分:9)

这个想法本身就是合理的。但是如果你将 root type (这里: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之外的任何类都是不可见的。它甚至无法在它从某个地方获得的public实例上调用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。拥有它的主要好处:客户端可以声明此类型的变量,并且编译器能够键入check。但是,您的框架仍然是唯一可以创建此类型的具体实例的地方,因此,您的框架可以完全控制所有值。

答案 1 :(得分:0)

我可以想到两种方法来做到这一点,但两种方法都不起作用:

  1. NumberSet声明为包私有,但是将当前使用NumberSet类型的公共API方法更改为使用Object,并让实现从{{{{}}转换参数根据需要1}}到Object。这依赖于调用者总是传递正确类型的对象,并且易于NumberSet s。

  2. 实现一个没有方法的公共ClassCastException接口和实现NumberSet的私有InternalNumberSet包,并定义内部所需的方法。再次使用类型转换将参数NumberSet强制转换为NumberSet。这比以前的方法更好,但是如果某人创建了一个实现InternalNumberSet的类而没有实现NumberSet,那么结果将再次成为InternalNumberSet

  3. 我个人认为您应该将ClassCastException界面声明为公开。在接口中公开getter没有真正的危害,如果你希望你的实现类是不可变的,请将它们声明为final,并且不要公开setter。