通过公共API导出非公共类型
如果我有很少的工厂方法返回非公共类型和配对方法集合给出这种非公共类型的变量怎么办? 这会在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)) );
这不好主意吗?
这个想法本身就是合理的。 但是如果你将root类型 (here: 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
实例上的public
方法,因为它不知道这样的公共方法存在(它看不到类,更不用说它暴露的任何成员)。 所以,客户在这里做的最好的事情是这样的:
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 } }
就客户端代码而言,上述类是“空的”; 它不提供可用的API,除了Object
已经提供的东西。 拥有它的主要好处:客户端可以声明此类型的变量,并且编译器能够键入check。 但是,您的框架仍然是唯一可以创建此类型的具体实例的地方,因此,您的框架可以完全控制所有值。
我可以想到两种方法来做到这一点,但两种方法都没有真正起作用:
-
将
NumberSet
声明为包private,但是将当前使用NumberSet
类型的公共API方法更改为使用Object
,并根据需要将实现从Object
为NumberSet
。 这依赖于调用者总是传递正确类型的对象,并且将容易出现ClassCastException
。 -
实现没有方法的公共
NumberSet
接口,并打包实现NumberSet
私有InternalNumberSet
并定义内部所需的方法。 类型转换再次用于将参数NumberSet
为InternalNumberSet
。 这比以前的方法更好,但是如果有人创建了一个实现NumberSet
但没有实现InternalNumberSet
,那么结果将再次成为ClassCastException
。
我个人认为你应该将NumberSet
接口声明为public。 在接口中公开getter没有真正的危害,如果你希望你的实现类是不可变的,请将它们声明为final,不要公开setter。