Java 通过公共API导出非公共类型
如果我有几个返回非公共类型的工厂方法和一组给出这种非公共类型变量的方法,该怎么办?这会导致NetBeans中出现标题为“警告”的消息 结果,公共API将只包含两组方法。原因是要将我的类型层次结构密封(就像Scala中的seald类),并且只允许用户通过工厂方法实例化这些类型。所以我们在某种意义上得到了DSL 例如,由日历字段的约束表示的Schedule类。有一些类型的约束-范围、单例、列表、全集-以NumberSet接口作为根。我们不想公开这些类型以及Schedule如何与它们交互。我们只需要用户提供规范。所以我们将NumberSet包设为私有。在类计划中,我们为约束创建了几个工厂方法:Java 通过公共API导出非公共类型,java,api,inheritance,Java,Api,Inheritance,如果我有几个返回非公共类型的工厂方法和一组给出这种非公共类型变量的方法,该怎么办?这会导致NetBeans中出现标题为“警告”的消息 结果,公共API将只包含两组方法。原因是要将我的类型层次结构密封(就像Scala中的seald类),并且只允许用户通过工厂方法实例化这些类型。所以我们在某种意义上得到了DSL 例如,由日历字段的约束表示的Schedule类。有一些类型的约束-范围、单例、列表、全集-以NumberSet接口作为根。我们不想公开这些类型以及Schedule如何与它们交互。我们只需要用
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)) );
这是个坏主意吗?这个主意本身就是好主意。但如果将根类型(此处:
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
,因为该类型对于包示例
之外的任何类都是不可见的。它甚至不能在它从某处获得的Bar
实例上调用public
方法,因为它不知道这样一个公共方法的存在(它看不到类,更不用说它公开的任何成员了)。因此,客户在这里能做的最好的事情是:
Object bar = foo.newBar();
这基本上是无用的。另一种情况是使用公共接口(或抽象类)而不是包私有接口,如上面定义的代码所示。在这种情况下,客户机实际上可以声明NumberSet
类型的变量。它不能创建自己的实例或派生子类,因为构造函数是隐藏的,但它可以访问定义的公共API
再次编辑即使您想要一个“无特征”值(从客户端的角度来看),即一个不定义客户端可能要调用的任何有趣API的值,以上述方式公开公共基类型仍然是一个好主意。并且,这仅仅是为了编译器能够执行类型检查,并允许客户端代码将这样的值临时存储到(正确声明的)变量中
如果您不希望您的客户机调用该类型上的任何API方法:那很好。没有任何东西可以阻止您在(否则)公共基类型上不提供公共API。只是不要申报。使用一个“空”抽象基类(从客户机的角度来看是空的,因为所有感兴趣的方法都是包私有的,因此是隐藏的)。但是您必须提供一个公共基类型,或者您应该使用普通的对象
作为返回值,但是在编译时您将放弃错误检查
经验法则:如果客户机必须调用某个方法才能获得一个值并将其传递给其他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。拥有它的主要好处是:客户端可以声明这种类型的变量,编译器可以进行类型检查。但是,您的框架仍然是唯一可以创建这种类型的具体实例的地方,因此,您的框架可以完全控制所有值。我可以想出两种方法来实现这一点,但这两种方法都不起作用:
NumberSet
声明为包私有,但将当前使用NumberSet
类型的公共API方法改为使用Object
,并让实现根据需要将参数从Object
强制转换为NumberSet
。这依赖于调用方始终传递正确类型的对象,并且容易出现ClassCastException
sNumberSet
接口和包privateInternalNumberSet
,该接口实现NumberSet
并定义内部所需的方法。类型转换再次用于将参数NumberSet
转换为InternalNumberSet
。这比前面的方法更好,但是如果有人创建了一个实现NumberSet
的类,而没有同时实现InternalNumberSet
,那么结果将再次是ClassCastException
s我个人认为您应该将
NumberSet
接口声明为public。在接口中公开getter并没有真正的危害,如果您希望实现类是不可变的,请将它们声明为final,不要公开setter。是的,但看到这种类型是不必要的。我们只需要通过工厂方法进行规范,并通过工厂方法为调度对象传递规范。